unclean SPARC
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
<article eiccard class="fasttracks dashboard">
|
||||
<header>
|
||||
<h1>Bypass</h1>
|
||||
<h2>Global monitoring</h2>
|
||||
</header>
|
||||
<section>
|
||||
<div class="tabs-extended">
|
||||
<section>
|
||||
<menu eictab>
|
||||
<li>Tokens</li>
|
||||
<li>History</li>
|
||||
<li class="settings">Settings</li>
|
||||
</menu>
|
||||
</section>
|
||||
<!--
|
||||
<section>
|
||||
<button eicbutton rounded basic primary small icon="icon-cog" title="Manage settings"></button>
|
||||
</section>-->
|
||||
</div>
|
||||
<article eiccard class="tokens content">
|
||||
<section>
|
||||
<div class="cols-2 right middle">
|
||||
<div class="metrics">
|
||||
<div class="assigned-past"><span>0</span><label>assigned <span class="year"></span></label></div>
|
||||
<div class="assigned-current"><span>0</span><label>assigned <span class="year"></span></label></div>
|
||||
<div class="consumed-past"><span success>0</span><label success>consumed <span class="year"></span></label></div>
|
||||
<div class="consumed-current"><span success>0</span><label success>consumed <span class="year"></span></label></div>
|
||||
</div>
|
||||
<button eicbutton primary class="grant-token">Grant a token</button>
|
||||
</div>
|
||||
<div eicdatagrid class="global-granted-tokens" footer="hidden"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="history content">
|
||||
<section>
|
||||
<div eicdatagrid class="history-tokens"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="config content"></article>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
class BypassAdminDashboard extends EICDomContent {
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
ui.eicfy(this.el);
|
||||
|
||||
this.profile = options.profile;
|
||||
for(let model in options.models) this[model] = options.models[model];
|
||||
|
||||
this.tabs = new Tab();
|
||||
this.tabs.addTabs(this.findAll('.tabs-extended menu li'), this.findAll('.content'))
|
||||
|
||||
this.gridTokens = new DataGrid(this.find('.global-granted-tokens'), {
|
||||
headers: [
|
||||
{ label: 'PIC', type: 'markup', filter: 'text', sortable: true},
|
||||
{ label: 'Company name', filter: 'text', sortable: true},
|
||||
{ label: 'Token granted',type: 'date', filter: 'text', sortable: true},
|
||||
{ label: 'Scheme', filter: 'list', sortable: true},
|
||||
{ label: 'Entity', filter: 'text', sortable: true},
|
||||
{ label: 'Status', filter: 'list', sortable: true},
|
||||
{ label: 'Project', filter: 'text', sortable: true},
|
||||
{ label: 'Short proposal submission', type: 'date', filter: 'text', sortable: true}
|
||||
],
|
||||
height: 'calc(100vh - 472px)',
|
||||
actions: []
|
||||
});
|
||||
|
||||
this.btTokenDl = new Button(null, {icon: 'icon-download', severity: 'secondary', rounded: true, size: 'small', hint: 'Download complete list as CSV'})
|
||||
this.btTokenDl.addEventListener('click', this.downloadTokensList.bind(this));
|
||||
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenDl.el);
|
||||
|
||||
this.btTokenRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
|
||||
this.btTokenRefresh.addEventListener('click', this.refreshTokensList.bind(this));
|
||||
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenRefresh.el);
|
||||
|
||||
this.gridTokens.onRowFiltered = this.refreshMetrics.bind(this);
|
||||
|
||||
this.gridHistory = new DataGrid(this.find('.history-tokens'), {
|
||||
headers: [
|
||||
{ label: 'Date', type: 'dateTime', filter: 'text', sortable: true},
|
||||
{ label: 'PIC', filter: 'text', sortable: true},
|
||||
{ label: 'Company name', filter: 'text', sortable: true},
|
||||
{ label: 'Action', filter: 'list', sortable: true},
|
||||
{ label: 'Managed by', filter: 'text', sortable: true},
|
||||
],
|
||||
height: 'calc(100vh - 420px)',
|
||||
actions: []
|
||||
});
|
||||
|
||||
let btGrant = this.find('.grant-token');
|
||||
if(this.company.hasPrivilege('grant')) {
|
||||
btGrant.addEventListener('click', this.onTokenGrant.bind(this));
|
||||
} else {
|
||||
ui.hide(btGrant);
|
||||
}
|
||||
|
||||
if(this.tokens.hasPrivilege('list')) { this.refreshTokensList(); }
|
||||
|
||||
if(this.users) {
|
||||
this.loadContent(
|
||||
'projects/bypass/BypassAdminManagementContent',
|
||||
{ title: 'Accelerator configuration', subtitle: 'Users access & token management' },
|
||||
{ profile: this.profile, models: { "users": this.users, "tokens": this.tokens } }
|
||||
).then(view => this.setupUserManagement(view));
|
||||
} else {
|
||||
ui.hide(this.find('li.settings'));
|
||||
}
|
||||
}
|
||||
|
||||
setupUserManagement(view) {
|
||||
this.userManagement = view;
|
||||
this.find('.content.config').append(view.el);
|
||||
}
|
||||
|
||||
fill() {
|
||||
let year = new Date().getFullYear();
|
||||
let awardedPast = this.tokens.awardedTokens(year - 1);
|
||||
awardedPast.forEach(item => item.ref = 'assigned-past');
|
||||
let awardedCurrent = this.tokens.awardedTokens(year);
|
||||
awardedCurrent.forEach(item => item.ref = 'assigned-current');
|
||||
let consumedPast = this.tokens.consumedTokens(year - 1);
|
||||
consumedPast.forEach(item => item.ref = 'consumed-past');
|
||||
let consumedCurrent = this.tokens.consumedTokens(year);
|
||||
consumedCurrent.forEach(item => item.ref = 'consumed-current');
|
||||
|
||||
let history = this.tokens.history();
|
||||
|
||||
this.find('.metrics .assigned-current .year').innerText = year;
|
||||
this.find('.metrics .consumed-current .year').innerText = year;
|
||||
this.find('.metrics .assigned-past .year').innerText = year - 1;
|
||||
this.find('.metrics .consumed-past .year').innerText = year - 1;
|
||||
|
||||
this.gridHistory.loading = false;
|
||||
this.gridTokens.loading = false;
|
||||
this.btTokenRefresh.loading = false;
|
||||
|
||||
this.gridTokens.clear();
|
||||
|
||||
let awarded = awardedCurrent.concat(awardedPast);
|
||||
awarded.sort((a,b) => a.itemData.granted < b.itemData.granted ? 1: -1);
|
||||
|
||||
for (const item of awarded) {
|
||||
let priority = '';
|
||||
switch(item.itemData.status) {
|
||||
case 'consumed':
|
||||
priority = 'success';
|
||||
break;
|
||||
}
|
||||
|
||||
let row = this.gridTokens.addRow(item.itemData.id, [
|
||||
`<a href="#" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" title="View token details">${item.itemData.enterprise.pic}</a>`,
|
||||
item.itemData.enterprise.legalname,
|
||||
item.itemData.granted,
|
||||
app.meta.toString('accelerator-tracks', item.itemData.track),
|
||||
app.meta.toString('accelerator-tracks', item.itemData.domain),
|
||||
`<span ${priority}>${item.itemData.status}</span>`,
|
||||
item.itemData.project ? item.itemData.project.acronym: '',
|
||||
item.itemData.project ? item.itemData.project.submissionDate: ''
|
||||
], true);
|
||||
|
||||
let tokenLink = row.querySelector('.cell:nth-child(2) a')
|
||||
tokenLink.addEventListener('click', this.onTokenView.bind(this));
|
||||
|
||||
row.classList.add(item.ref);
|
||||
|
||||
// checking if token can be revoked (meaning status is allocated and user has privilege for that action)
|
||||
if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (!item.itemData.isAssignedToShortProposal)) {
|
||||
let buttonBar = ui.create(`<div class="cols-2"></div>`)
|
||||
let revokeButton = ui.create(`<button eicbutton xsmall danger title="revoke this token" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Revoke</button>`);
|
||||
revokeButton.addEventListener('click', this.onTokenRevoke.bind(this));
|
||||
buttonBar.appendChild(revokeButton);
|
||||
|
||||
let useButton = ui.create(`<button eicbutton xsmall info title="Activate the coaching" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}">Consume</button>`);
|
||||
useButton.addEventListener('click', this.onTokenConsume.bind(this));
|
||||
buttonBar.appendChild(useButton);
|
||||
|
||||
row.querySelector('.cell.actions').appendChild(buttonBar);
|
||||
} else if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (item.itemData.isAssignedToShortProposal)){
|
||||
row.querySelector('.cell.actions').appendChild(ui.create(`<span>Consuming...</span>`));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
this.gridTokens.updateFilters();
|
||||
|
||||
// filling the history list
|
||||
this.gridHistory.clear();
|
||||
for (const item of history) {
|
||||
let priority = ''
|
||||
switch(item.action) {
|
||||
case 'revoked': priority = 'danger'; break;
|
||||
case 'consumed': priority = 'success'; break;
|
||||
}
|
||||
let row = this.gridHistory.addRow(item.date, [
|
||||
item.date,
|
||||
item.pic,
|
||||
item.legalname,
|
||||
`<span ${priority}>${item.action}</span>`,
|
||||
`${item.actor.firstname ? item.actor.firstname[0] + '. ': ''}${item.actor.lastname}`
|
||||
], true);
|
||||
}
|
||||
this.gridHistory.updateFilters();
|
||||
|
||||
this.refreshMetrics();
|
||||
}
|
||||
|
||||
refreshMetrics() {
|
||||
let rows = Array.from(this.gridTokens.filteredRows);
|
||||
|
||||
let assignedPast = rows.filter(item => item.classList.contains('assigned-past'));
|
||||
let assignedCurrent = rows.filter(item => item.classList.contains('assigned-current'));
|
||||
let consumedPast = rows.filter(item => item.classList.contains('consumed-past'));
|
||||
let consumedCurrent = rows.filter(item => item.classList.contains('consumed-current'));
|
||||
|
||||
this.find('.metrics .assigned-past span').innerText = assignedPast.length;
|
||||
this.find('.metrics .assigned-current span').innerText = assignedCurrent.length;
|
||||
this.find('.metrics .consumed-past span').innerText = consumedPast.length;
|
||||
this.find('.metrics .consumed-current span').innerText = consumedCurrent.length;
|
||||
}
|
||||
|
||||
refreshTokensList(event) {
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this.btTokenRefresh.loading = true;
|
||||
this.gridTokens.loading = true;
|
||||
this.gridHistory.loading = true;
|
||||
this.tokens.list().then(this.fill.bind(this));
|
||||
}
|
||||
|
||||
downloadTokensList(event) {
|
||||
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.btTokenDl.loading = true;
|
||||
this.tokens.export({filter: { includePersons: true }}).then(this.buildCSV.bind(this));
|
||||
}
|
||||
|
||||
buildCSV(payload) {
|
||||
|
||||
let options = {
|
||||
headers: [
|
||||
'PIC',
|
||||
'Organisation Name',
|
||||
'Granted on',
|
||||
'Scheme',
|
||||
'Entity',
|
||||
'Program',
|
||||
'Status',
|
||||
'Project',
|
||||
'Submission date',
|
||||
'Contact name',
|
||||
'Contact gender',
|
||||
'Contact position',
|
||||
'Contact email',
|
||||
'Contact phone',
|
||||
'SPOC name',
|
||||
'SPOC email',
|
||||
],
|
||||
filename: 'EIC-Bypass',
|
||||
delimiter: ';'
|
||||
}
|
||||
|
||||
let data = [];
|
||||
|
||||
for(let token of payload.tokens) {
|
||||
let row = [
|
||||
token.enterprise.pic,
|
||||
token.enterprise.legalname,
|
||||
token.granted,
|
||||
app.meta.toString('accelerator-tracks', token.track),
|
||||
app.meta.toString('accelerator-tracks', token.domain),
|
||||
token.program || "",
|
||||
token.history[0].status,
|
||||
token.project ? token.project.acronym: '',
|
||||
token.project ? token.project.submissiondate: '',
|
||||
token.shortProposal ? token.shortProposal.contact.lastname + ' ' + token.shortProposal.contact.firstname: '',
|
||||
token.shortProposal ? token.shortProposal.contact.gender: '',
|
||||
token.shortProposal ? token.shortProposal.contact.position: '',
|
||||
token.shortProposal ? token.shortProposal.contact.email: '',
|
||||
token.shortProposal ? token.shortProposal.contact.phone: '',
|
||||
token.spoc ? token.spoc.lastname + ' ' + token.spoc.firstname: '',
|
||||
token.spoc ? token.spoc.email: '',
|
||||
]
|
||||
|
||||
data.push(row);
|
||||
}
|
||||
|
||||
app.helpers.translator.toCSV( data, options );
|
||||
this.btTokenDl.loading = false;
|
||||
}
|
||||
|
||||
async onTokenGrant(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let grantResult = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantDialog',
|
||||
{ title: 'Grant a token' },
|
||||
{ models: { 'company': this.company, 'tokens': this.tokens }, profile: this.profile }
|
||||
)
|
||||
);
|
||||
|
||||
if(grantResult) {
|
||||
ui.growl.append('Token granted', 'success');
|
||||
let ConsumeResult = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
|
||||
{ title: 'Activate the coaching for...', subtitle: `${this.company.itemData.legalname} (${this.company.itemData.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
profile: this.profile,
|
||||
tokenId: this.company.itemData.token.id,
|
||||
pic: this.company.itemData.pic,
|
||||
legalname: this.company.itemData.legalname,
|
||||
wizard: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
this.refreshTokensList();
|
||||
}
|
||||
|
||||
async onTokenRevoke(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenRevokeDialog',
|
||||
{ title: 'Revoke a token for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
model: this.company,
|
||||
profile: this.profile,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
track: event.currentTarget.dataset.track,
|
||||
domain: event.currentTarget.dataset.domain,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token revoked', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenView(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
|
||||
{ title: 'Token granted to ', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
mode: 'read',
|
||||
profile: this.profile,
|
||||
tokenId: event.currentTarget.dataset.id,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
legalname: event.currentTarget.dataset.legalname,
|
||||
wizard: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token consumed', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenConsume(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
|
||||
{ title: 'Activate the coaching for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
profile: this.profile,
|
||||
tokenId: event.currentTarget.dataset.id,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
legalname: event.currentTarget.dataset.legalname,
|
||||
wizard: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token consumed', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
app.registerClass('BypassAdminDashboard', BypassAdminDashboard);
|
||||
@@ -0,0 +1,42 @@
|
||||
<div>
|
||||
<section>
|
||||
<article eiccard collapsable>
|
||||
<header>
|
||||
<h1>Users Access</h1>
|
||||
<h2></h2>
|
||||
</header>
|
||||
<section>
|
||||
<div eicdatagrid class="users-list"></div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-1 right"><button eicbutton primary small class="user-add">add user...</button></div>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
<section class="cols-2">
|
||||
<article eiccard collapsable>
|
||||
<header>
|
||||
<h1>EIC Fast Track Settings</h1>
|
||||
<h2>Yearly token assignment per domain</h2>
|
||||
</header>
|
||||
<section>
|
||||
<div eicdatagrid class="kic-domains-list" footer="hidden"></div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-1 right"><button class="kic-edit" eicbutton primary small>update...</button></div>
|
||||
</footer>
|
||||
</article>
|
||||
<article eiccard collapsable>
|
||||
<header>
|
||||
<h1>EIC Plug In Settings</h1>
|
||||
<h2>Yearly token assignment per domain</h2>
|
||||
</header>
|
||||
<section>
|
||||
<div eicdatagrid class="plugin-domains-list" footer="hidden"></div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-1 right"><button class="plugin-edit" eicbutton primary small>update...</button></div>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,104 @@
|
||||
class BypassAdminManagementContent extends BypassBaseManagementContent {
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
|
||||
super.DOMContentLoaded(options);
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.gridKics = new DataGrid(this.find('.kic-domains-list'), {
|
||||
headers: [ { label: 'Thematic'}, { label: 'Tokens' }, { label: 'Remaining' } ],
|
||||
height: '320px'
|
||||
});
|
||||
|
||||
this.gridPlugins = new DataGrid(this.find('.plugin-domains-list'), {
|
||||
headers: [ { label: 'Country'}, { label: 'Tokens'}, { label: 'Remaining' } ],
|
||||
height: '320px'
|
||||
});
|
||||
|
||||
this.pluginEditButton = components.find(component => component.el.classList.contains('plugin-edit'));
|
||||
if(this.tokens.hasPrivilege('setSettings')) {
|
||||
this.pluginEditButton.addEventListener('click', this.onPluginEdit.bind(this));
|
||||
} else {
|
||||
ui.hide(this.pluginEditButton);
|
||||
}
|
||||
|
||||
this.kicEditButton = components.find(component => component.el.classList.contains('kic-edit'));
|
||||
if(this.tokens.hasPrivilege('setSettings')) {
|
||||
this.kicEditButton.addEventListener('click', this.onKicEdit.bind(this));
|
||||
} else {
|
||||
ui.hide(this.kicEditButton);
|
||||
}
|
||||
|
||||
this.gridKics.loading = true;
|
||||
this.tokens.getSettings('d7xAg5kIhQYeDMB1H6eXnBg')
|
||||
.then(this.fillKics.bind(this));
|
||||
|
||||
this.gridPlugins.loading = true;
|
||||
this.tokens.getSettings('daTai94ymStyRbQWybH3eDw')
|
||||
.then(this.fillPlugins.bind(this));
|
||||
}
|
||||
|
||||
fillKics(payload) {
|
||||
this.gridKics.clear();
|
||||
this.gridKics.loading = false;
|
||||
|
||||
for(let item of payload.domains) {
|
||||
this.gridKics.addRow(item.domain, [app.meta.toString('accelerator-tracks', item.domain), item.tokens, item.remaining])
|
||||
}
|
||||
this.gridKics.sort(1, 'asc');
|
||||
}
|
||||
|
||||
fillPlugins(payload) {
|
||||
this.gridPlugins.clear();
|
||||
this.gridPlugins.loading = false;
|
||||
for(let item of payload.domains) {
|
||||
this.gridPlugins.addRow(item.domain, [app.meta.toString('accelerator-tracks', item.domain), item.tokens, item.remaining])
|
||||
}
|
||||
this.gridPlugins.sort(1, 'asc');
|
||||
}
|
||||
|
||||
async onKicEdit(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTrackSetupDialog',
|
||||
{ title: 'Fast-Track Yearly token attribution', subtitle: 'Set up the yearly token attribution per domain' },
|
||||
{ model: this.tokens, track: 'd7xAg5kIhQYeDMB1H6eXnBg' }
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Settings updated', 'success');
|
||||
|
||||
this.gridKics.loading = true;
|
||||
this.tokens.getSettings('d7xAg5kIhQYeDMB1H6eXnBg')
|
||||
.then(this.fillKics.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
async onPluginEdit(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTrackSetupDialog',
|
||||
{ title: 'Plugins Yearly token attribution', subtitle: 'Set up the yearly token attribution per country' },
|
||||
{ model: this.tokens, track: 'daTai94ymStyRbQWybH3eDw' }
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Settings updated', 'success');
|
||||
|
||||
this.gridPlugins.loading = true;
|
||||
this.tokens.getSettings('daTai94ymStyRbQWybH3eDw')
|
||||
.then(this.fillPlugins.bind(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassAdminManagementContent', BypassAdminManagementContent);
|
||||
@@ -0,0 +1,117 @@
|
||||
class BypassBaseManagementContent extends EICDomContent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
Object.assign(this, app.helpers.basicDialogs)
|
||||
}
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
|
||||
this.profile = options.profile;
|
||||
for(let model in options.models) this[model] = options.models[model];
|
||||
|
||||
this.users.getRoles(); // We'll need them later, but quite slow
|
||||
|
||||
this.gridUsers = new DataGrid(this.find('.users-list'), {
|
||||
headers: [
|
||||
{ label: 'Login', filter: 'text', sortable: true},
|
||||
{ label: 'Email', filter: 'text', sortable: true},
|
||||
{ label: 'Fullname', filter: 'text', sortable: true},
|
||||
{ label: 'Role', filter: 'list', sortable: true},
|
||||
{ label: 'Scheme', filter: 'list', sortable: true},
|
||||
{ label: 'Domain', filter: 'list', sortable: true},
|
||||
],
|
||||
height: 'calc(100vh - 620px)',
|
||||
actions: []
|
||||
});
|
||||
this.btUsersRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
|
||||
this.btUsersRefresh.addEventListener('click', this.refreshUsersList.bind(this));
|
||||
this.gridUsers.el.querySelector('header .cell.actions').append(this.btUsersRefresh.el);
|
||||
|
||||
this.userAddButton = this.find('.user-add');
|
||||
if(this.users.hasPrivilege('create')) {
|
||||
this.userAddButton.addEventListener('click', this.onUserCreate.bind(this));
|
||||
} else {
|
||||
ui.hide(this.userAddButton);
|
||||
}
|
||||
|
||||
this.gridUsers.loading = true;
|
||||
this.users.list().then(this.fillUsers.bind(this));
|
||||
}
|
||||
|
||||
fillUsers() {
|
||||
this.gridUsers.clear();
|
||||
this.gridUsers.loading = false;
|
||||
this.btUsersRefresh.loading = false;
|
||||
|
||||
for (const item of this.users.collection) {
|
||||
let row = this.gridUsers.addRow(item.uid, [
|
||||
item.uid,
|
||||
item.email,
|
||||
`${item.firstname} ${item.lastname}`,
|
||||
item.BProle,
|
||||
item.track.label,
|
||||
item.domain.label,
|
||||
], true);
|
||||
|
||||
if(this.users.hasPrivilege('revoke') && (item.uid != app.User.identity.uuid )) {
|
||||
let button = ui.create(`<button eicbutton xsmall danger title="revoke this user" data-uid="${item.uid}">revoke</button>`);
|
||||
button.addEventListener('click', this.onUserRevoke.bind(this));
|
||||
row.querySelector('.cell.actions').appendChild(button);
|
||||
}
|
||||
}
|
||||
this.gridUsers.updateFilters();
|
||||
}
|
||||
|
||||
refreshUsersList(event) {
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this.btUsersRefresh.loading = true;
|
||||
this.gridUsers.loading = true;
|
||||
this.users.list().then(this.fillUsers.bind(this));
|
||||
}
|
||||
|
||||
async onUserCreate(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(await this.loadContent('projects/bypass/dialogs/BypassUserProfileDialog',{
|
||||
title: 'Add a user',
|
||||
subtitle: 'Please first enter the user UUID then define role and scopes of activity'
|
||||
}, {
|
||||
model: this.users,
|
||||
mode: 'create',
|
||||
profile: this.profile
|
||||
}));
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('User added', 'success');
|
||||
this.refreshUsersList();
|
||||
}
|
||||
}
|
||||
|
||||
async onUserRevoke(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let uid = event.currentTarget.dataset.uid;
|
||||
let user = this.users.getUid(uid);
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Revoke a user',
|
||||
message: `Please be aware that you are about to revoke the user "<strong>${user.firstname} ${user.lastname}</strong>"<br>
|
||||
This is permanent !<br>
|
||||
Are you positive ?`,
|
||||
okLabel: 'Revoke user',
|
||||
okPromise:() => { return(this.users.revoke(uid)) },
|
||||
})
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('User revoked', 'success');
|
||||
this.refreshUsersList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassBaseManagementContent', BypassBaseManagementContent);
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<article eiccard class="fasttracks dashboard">
|
||||
<header>
|
||||
<h1>EIC Fast Track</h1>
|
||||
<h2>Monitoring for entity/programme <b info>${app.meta.toString('accelerator-tracks', profile.domain)}</b></h2>
|
||||
</header>
|
||||
<section>
|
||||
<div class="tabs-extended">
|
||||
<section>
|
||||
<menu eictab>
|
||||
<li>Tokens</li>
|
||||
<li>History</li>
|
||||
<li class="settings">Settings</li>
|
||||
</menu>
|
||||
</section>
|
||||
</div>
|
||||
<article eiccard class="tokens content">
|
||||
<section>
|
||||
<div class="cols-2 right middle">
|
||||
<div class="metrics">
|
||||
<div class="available"><span>0</span><label>available</label></div>
|
||||
<div class="assigned-past"><span>0</span><label>assigned <span class="year"></span></label></div>
|
||||
<div class="assigned-current"><span>0</span><label>assigned <span class="year"></span></label></div>
|
||||
<div class="consumed-past"><span success>0</span><label success>consumed <span class="year"></span></label></div>
|
||||
<div class="consumed-current"><span success>0</span><label success>consumed <span class="year"></span></label></div>
|
||||
</div>
|
||||
<button eicbutton primary class="grant-token">Grant a token</button>
|
||||
</div>
|
||||
<div eicdatagrid class="granted-tokens" footer="hidden"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="history content">
|
||||
<section>
|
||||
<div eicdatagrid class="history-tokens"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="config content">
|
||||
<section>
|
||||
<div eicdatagrid class="users" footer="hidden"></div>
|
||||
</section>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
class BypassKICDashboard extends EICDomContent {
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
ui.eicfy(this.el);
|
||||
|
||||
this.profile = options.profile;
|
||||
for(let model in options.models) this[model] = options.models[model];
|
||||
|
||||
this.tabs = new Tab();
|
||||
this.tabs.addTabs(this.findAll('menu li'), this.findAll('.content'))
|
||||
|
||||
this.gridTokens = new DataGrid(this.find('.granted-tokens'), {
|
||||
headers: [
|
||||
{ label: 'PIC', type:'markup', filter: 'text', sortable: true},
|
||||
{ label: 'Company name', filter: 'text', sortable: true},
|
||||
{ label: 'Entity/programme', filter: 'list', sortable: true},
|
||||
{ label: 'Token granted', type: 'date', filter: 'text', sortable: true},
|
||||
{ label: 'Status', filter: 'list', sortable: true},
|
||||
{ label: 'Project', filter: 'text', sortable: true},
|
||||
{ label: 'Short proposal submission', type: 'date', filter: 'text', sortable: true}
|
||||
],
|
||||
height: 'calc(100vh - 472px)',
|
||||
actions: []
|
||||
});
|
||||
|
||||
this.btTokenDl = new Button(null, {icon: 'icon-download', severity: 'secondary', rounded: true, size: 'small', hint: 'Download complete list as CSV'})
|
||||
this.btTokenDl.addEventListener('click', this.downloadTokensList.bind(this));
|
||||
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenDl.el);
|
||||
|
||||
this.btTokenRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
|
||||
this.btTokenRefresh.addEventListener('click', this.refreshTokensList.bind(this));
|
||||
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenRefresh.el);
|
||||
|
||||
this.gridTokens.onRowFiltered = this.refreshMetrics.bind(this);
|
||||
|
||||
this.gridHistory = new DataGrid(this.find('.history-tokens'), {
|
||||
headers: [
|
||||
{ label: 'Date', type: 'dateTime', filter: 'text', sortable: true},
|
||||
{ label: 'PIC', filter: 'text', sortable: true},
|
||||
{ label: 'Company name', filter: 'text', sortable: true},
|
||||
{ label: 'Action', filter: 'list', sortable: true},
|
||||
{ label: 'Managed by', filter: 'text', sortable: true},
|
||||
],
|
||||
height: 'calc(100vh - 420px)',
|
||||
actions: []
|
||||
});
|
||||
|
||||
let btGrant = this.find('.grant-token');
|
||||
if(this.company.hasPrivilege('grant')) {
|
||||
btGrant.addEventListener('click', this.onTokenGrant.bind(this));
|
||||
} else {
|
||||
ui.hide(btGrant);
|
||||
}
|
||||
|
||||
if(this.tokens.hasPrivilege('list')) { this.refreshTokensList(); }
|
||||
|
||||
if(this.users) {
|
||||
this.loadContent(
|
||||
'projects/bypass/BypassKICManagementContent',
|
||||
{ title: 'Accelerator configuration', subtitle: 'Users access & token management' },
|
||||
{ profile: this.profile, models: { "users": this.users } }
|
||||
).then(view => this.setupUserManagement(view))
|
||||
} else {
|
||||
ui.hide(this.find('li.settings'));
|
||||
}
|
||||
}
|
||||
|
||||
setupUserManagement(view) {
|
||||
this.userManagement = view;
|
||||
this.find('.content.config').append(view.el);
|
||||
}
|
||||
|
||||
fill() {
|
||||
let year = new Date().getFullYear();
|
||||
let awardedPast = this.tokens.awardedTokens(year - 1);
|
||||
awardedPast.forEach(item => item.ref = 'assigned-past');
|
||||
let awardedCurrent = this.tokens.awardedTokens(year);
|
||||
awardedCurrent.forEach(item => item.ref = 'assigned-current');
|
||||
let consumedPast = this.tokens.consumedTokens(year - 1);
|
||||
consumedPast.forEach(item => item.ref = 'consumed-past');
|
||||
let consumedCurrent = this.tokens.consumedTokens(year);
|
||||
consumedCurrent.forEach(item => item.ref = 'consumed-current');
|
||||
|
||||
let history = this.tokens.history();
|
||||
|
||||
this.find('.metrics .available span').innerText = this.tokens.freeTokens - awardedCurrent.length;
|
||||
this.find('.metrics .assigned-current .year').innerText = year;
|
||||
this.find('.metrics .consumed-current .year').innerText = year;
|
||||
this.find('.metrics .assigned-past .year').innerText = year - 1;
|
||||
this.find('.metrics .consumed-past .year').innerText = year - 1;
|
||||
|
||||
this.gridHistory.loading = false;
|
||||
this.gridTokens.loading = false;
|
||||
this.btTokenRefresh.loading = false;
|
||||
|
||||
this.gridTokens.clear();
|
||||
|
||||
let awarded = awardedCurrent.concat(awardedPast);
|
||||
awarded.sort((a,b) => a.itemData.granted < b.itemData.granted ? 1: -1);
|
||||
|
||||
for (const item of awarded) {
|
||||
let priority = ''
|
||||
switch(item.itemData.status) {
|
||||
case 'consumed':
|
||||
priority = 'success';
|
||||
break;
|
||||
}
|
||||
|
||||
let row = this.gridTokens.addRow(item.itemData.id, [
|
||||
`<a href="#" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" title="View token details">${item.itemData.enterprise.pic}</a>`,
|
||||
item.itemData.enterprise.legalname,
|
||||
app.meta.toString('accelerator-tracks', item.itemData.domain),
|
||||
item.itemData.granted,
|
||||
`<span ${priority}>${item.itemData.status}</span>`,
|
||||
item.itemData.project ? item.itemData.project.acronym: '',
|
||||
item.itemData.project ? item.itemData.project.submissionDate: ''
|
||||
], true);
|
||||
|
||||
let tokenLink = row.querySelector('.cell:nth-child(2) a')
|
||||
tokenLink.addEventListener('click', this.onTokenView.bind(this));
|
||||
|
||||
row.classList.add(item.ref);
|
||||
|
||||
// checking if token can be revoked (meaning status is allocated and user has privilege for that action)
|
||||
if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (!item.itemData.isAssignedToShortProposal)) {
|
||||
let buttonBar = ui.create(`<div class="cols-2"></div>`)
|
||||
let revokeButton = ui.create(`<button eicbutton xsmall danger title="revoke this token" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Revoke</button>`);
|
||||
revokeButton.addEventListener('click', this.onTokenRevoke.bind(this));
|
||||
buttonBar.appendChild(revokeButton);
|
||||
|
||||
let useButton = ui.create(`<button eicbutton xsmall info title="Activate the coaching" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Consume</button>`);
|
||||
useButton.addEventListener('click', this.onTokenConsume.bind(this));
|
||||
buttonBar.appendChild(useButton);
|
||||
|
||||
row.querySelector('.cell.actions').appendChild(buttonBar);
|
||||
} else if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (item.itemData.isAssignedToShortProposal)){
|
||||
row.querySelector('.cell.actions').appendChild(ui.create(`<span>Consuming...</span>`));
|
||||
}
|
||||
}
|
||||
this.gridTokens.updateFilters();
|
||||
|
||||
// filling the history list
|
||||
this.gridHistory.clear();
|
||||
for (const item of history) {
|
||||
let priority = ''
|
||||
switch(item.action) {
|
||||
case 'revoked': priority = 'danger'; break;
|
||||
case 'consumed': priority = 'success'; break;
|
||||
}
|
||||
let row = this.gridHistory.addRow(item.date, [
|
||||
item.date,
|
||||
item.pic,
|
||||
item.legalname,
|
||||
`<span ${priority}>${item.action}</span>`,
|
||||
`${item.actor.firstname ? item.actor.firstname[0] + '. ': ''}${item.actor.lastname}`
|
||||
], true);
|
||||
}
|
||||
this.gridHistory.updateFilters();
|
||||
|
||||
this.refreshMetrics();
|
||||
}
|
||||
|
||||
refreshMetrics() {
|
||||
let rows = Array.from(this.gridTokens.filteredRows);
|
||||
|
||||
let assignedPast = rows.filter(item => item.classList.contains('assigned-past'));
|
||||
let assignedCurrent = rows.filter(item => item.classList.contains('assigned-current'));
|
||||
let consumedPast = rows.filter(item => item.classList.contains('consumed-past'));
|
||||
let consumedCurrent = rows.filter(item => item.classList.contains('consumed-current'));
|
||||
|
||||
this.find('.metrics .assigned-past span').innerText = assignedPast.length;
|
||||
this.find('.metrics .assigned-current span').innerText = assignedCurrent.length;
|
||||
this.find('.metrics .consumed-past span').innerText = consumedPast.length;
|
||||
this.find('.metrics .consumed-current span').innerText = consumedCurrent.length;
|
||||
}
|
||||
|
||||
refreshTokensList(event) {
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this.btTokenRefresh.loading = true;
|
||||
this.gridTokens.loading = true;
|
||||
this.gridHistory.loading = true;
|
||||
this.tokens.list().then(this.fill.bind(this));
|
||||
}
|
||||
|
||||
downloadTokensList(event) {
|
||||
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.btTokenDl.loading = true;
|
||||
this.tokens.export({filter: { includePersons: true }}).then(this.buildCSV.bind(this));
|
||||
}
|
||||
|
||||
buildCSV(payload) {
|
||||
|
||||
let options = {
|
||||
headers: [
|
||||
'PIC',
|
||||
'Organisation Name',
|
||||
'Granted on',
|
||||
'Scheme',
|
||||
'Entity',
|
||||
'Status',
|
||||
'Project',
|
||||
'Submission date',
|
||||
'Contact name',
|
||||
'Contact gender',
|
||||
'Contact position',
|
||||
'Contact email',
|
||||
'Contact phone',
|
||||
'SPOC name',
|
||||
'SPOC email',
|
||||
],
|
||||
filename: 'EIC-Bypass',
|
||||
delimiter: ';'
|
||||
}
|
||||
|
||||
let data = [];
|
||||
|
||||
for(let token of payload.tokens) {
|
||||
let row = [
|
||||
token.enterprise.pic,
|
||||
token.enterprise.legalname,
|
||||
token.granted,
|
||||
app.meta.toString('accelerator-tracks', token.track),
|
||||
app.meta.toString('accelerator-tracks', token.domain),
|
||||
token.history[0].status,
|
||||
token.project ? token.project.acronym: '',
|
||||
token.project ? token.project.submissiondate: '',
|
||||
token.shortProposal ? token.shortProposal.contact.lastname + ' ' + token.shortProposal.contact.firstname: '',
|
||||
token.shortProposal ? token.shortProposal.contact.gender: '',
|
||||
token.shortProposal ? token.shortProposal.contact.position: '',
|
||||
token.shortProposal ? token.shortProposal.contact.email: '',
|
||||
token.shortProposal ? token.shortProposal.contact.phone: '',
|
||||
token.spoc ? token.spoc.lastname + ' ' + token.spoc.firstname: '',
|
||||
token.spoc ? token.spoc.email: '',
|
||||
]
|
||||
|
||||
data.push(row);
|
||||
}
|
||||
|
||||
app.helpers.translator.toCSV( data, options );
|
||||
this.btTokenDl.loading = false;
|
||||
}
|
||||
async onTokenGrant(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let grantResult = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantDialog',
|
||||
{ title: 'Grant a token' },
|
||||
{ models: { 'company': this.company, 'tokens': this.tokens }, profile: this.profile }
|
||||
)
|
||||
);
|
||||
|
||||
if(grantResult) {
|
||||
ui.growl.append('Token granted', 'success');
|
||||
let ConsumeResult = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantPropaDialog',
|
||||
{ title: 'Activate the coaching for...', subtitle: `${this.company.itemData.legalname} (${this.company.itemData.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
profile: this.profile,
|
||||
tokenId: this.company.itemData.token.id,
|
||||
pic: this.company.pic,
|
||||
legalname: this.company.itemData.legalname,
|
||||
wizard: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
this.refreshTokensList();
|
||||
}
|
||||
|
||||
async onTokenRevoke(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenRevokeDialog',
|
||||
{ title: 'Revoke a token for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
model: this.company,
|
||||
profile: this.profile,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
track: event.currentTarget.dataset.track,
|
||||
domain: event.currentTarget.dataset.domain,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token revoked', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenView(event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
|
||||
{ title: 'Token granted to ', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
mode: 'read',
|
||||
profile: this.profile,
|
||||
tokenId: event.currentTarget.dataset.id,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
legalname: event.currentTarget.dataset.legalname,
|
||||
wizard: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token consumed', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenConsume(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
|
||||
{ title: 'Activate the coaching for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
profile: this.profile,
|
||||
tokenId: event.currentTarget.dataset.id,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
legalname: event.currentTarget.dataset.legalname,
|
||||
wizard: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token consumed', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassKICDashboard', BypassKICDashboard);
|
||||
@@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<section>
|
||||
<article eiccard collapsable>
|
||||
<header>
|
||||
<h1>Users Access</h1>
|
||||
<h2></h2>
|
||||
</header>
|
||||
<section>
|
||||
<div eicdatagrid class="users-list"></div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-1 right"><button eicbutton primary small class="user-add">add user...</button></div>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
class BypassKICManagementContent extends BypassBaseManagementContent {
|
||||
|
||||
DOMContentLoaded(options) { super.DOMContentLoaded(options); }
|
||||
|
||||
}
|
||||
|
||||
app.registerClass('BypassKICManagementContent', BypassKICManagementContent);
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<article eiccard class="fasttracks dashboard">
|
||||
<header>
|
||||
<h1>EIC Plug In</h1>
|
||||
<h2>Monitoring for country <b info>${app.meta.toString('accelerator-tracks', profile.domain)}</b></h2>
|
||||
</header>
|
||||
<section>
|
||||
<div class="tabs-extended">
|
||||
<section>
|
||||
<menu eictab>
|
||||
<li>Tokens</li>
|
||||
<li>History</li>
|
||||
<li class="settings">Settings</li>
|
||||
</menu>
|
||||
</section>
|
||||
</div>
|
||||
<article eiccard class="tokens content">
|
||||
<section>
|
||||
<div class="cols-2 right middle">
|
||||
<div class="metrics">
|
||||
<div class="available"><span>0</span><label>available</label></div>
|
||||
<div class="assigned-past"><span>0</span><label>assigned <span class="year"></span></label></div>
|
||||
<div class="assigned-current"><span>0</span><label>assigned <span class="year"></span></label></div>
|
||||
<div class="consumed-past"><span success>0</span><label success>consumed <span class="year"></span></label></div>
|
||||
<div class="consumed-current"><span success>0</span><label success>consumed <span class="year"></span></label></div>
|
||||
</div>
|
||||
<button eicbutton primary class="grant-token">Grant a token</button>
|
||||
</div>
|
||||
<div eicdatagrid class="granted-tokens" footer="hidden"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="history content">
|
||||
<section>
|
||||
<div eicdatagrid class="history-tokens"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="config content">
|
||||
<section>
|
||||
<div eicdatagrid class="users" footer="hidden"></div>
|
||||
</section>
|
||||
</article>
|
||||
</section>
|
||||
</article>
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
class BypassPluginDashboard extends EICDomContent {
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
ui.eicfy(this.el);
|
||||
|
||||
this.profile = options.profile;
|
||||
for(let model in options.models) this[model] = options.models[model];
|
||||
|
||||
this.tabs = new Tab();
|
||||
this.tabs.addTabs(this.findAll('menu li'), this.findAll('.content'))
|
||||
|
||||
this.gridTokens = new DataGrid(this.find('.granted-tokens'), {
|
||||
headers: [
|
||||
{ label: 'PIC', type: 'markup', filter: 'text', sortable: true},
|
||||
{ label: 'Company name', filter: 'text', sortable: true},
|
||||
{ label: 'Country', filter: 'list', sortable: true},
|
||||
{ label: 'Token granted', type: 'date', filter: 'text', sortable: true},
|
||||
{ label: 'Status', filter: 'list', sortable: true},
|
||||
{ label: 'Project', filter: 'text', sortable: true},
|
||||
{ label: 'Short proposal submission', type: 'date', filter: 'text', sortable: true},
|
||||
],
|
||||
height: 'calc(100vh - 472px)',
|
||||
actions: []
|
||||
});
|
||||
|
||||
this.btTokenDl = new Button(null, {icon: 'icon-download', severity: 'secondary', rounded: true, size: 'small', hint: 'Download complete list as CSV'})
|
||||
this.btTokenDl.addEventListener('click', this.downloadTokensList.bind(this));
|
||||
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenDl.el);
|
||||
|
||||
this.btTokenRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
|
||||
this.btTokenRefresh.addEventListener('click', this.refreshTokensList.bind(this));
|
||||
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenRefresh.el);
|
||||
|
||||
this.gridTokens.onRowFiltered = this.refreshMetrics.bind(this);
|
||||
|
||||
this.gridHistory = new DataGrid(this.find('.history-tokens'), {
|
||||
headers: [
|
||||
{ label: 'Date', type: 'dateTime', filter: 'text', sortable: true},
|
||||
{ label: 'PIC', filter: 'text', sortable: true},
|
||||
{ label: 'Company name', filter: 'text', sortable: true},
|
||||
{ label: 'Action', filter: 'list', sortable: true},
|
||||
{ label: 'Managed by', filter: 'text', sortable: true},
|
||||
],
|
||||
height: 'calc(100vh - 420px)',
|
||||
actions: []
|
||||
});
|
||||
|
||||
let btGrant = this.find('.grant-token');
|
||||
if(this.company.hasPrivilege('grant')) {
|
||||
btGrant.addEventListener('click', this.onTokenGrant.bind(this));
|
||||
} else {
|
||||
ui.hide(btGrant);
|
||||
}
|
||||
|
||||
if(this.tokens.hasPrivilege('list')) { this.refreshTokensList(); }
|
||||
|
||||
if(this.users) {
|
||||
this.loadContent(
|
||||
'projects/bypass/BypassPluginManagementContent',
|
||||
{ title: 'Accelerator configuration', subtitle: 'Users access & token management' },
|
||||
{ profile: this.profile, models: { users: this.users, tokens: this.tokens } }
|
||||
).then(view => this.setupUserManagement(view))
|
||||
} else {
|
||||
ui.hide(this.find('li.settings'));
|
||||
}
|
||||
}
|
||||
|
||||
setupUserManagement(view) {
|
||||
this.userManagement = view;
|
||||
this.find('.content.config').append(view.el);
|
||||
}
|
||||
|
||||
fill() {
|
||||
let year = new Date().getFullYear();
|
||||
let awardedPast = this.tokens.awardedTokens(year - 1);
|
||||
awardedPast.forEach(item => item.ref = 'assigned-past');
|
||||
let awardedCurrent = this.tokens.awardedTokens(year);
|
||||
awardedCurrent.forEach(item => item.ref = 'assigned-current');
|
||||
let consumedPast = this.tokens.consumedTokens(year - 1);
|
||||
consumedPast.forEach(item => item.ref = 'consumed-past');
|
||||
let consumedCurrent = this.tokens.consumedTokens(year);
|
||||
consumedCurrent.forEach(item => item.ref = 'consumed-current');
|
||||
|
||||
let history = this.tokens.history();
|
||||
|
||||
this.find('.metrics .available span').innerText = this.tokens.freeTokens - awardedCurrent.length;
|
||||
this.find('.metrics .assigned-current .year').innerText = year;
|
||||
this.find('.metrics .consumed-current .year').innerText = year;
|
||||
this.find('.metrics .assigned-past .year').innerText = year - 1;
|
||||
this.find('.metrics .consumed-past .year').innerText = year - 1;
|
||||
|
||||
this.gridHistory.loading = false;
|
||||
this.gridTokens.loading = false;
|
||||
this.btTokenRefresh.loading = false;
|
||||
|
||||
this.gridTokens.clear();
|
||||
|
||||
let awarded = awardedCurrent.concat(awardedPast);
|
||||
awarded.sort((a,b) => a.itemData.granted < b.itemData.granted ? 1: -1);
|
||||
|
||||
for (const item of awarded) {
|
||||
let priority = ''
|
||||
switch(item.itemData.status) {
|
||||
case 'consumed':
|
||||
priority = 'success';
|
||||
break;
|
||||
}
|
||||
|
||||
let row = this.gridTokens.addRow(item.itemData.id, [
|
||||
`<a href="#" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" title="View token details">${item.itemData.enterprise.pic}</a>`,
|
||||
item.itemData.enterprise.legalname,
|
||||
app.meta.toString('accelerator-tracks', item.itemData.domain),
|
||||
item.itemData.granted,
|
||||
`<span ${priority}>${item.itemData.status}</span>`,
|
||||
item.itemData.project ? item.itemData.project.acronym: '',
|
||||
item.itemData.project ? item.itemData.project.submissionDate: ''
|
||||
], true);
|
||||
|
||||
let tokenLink = row.querySelector('.cell:nth-child(2) a')
|
||||
tokenLink.addEventListener('click', this.onTokenView.bind(this));
|
||||
|
||||
row.classList.add(item.ref);
|
||||
|
||||
// checking if token can be revoked (meaning status is allocated and user has privilege for that action)
|
||||
if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (!item.itemData.isAssignedToShortProposal)) {
|
||||
let buttonBar = ui.create(`<div class="cols-2"></div>`)
|
||||
let revokeButton = ui.create(`<button eicbutton xsmall danger title="revoke this token" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Revoke</button>`);
|
||||
revokeButton.addEventListener('click', this.onTokenRevoke.bind(this));
|
||||
buttonBar.appendChild(revokeButton);
|
||||
|
||||
let useButton = ui.create(`<button eicbutton xsmall info title="Activate the coaching" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Consume</button>`);
|
||||
useButton.addEventListener('click', this.onTokenConsume.bind(this));
|
||||
buttonBar.appendChild(useButton);
|
||||
|
||||
row.querySelector('.cell.actions').appendChild(buttonBar);
|
||||
} else if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (item.itemData.isAssignedToShortProposal)){
|
||||
row.querySelector('.cell.actions').appendChild(ui.create(`<span>Consuming...</span>`));
|
||||
}
|
||||
}
|
||||
this.gridTokens.updateFilters();
|
||||
|
||||
// filling the history list
|
||||
this.gridHistory.clear();
|
||||
for (const item of history) {
|
||||
let priority = ''
|
||||
switch(item.action) {
|
||||
case 'revoked': priority = 'danger'; break;
|
||||
case 'consumed': priority = 'success'; break;
|
||||
}
|
||||
let row = this.gridHistory.addRow(item.date, [
|
||||
item.date,
|
||||
item.pic,
|
||||
item.legalname,
|
||||
`<span ${priority}>${item.action}</span>`,
|
||||
`${item.actor.firstname ? item.actor.firstname[0] + '. ': ''}${item.actor.lastname}`
|
||||
], true);
|
||||
}
|
||||
this.gridHistory.updateFilters();
|
||||
|
||||
this.refreshMetrics();
|
||||
}
|
||||
|
||||
refreshMetrics() {
|
||||
let rows = Array.from(this.gridTokens.filteredRows);
|
||||
|
||||
let assignedPast = rows.filter(item => item.classList.contains('assigned-past'));
|
||||
let assignedCurrent = rows.filter(item => item.classList.contains('assigned-current'));
|
||||
let consumedPast = rows.filter(item => item.classList.contains('consumed-past'));
|
||||
let consumedCurrent = rows.filter(item => item.classList.contains('consumed-current'));
|
||||
|
||||
this.find('.metrics .assigned-past span').innerText = assignedPast.length;
|
||||
this.find('.metrics .assigned-current span').innerText = assignedCurrent.length;
|
||||
this.find('.metrics .consumed-past span').innerText = consumedPast.length;
|
||||
this.find('.metrics .consumed-current span').innerText = consumedCurrent.length;
|
||||
}
|
||||
|
||||
refreshTokensList(event) {
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this.btTokenRefresh.loading = true;
|
||||
this.gridTokens.loading = true;
|
||||
this.gridHistory.loading = true;
|
||||
this.tokens.list().then(this.fill.bind(this));
|
||||
}
|
||||
|
||||
downloadTokensList(event) {
|
||||
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.btTokenDl.loading = true;
|
||||
this.tokens.export({filter: { includePersons: true }}).then(this.buildCSV.bind(this));
|
||||
}
|
||||
|
||||
buildCSV(payload) {
|
||||
|
||||
let options = {
|
||||
headers: [
|
||||
'PIC',
|
||||
'Organisation Name',
|
||||
'Granted on',
|
||||
'Scheme',
|
||||
'Entity',
|
||||
'Program',
|
||||
'Status',
|
||||
'Project',
|
||||
'Submission date',
|
||||
'Contact name',
|
||||
'Contact gender',
|
||||
'Contact position',
|
||||
'Contact email',
|
||||
'Contact phone',
|
||||
'SPOC name',
|
||||
'SPOC email',
|
||||
],
|
||||
filename: 'EIC-Bypass',
|
||||
delimiter: ';'
|
||||
}
|
||||
|
||||
let data = [];
|
||||
|
||||
for(let token of payload.tokens) {
|
||||
let row = [
|
||||
token.enterprise.pic,
|
||||
token.enterprise.legalname,
|
||||
token.granted,
|
||||
app.meta.toString('accelerator-tracks', token.track),
|
||||
app.meta.toString('accelerator-tracks', token.domain),
|
||||
token.program || '',
|
||||
token.history[0].status,
|
||||
token.project ? token.project.acronym: '',
|
||||
token.project ? token.project.submissiondate: '',
|
||||
token.shortProposal ? token.shortProposal.contact.lastname + ' ' + token.shortProposal.contact.firstname: '',
|
||||
token.shortProposal ? token.shortProposal.contact.gender: '',
|
||||
token.shortProposal ? token.shortProposal.contact.position: '',
|
||||
token.shortProposal ? token.shortProposal.contact.email: '',
|
||||
token.shortProposal ? token.shortProposal.contact.phone: '',
|
||||
token.spoc ? token.spoc.lastname + ' ' + token.spoc.firstname: '',
|
||||
token.spoc ? token.spoc.email: '',
|
||||
]
|
||||
|
||||
data.push(row);
|
||||
}
|
||||
|
||||
app.helpers.translator.toCSV( data, options );
|
||||
this.btTokenDl.loading = false;
|
||||
}
|
||||
|
||||
async onTokenGrant(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let grantResult = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantDialog',
|
||||
{ title: 'Grant a token' },
|
||||
{ models: { 'company': this.company, 'tokens': this.tokens }, profile: this.profile }
|
||||
)
|
||||
);
|
||||
|
||||
if(grantResult) {
|
||||
ui.growl.append('Token granted', 'success');
|
||||
let ConsumeResult = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantPropaDialog',
|
||||
{ title: 'Activate the coaching for...', subtitle: `${this.company.itemData.legalname} (${this.company.itemData.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
mode: 'edit',
|
||||
profile: this.profile,
|
||||
tokenId: this.company.itemData.token.id,
|
||||
pic: this.company.pic,
|
||||
legalname: this.company.itemData.legalname,
|
||||
wizard: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
this.refreshTokensList();
|
||||
}
|
||||
|
||||
async onTokenRevoke(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenRevokeDialog',
|
||||
{ title: 'Revoke a token for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
model: this.company,
|
||||
profile: this.profile,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
track: event.currentTarget.dataset.track,
|
||||
domain: event.currentTarget.dataset.domain,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token revoked', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenView(event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
|
||||
{ title: 'Token granted to ', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
mode: 'read',
|
||||
profile: this.profile,
|
||||
tokenId: event.currentTarget.dataset.id,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
legalname: event.currentTarget.dataset.legalname,
|
||||
wizard: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token consumed', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenConsume(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
|
||||
{ title: 'Activate the coaching for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
|
||||
{
|
||||
models: { users: this.users, token: this.tokens },
|
||||
mode: 'edit',
|
||||
profile: this.profile,
|
||||
tokenId: event.currentTarget.dataset.id,
|
||||
pic: event.currentTarget.dataset.pic,
|
||||
legalname: event.currentTarget.dataset.legalname,
|
||||
wizard: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Token consumed', 'success');
|
||||
this.refreshTokensList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassPluginDashboard', BypassPluginDashboard);
|
||||
@@ -0,0 +1,30 @@
|
||||
<div>
|
||||
<section>
|
||||
<article eiccard collapsable>
|
||||
<header>
|
||||
<h1>Users Access</h1>
|
||||
<h2></h2>
|
||||
</header>
|
||||
<section>
|
||||
<div eicdatagrid class="users-list"></div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-1 right"><button eicbutton primary small class="user-add">add user...</button></div>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
<section>
|
||||
<article eiccard collapsable class="programmes-article">
|
||||
<header>
|
||||
<h1>Programme Settings</h1>
|
||||
<h2>Programmes managed by your country</h2>
|
||||
</header>
|
||||
<section>
|
||||
<div eicdatagrid class="programmes-list" footer="hidden"></div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-1 right"><button class="programme-create" eicbutton primary small>add programme...</button></div>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,98 @@
|
||||
class BypassPluginManagementContent extends BypassBaseManagementContent {
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
|
||||
super.DOMContentLoaded(options);
|
||||
Object.assign(this, app.helpers.basicDialogs)
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.programsArticle = components.find(component => component.el.classList.contains('programmes-article'));
|
||||
|
||||
this.programsGrid = new DataGrid(this.find('.programmes-list'), {
|
||||
headers: [ { label: 'Program'} ],
|
||||
height: '320px',
|
||||
actions: []
|
||||
});
|
||||
|
||||
let programmeBt = this.find('.programme-create');
|
||||
if(this.tokens.hasPrivilege('addProgram')) {
|
||||
programmeBt.addEventListener('click', this.onProgramAdd.bind(this));
|
||||
} else {
|
||||
ui.hide(programmeBt);
|
||||
}
|
||||
|
||||
this.programsGrid.loading = true;
|
||||
this.tokens.getPrograms(this.profile.track, this.profile.domain)
|
||||
.then(this.fillPrograms.bind(this));
|
||||
}
|
||||
|
||||
fillPrograms(payload) {
|
||||
this.programsGrid.clear();
|
||||
this.programsGrid.loading = false;
|
||||
|
||||
for(let item of payload) {
|
||||
let row = this.programsGrid.addRow(0, [ item ]);
|
||||
|
||||
// checking if programme can be deleted
|
||||
if(this.tokens.hasPrivilege('removeProgram')) {
|
||||
let button = ui.create(`<button eicbutton small danger title="delete this programme" data-label="${item}" data-track="${this.profile.track}" data-domain="${this.profile.domain}" >delete</button>`);
|
||||
button.addEventListener('click', this.onProgramDelete.bind(this));
|
||||
row.querySelector('.cell.actions').appendChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onProgramAdd(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'projects/bypass/dialogs/BypassProgramAddDialog',
|
||||
{ title: 'Add a programme' },
|
||||
{ model: this.tokens, profile: this.profile }
|
||||
)
|
||||
);
|
||||
|
||||
if(result) {
|
||||
ui.growl.append('Programme added', 'success');
|
||||
this.programsGrid.loading = true;
|
||||
this.tokens.getPrograms(this.profile.track, this.profile.domain)
|
||||
.then((payload) => {
|
||||
this.fillPrograms(payload);
|
||||
this.programsArticle.expand();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async onProgramDelete(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let result = await this.confirmDialog({
|
||||
title: 'Delete this programme',
|
||||
message: `Please be aware that you are about to delete the programme "<strong>${event.target.dataset.label}</strong>"<br>
|
||||
Are you positive ?`,
|
||||
okLabel: 'Delete',
|
||||
okPromise:() => {
|
||||
let payload = {
|
||||
track: event.target.dataset.track,
|
||||
domain: event.target.dataset.domain,
|
||||
program: event.target.dataset.label
|
||||
}
|
||||
// this promise should always return a result (awaited above)
|
||||
// either built here or from the model action.
|
||||
return(this.tokens.removeProgram(payload))
|
||||
}
|
||||
});
|
||||
|
||||
if(result) {
|
||||
this.programsGrid.loading = true;
|
||||
this.tokens.getPrograms(this.profile.track, this.profile.domain)
|
||||
.then(this.fillPrograms.bind(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassPluginManagementContent', BypassPluginManagementContent);
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="fasttracks ">
|
||||
<section class="program">
|
||||
<input type="hidden" name="track" value="" />
|
||||
<input type="hidden" name="domain" value="" />
|
||||
<label>Programme name</label>
|
||||
<input type="text" class="required" eicinput name="program" value="" />
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,54 @@
|
||||
class BypassProgramAddDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
label: 'Create programme',
|
||||
onclick: this.add.bind(this),
|
||||
severity: 'primary',
|
||||
role: 'add',
|
||||
disabled: false
|
||||
}
|
||||
]
|
||||
|
||||
constructor(options) { super(options); }
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
this.model = options.model;
|
||||
this.profile = options.profile;
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.form = new Form(this.find('.program'));
|
||||
this.find('input[name="track"]').value = this.profile.track;
|
||||
this.find('input[name="domain"]').value = this.profile.domain;
|
||||
}
|
||||
|
||||
add() {
|
||||
if(this.form.validate()) {
|
||||
this.actions.find(o => o.role == 'add').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.addProgram(this.form.value)
|
||||
.then( this.onSuccess.bind(this), this.onFailed.bind(this) )
|
||||
}
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
this.actions.find(o => o.role == 'add').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.commit(true);
|
||||
}
|
||||
|
||||
onFailed() {
|
||||
this.actions.find(o => o.role == 'add').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.abort();
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassProgramAddDialog',BypassProgramAddDialog);
|
||||
@@ -0,0 +1,34 @@
|
||||
<div class="fasttracks ">
|
||||
<section class="cols-2 right top search">
|
||||
<input eicinput type="search" class="required validate-number" data-path="query" hint="The PIC number of a company is at least 8 digit long number" placeholder="Enter a PIC number..." />
|
||||
<button eicbutton primary icon="icon-search" class="search"></button>
|
||||
</section>
|
||||
<section class="result">
|
||||
|
||||
</section>
|
||||
<section class="explanation">
|
||||
<input type="hidden" data-path="pic" value="" />
|
||||
<alert eicalert info>In order to complete your request, please fill in the following</alert>
|
||||
<p><span class="company"></span> should get access to the Accelerator under this specific scheme:</p>
|
||||
<section class="cols-2">
|
||||
<div>
|
||||
<label>Scheme</label>
|
||||
<select eicselect data-path="track" class="required"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Entity</label>
|
||||
<select eicselect data-path="domain" class="required"></select>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<textarea name="justification" eictextarea class="required"
|
||||
placeholder="Please explain why you are proposing this company for the EIC Accelerator"></textarea>
|
||||
<div class="programme">
|
||||
<label>Related programme</label>
|
||||
<select data-path="programme" eicselect placeholder="Please select a programme..."></select>
|
||||
</div>
|
||||
<input eiccheckbox type="checkbox" data-path="justification.independentExpert" data-type="ignore" class="required" label="You confirm that you have relied on independent external experts to perform the project review" value="yes" />
|
||||
<input eiccheckbox type="checkbox" data-path="justification.rulesCompliant" data-type="ignore" class="required" label="You confirm that you have complied with all the conditions and requirements set out for the scheme" value="yes" />
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
class BypassTokenGrantDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
label: 'Grant token',
|
||||
onclick: this.grant.bind(this),
|
||||
severity: 'primary',
|
||||
role: 'grant',
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
|
||||
status = '';
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
|
||||
for(let model in options.models) this[model] = options.models[model];
|
||||
this.profile = options.profile;
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.searchbar = this.find('.search');
|
||||
this.result = this.find('.result');
|
||||
this.query = components.find(component => component.el.dataset.path == 'query');
|
||||
this.query.onQuery = this.onSearch.bind(this);
|
||||
this.trackSelect = components.find(component => component.el.dataset.path == 'track');
|
||||
this.domainSelect = components.find(component => component.el.dataset.path == 'domain');
|
||||
this.programmeSelect = components.find(component => component.el.dataset.path == 'programme');
|
||||
this.searchButton = components.find(component => component.el.classList.contains('search'));
|
||||
|
||||
ui.hide(this.find('.programme'));
|
||||
this.programmeSelect.el.classList.remove('required');
|
||||
|
||||
let tracks = app.meta.toOptions(app.meta.getCollection('accelerator-tracks'), this.profile.track, true);
|
||||
tracks.forEach(item => this.trackSelect.el.append(item));
|
||||
|
||||
if(this.profile.track) {
|
||||
this.fillDomain(this.profile.track, this.profile.domain);
|
||||
//NIKE : Quickpatch because <option selected> is broken by the famous "couldn't care less update" in eicui
|
||||
this.trackSelect.value = this.profile.track
|
||||
this.trackSelect.disabled = true;
|
||||
|
||||
} else {
|
||||
this.trackSelect.change(this.onTrackChange.bind(this));
|
||||
}
|
||||
|
||||
if(this.profile.domain) {
|
||||
//NIKE : Quickpatch because <option selected> is broken by the famous "couldn't care less update" in eicui
|
||||
this.domainSelect.value = this.profile.domain
|
||||
this.domainSelect.disabled = true;
|
||||
// MFA: added this as it seems when using SPOCs, programmes were not handled
|
||||
this.fillProgramme(this.trackSelect.value, this.domainSelect.value);
|
||||
} else {
|
||||
this.domainSelect.change(this.onDomainChange.bind(this));
|
||||
}
|
||||
|
||||
this.form = new Form(this.find('.explanation'));
|
||||
|
||||
ui.hide(this.result)
|
||||
ui.hide(this.form.el)
|
||||
|
||||
this.searchButton.el.addEventListener('click', this.onSearch.bind(this));
|
||||
}
|
||||
|
||||
onTrackChange(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if(this.trackSelect.value) {
|
||||
this.fillDomain(this.trackSelect.value);
|
||||
} else {
|
||||
this.domainSelect.empty();
|
||||
}
|
||||
|
||||
this.fillProgramme(this.trackSelect.value, this.domainSelect.value);
|
||||
}
|
||||
|
||||
onDomainChange(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.fillProgramme(this.trackSelect.value, this.domainSelect.value);
|
||||
}
|
||||
|
||||
fillDomain(track, domain) {
|
||||
let domains = track ? app.meta.toOptions(app.meta.getItem('accelerator-tracks', track).children, domain, true): [];
|
||||
this.domainSelect.empty();
|
||||
domains.forEach(item => this.domainSelect.el.append(item));
|
||||
}
|
||||
|
||||
fillProgramme(track, domain) {
|
||||
|
||||
ui.hide(this.find('.programme'));
|
||||
this.programmeSelect.empty();
|
||||
this.programmeSelect.el.classList.remove('required');
|
||||
this.programmeSelect.el.dataset.type = 'ignore';
|
||||
|
||||
if(track == 'daTai94ymStyRbQWybH3eDw') {
|
||||
if(domain) {
|
||||
|
||||
ui.lock();
|
||||
this.tokens.getPrograms(track, domain)
|
||||
.then(payload => {
|
||||
ui.unlock();
|
||||
if(payload.length > 0) {
|
||||
ui.show(this.find('.programme'));
|
||||
this.programmeSelect.el.classList.add('required');
|
||||
this.programmeSelect.el.dataset.type = 'text';
|
||||
this.programmeSelect.el.append(ui.create('<option value="">(select a program)</option>'))
|
||||
for(let program of payload) {
|
||||
this.programmeSelect.el.append(ui.create(`<option value="${program}">${program}</option>`))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(event) {
|
||||
if(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
this.query.value = this.query.value.trim();
|
||||
if(this.query.value != '') {
|
||||
ui.hide(this.result);
|
||||
this.searchButton.loading = true;
|
||||
this.company.search(this.query.value)
|
||||
.then(this.onResult.bind(this), this.onEmptyResult.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
onResult(result) {
|
||||
ui.show(this.result);
|
||||
this.searchButton.loading = false;
|
||||
let hasToken = result.token && Object.keys(result.token).length > 0;
|
||||
let status = hasToken ? 'danger': 'success';
|
||||
let message = hasToken ?
|
||||
'This company is already benefitting of the EIC Accelerator':
|
||||
'This company is elligible for the EIC Accelerator';
|
||||
|
||||
this.result.innerHTML = `<div class="company" ${status} class="icon-success">
|
||||
<div><span class="legalname" primary>${result.legalname}</span></div>
|
||||
<div class="cols-3 top">
|
||||
<div><label>PIC</label><span class="pic">${result.pic}</span></div>
|
||||
<div><label>Country</label><span class="domain">${result.country || '(unknown)'}</span></div>
|
||||
<div><label>Location</label><span class="domain">${result.city || '(unknown)'}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div eicalert ${status} success>${message}</div>`;
|
||||
|
||||
this.actions.find(o => o.role == 'grant').button.disabled = true;
|
||||
|
||||
// resolving user interaction
|
||||
if(!hasToken) {
|
||||
//this.find('[data-path="pic"]').value
|
||||
this.legalname = result.legalname;
|
||||
this.status = 'selecting'
|
||||
this.find('span.company').innerHTML = `${result.legalname} (${result.pic})`;
|
||||
this.actions.find(o => o.role == 'grant').button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
onEmptyResult() {
|
||||
ui.show(this.result);
|
||||
this.searchButton.loading = false;
|
||||
this.result.innerHTML = `<div eicalert danger>Sorry, this PIC is not registered</div>`;
|
||||
this.status = 'selecting';
|
||||
this.actions.find(o => o.role == 'grant').button.disabled = true;
|
||||
}
|
||||
|
||||
onSelect() {
|
||||
this.find('[data-path="pic"]').value = this.query.value;
|
||||
ui.hide(this.searchbar);
|
||||
ui.hide(this.result);
|
||||
ui.show(this.form.el);
|
||||
|
||||
this.status = 'completing';
|
||||
}
|
||||
|
||||
grant() {
|
||||
switch(this.status) {
|
||||
case 'selecting':
|
||||
this.onSelect();
|
||||
break;
|
||||
case 'completing':
|
||||
if(this.form.validate()) {
|
||||
this.actions.find(o => o.role == 'grant').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.company.grant(this.company.itemData.pic, this.form.value)
|
||||
.then( this.onGrantSuccess.bind(this), this.onGrantFailed.bind(this))
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onGrantSuccess() {
|
||||
this.actions.find(o => o.role == 'grant').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.commit(true);
|
||||
}
|
||||
|
||||
onGrantFailed() {
|
||||
this.actions.find(o => o.role == 'grant').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.abort();
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassTokenGrantDialog',BypassTokenGrantDialog);
|
||||
@@ -0,0 +1,155 @@
|
||||
<style>
|
||||
.fasttracks-propa {
|
||||
padding: var(--eicui-base-spacing-xl);
|
||||
transition: all 0.5s;
|
||||
}
|
||||
.fasttracks-propa.loading {
|
||||
padding: var(--eicui-base-spacing-xl);
|
||||
transition: all 0.5s;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
.fasttracks-propa .spinner {
|
||||
text-align: center;
|
||||
display: none;
|
||||
animation: spin 1s infinite linear;
|
||||
}
|
||||
.fasttracks-propa .content { display: block; }
|
||||
.fasttracks-propa.loading .spinner { display: block; }
|
||||
.fasttracks-propa.loading .content { display: none; }
|
||||
.fasttracks-propa .fields-group { min-height: 490px; }
|
||||
.fasttracks-propa .fields-group.read { min-height: 380px; }
|
||||
</style>
|
||||
<div class="fasttracks-propa loading">
|
||||
<div large class="spinner icon-spinner">
|
||||
</div>
|
||||
<div class="content">
|
||||
<input eicinput type="hidden" name="pic" data-path="organisation" value="" />
|
||||
<div eicalert success class="wizard-message">A token has been successfully granted to <span class="company"></span></div>
|
||||
<p class="instructions">In order to immediately activate the coaching, please fill-in the following informations :</p>
|
||||
<div class="cols-2 left">
|
||||
<div class="tabs-extended vertical">
|
||||
<menu eictab>
|
||||
<li data-target="token">Token</li>
|
||||
<li data-target="enterprise">Company <span xxsmall eicbadge danger data-target="enterprise"></span></li>
|
||||
<li data-target="proposal">Proposal <span xxsmall eicbadge danger data-target="proposal"></span></li>
|
||||
<li data-target="contact">Contact person <span xxsmall eicbadge danger data-target="contact"></span></li>
|
||||
</menu>
|
||||
</div>
|
||||
<div class="sections">
|
||||
<article eiccard class="fields-group" data-target="token">
|
||||
<section class="cols-2">
|
||||
<div>
|
||||
<label>granted</label>
|
||||
<input eicinput name="granted" data-path="token" data-type="ignore" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label>status</label>
|
||||
<input eicinput name="status" data-path="" data-type="ignore" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label>Scheme</label>
|
||||
<input eicinput name="track" data-path="token" data-type="ignore" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label>Domain</label>
|
||||
<input eicinput name="domain" data-path="token" data-type="ignore" disabled />
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="fields-group" data-target="enterprise">
|
||||
<section>
|
||||
<div class="cols-2">
|
||||
<div>
|
||||
<label>PIC</label>
|
||||
<input eicinput name="pic" data-path="token.enterprise" data-type="ignore" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label>Legal name</label>
|
||||
<input eicinput name="legalname" data-path="token.enterprise" data-type="ignore" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Type of entity</label>
|
||||
<select eicselect name="companyType" data-path="organisation" class="required">
|
||||
<option value=""></option>
|
||||
<option value="sme">SME</option>
|
||||
<option value="midcap">Small mid-cap</option>
|
||||
<option value="person">Natural person</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="fields-group" data-target="proposal">
|
||||
<section>
|
||||
<div class="cols-2">
|
||||
<div>
|
||||
<label>Acronym</label>
|
||||
<input eicinput name="acronym" data-path="proposal" data-type="text" maxlen="50" class="required" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Submitted</label>
|
||||
<input eicinput name="submissiondate" data-path="token.project" data-type="ignore" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Full Title</label>
|
||||
<input eicinput name="title" data-path="proposal" data-type="text" maxlen="200" class="required" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Abstract</label>
|
||||
<textarea eictextarea name="abstract" data-path="proposal" data-type="text" maxlen="1000" class="required"></textarea>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="fields-group" data-target="contact">
|
||||
<section>
|
||||
<div class="lookup">
|
||||
<label>Please identify the contact person by the EU-Login</label>
|
||||
<div class="cols-2 right center">
|
||||
<input eicinput type="search" name="euLoginId" data-path="contact" class="required" value="" placeholder="Enter member's EU Login ID or email and press enter"/>
|
||||
<button eicbutton icon="icon-search" class="user-search" primary ></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cols-3">
|
||||
<div>
|
||||
<label>First name</label>
|
||||
<input eicinput disabled name="firstname" data-path="contact" data-type="text" class="required" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Last name</label>
|
||||
<input eicinput disabled name="lastname" data-path="contact" data-type="text" class="required" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Gender</label>
|
||||
<select eicselect name="gender" data-path="contact" data-type="text" class="required">
|
||||
<option></option>
|
||||
<option value="F">Female</option>
|
||||
<option value="M">Male</option>
|
||||
<option value="I">I prefer not to declare</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cols-2">
|
||||
<div>
|
||||
<label>email</label>
|
||||
<input eicinput disabled type="email" name="email" data-path="contact" data-type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Phone</label>
|
||||
<input eicinput type="tel" name="phone" data-path="contact" data-type="text" maxlen="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Position</label>
|
||||
<select eicselect lookup name="position" data-path="contact" data-type="text" class="required"></select>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,220 @@
|
||||
class BypassTokenGrantProposalDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
disabled: false,
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
label: 'Assign token to this proposal',
|
||||
onclick: this.consume.bind(this),
|
||||
severity: 'primary',
|
||||
disabled: false,
|
||||
role: 'consume'
|
||||
}
|
||||
]
|
||||
|
||||
status = '';
|
||||
mode = 'edit';
|
||||
validationInterval = 1000;
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
|
||||
console.log('==BypassTokenGrantProposalDialog==>', options)
|
||||
|
||||
this.models = options.models;
|
||||
this.mode = options.mode || this.mode;
|
||||
this.enableWizard = options.wizard;
|
||||
this.tokenId = options.tokenId;
|
||||
this.components = ui.eicfy(this.el);
|
||||
this.find('.fasttracks-propa').classList.add(this.mode)
|
||||
|
||||
this.tabs = new Tab();
|
||||
this.tabs.addTabs(this.findAll('.tabs-extended menu li'), this.findAll('.fields-group'))
|
||||
|
||||
let position = this.find('select[name="position"]');
|
||||
app.meta.toOptions('organisation-functions', null, true).forEach(item => position.append(item));
|
||||
|
||||
this.find('[name="pic"]').value = options.pic;
|
||||
|
||||
this.euid = this.components.find(component => component.el.name == 'euLoginId');
|
||||
this.firstname = this.components.find(component => component.el.name == 'firstname');
|
||||
this.lastname = this.components.find(component => component.el.name == 'lastname');
|
||||
this.email = this.components.find(component => component.el.name == 'email');
|
||||
this.userSearchButton = this.components.find(component => component.el.classList.contains('user-search'));
|
||||
this.cards = this.components.filter(component => component.el.classList.contains('fields-group'))
|
||||
|
||||
this.globalForm = new Form(this.el);
|
||||
this.miniForms = Array.from(this.cards.map(card=>card.el).map(el => new Form(el)))
|
||||
this.euid.onQuery = this.onUserSearch.bind(this)
|
||||
this.userSearchButton.el.addEventListener('click', this.onUserSearch.bind(this))
|
||||
|
||||
}
|
||||
|
||||
DOMContentFocused() {
|
||||
this.actions.forEach(o => o.button.disabled = true);
|
||||
this.models.token.viewToken(this.tokenId)
|
||||
.then(payload => { this.setMode(this.mode, payload) });
|
||||
}
|
||||
|
||||
setMode(mode, payload) {
|
||||
this.find('.fasttracks-propa').classList.remove('loading');
|
||||
// cleanup
|
||||
payload.token.granted = payload.token.granted ? ui.format.dateTime(payload.token.granted): ''
|
||||
payload.token.track = app.meta.toString('accelerator-tracks', payload.token.track)
|
||||
payload.token.domain = app.meta.toString('accelerator-tracks', payload.token.domain)
|
||||
payload.status = payload.token.history[0].status;
|
||||
if(!payload.token.project) payload.token.project = {}
|
||||
|
||||
payload.token.project.submissiondate = payload.token.project.submissiondate ? ui.format.dateTime(payload.token.project.submissiondate): ''
|
||||
|
||||
let fields = this.globalForm.scan();
|
||||
for(let field of fields) {
|
||||
let component = this.el2component(field.el.dataset.path, field.el.name);
|
||||
|
||||
if(component) component.disabled = this.mode == 'read' || field.el.hasAttribute('disabled');
|
||||
|
||||
let item = this.findField(payload, field.el.dataset.path, field.el.name)
|
||||
if(item) {
|
||||
if(component.el.type == 'checkbox'){
|
||||
component.el.checked = item == true;
|
||||
}
|
||||
else
|
||||
component.value = item;
|
||||
}
|
||||
}
|
||||
|
||||
switch(mode) {
|
||||
case 'read':
|
||||
ui.hide(this.find('.wizard-message'));
|
||||
ui.hide(this.find('.lookup'));
|
||||
ui.hide(this.find('.instructions'));
|
||||
ui.hide(this.actions.find(o => o.role == 'consume').button.el);
|
||||
this.actions.find(o => o.role == 'cancel').label = 'Close';
|
||||
|
||||
break;
|
||||
case 'edit':
|
||||
if(!this.enableWizard)
|
||||
ui.hide(this.find('.wizard-message'))
|
||||
else
|
||||
this.actions.find(o => o.role == 'cancel').label = 'Skip';
|
||||
|
||||
this.tabs.selectByIndex(1);
|
||||
|
||||
this.validationCheck = setInterval(this.validateTabs.bind(this), this.validationInterval);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.actions.forEach(o => o.button.disabled = false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
el2component(path, name) {
|
||||
let item = this.components
|
||||
.find(o =>
|
||||
o.el.name == name &&
|
||||
( !path || (path && o.el.dataset.path == path))
|
||||
)
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a data field value based on path an name
|
||||
* @param {*} path
|
||||
* @param {*} name
|
||||
* @returns
|
||||
*/
|
||||
findField(payload, path, name) {
|
||||
let levels = path && path != '' && path !== undefined ? path.split('.'): [];
|
||||
let tree = payload;
|
||||
|
||||
for(let level of levels)
|
||||
if(tree.hasOwnProperty(level)) tree = tree[level];
|
||||
if(tree.hasOwnProperty(name)) return tree[name];
|
||||
}
|
||||
|
||||
onUserSearch(event) {
|
||||
this.userSearchButton.loading = true;
|
||||
this.models.users.search(this.euid.value).then(
|
||||
(userList => {
|
||||
this.userSearchButton.loading = false;
|
||||
if(userList.length==0) return
|
||||
// TODO better manage if several (take most complete?)
|
||||
|
||||
let user = userList[0];
|
||||
this.euid.value = user.uid;
|
||||
this.firstname.value = user.given_name;
|
||||
this.lastname.value = user.family_name;
|
||||
this.email.value = user.email;
|
||||
}),
|
||||
(err) => {
|
||||
this.userSearchButton.loading = false;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
validateTabs() {
|
||||
let valid = true;
|
||||
for(let form of this.miniForms) {
|
||||
|
||||
let report = form.validate(true)
|
||||
let badge = this.components.find(item => item.el.hasAttribute('eicbadge') && item.el.dataset.target == form.el.dataset.target)
|
||||
if(badge) {
|
||||
badge.value = report.issues;
|
||||
}
|
||||
valid = valid && report.valid;
|
||||
}
|
||||
|
||||
this.actions.find(o => o.role == 'consume').button.disabled = !valid;
|
||||
}
|
||||
|
||||
consume() {
|
||||
if(this.globalForm.validate()) {
|
||||
this.actions.find(o => o.role == 'consume').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.models.token.consumeToken(this.globalForm.value)
|
||||
.then( this.onConsumeSuccess.bind(this), this.onConsumeFailed.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
onConsumeSuccess() {
|
||||
this.actions.find(o => o.role == 'consume').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.commit(true);
|
||||
}
|
||||
|
||||
onConsumeFailed() {
|
||||
this.actions.find(o => o.role == 'consume').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.abort();
|
||||
}
|
||||
|
||||
DOMContentResized() {
|
||||
super.DOMContentResized();
|
||||
|
||||
let device = this.parentContainer.getAttribute('device');
|
||||
//let tabs = this.find('.tabs-extended');
|
||||
|
||||
switch(device) {
|
||||
case 'desktop':
|
||||
this.tabs.triggers.forEach(item => ui.show(item));
|
||||
let current = this.tabs.getSelected();
|
||||
let target = current.dataset.target;
|
||||
this.findAll('article.fields-group').forEach(content => { if(content.dataset.target != target) ui.hide(content) });
|
||||
break;
|
||||
default:
|
||||
this.tabs.triggers.forEach(item => ui.hide(item));
|
||||
|
||||
this.findAll('article.fields-group').forEach(content => { ui.show(content) })
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassTokenGrantProposalDialog',BypassTokenGrantProposalDialog);
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="fasttracks ">
|
||||
<section class="explanation">
|
||||
<alert eicalert danger>Please be aware that you are about to revoke an awarded token.</alert>
|
||||
</section>
|
||||
<section class="agreement">
|
||||
<input type="hidden" name="track" value="" />
|
||||
<input type="hidden" name="domain" value="" />
|
||||
<textarea data-path="justification" eictextarea maxlen="200" class="required"
|
||||
hint="No more than 200 characters please."
|
||||
placeholder="Please explain why you are removing this company for the EIC Accelerator"></textarea>
|
||||
|
||||
<input eiccheckbox type="checkbox" name="agreed" data-type="ignore" value="yes" label="I understand" />
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,58 @@
|
||||
class BypassTokenRevokeDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
label: 'Revoke token',
|
||||
onclick: this.revoke.bind(this),
|
||||
severity: 'danger',
|
||||
role: 'revoke',
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
|
||||
constructor(options) { super(options); }
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
this.model = options.model;
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.form = new Form(this.find('.agreement'));
|
||||
this.pic = options.pic;
|
||||
this.find('input[name="track"]').value = options.track;
|
||||
this.find('input[name="domain"]').value = options.domain;
|
||||
this.optin = this.find('input[name="agreed"]');
|
||||
this.optin.addEventListener('change',this.onAgreement.bind(this));
|
||||
}
|
||||
|
||||
onAgreement() { this.actions.find(o => o.role == 'revoke').button.disabled = !this.optin.checked; }
|
||||
|
||||
revoke() {
|
||||
if(this.form.validate()) {
|
||||
this.actions.find(o => o.role == 'revoke').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.revoke(this.pic, this.form.value)
|
||||
.then( this.commit.bind(this), this.abort.bind(this) )
|
||||
}
|
||||
}
|
||||
|
||||
onRevokeSuccess() {
|
||||
this.actions.find(o => o.role == 'revoke').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.commit(true);
|
||||
}
|
||||
|
||||
onRevokeFailed() {
|
||||
this.actions.find(o => o.role == 'revoke').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.abort();
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassTokenRevokeDialog',BypassTokenRevokeDialog);
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="fasttracks settings">
|
||||
<section class="explanation">
|
||||
<alert eicalert danger>Please be aware that your changes will take place immediately.</alert>
|
||||
</section>
|
||||
<section class="domains">
|
||||
<input type="hidden" data-path="track" value="${track}" />
|
||||
<div eicdatagrid footer="hidden"></div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,80 @@
|
||||
class BypassTrackSetupDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
label: 'Save',
|
||||
onclick: this.save.bind(this),
|
||||
severity: 'primary',
|
||||
role: 'save'
|
||||
}
|
||||
]
|
||||
|
||||
constructor(options) { super(options); }
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
this.model = options.model;
|
||||
this.track = options.track;
|
||||
this.domains = options.domains;
|
||||
|
||||
|
||||
|
||||
this.grid = new DataGrid(this.find('.domains [eicdatagrid]'), {
|
||||
headers: [ { label: 'Domain'}, { label: 'Tokens', type: 'markup' }, { label: 'Min.' } ],
|
||||
height: '320px'
|
||||
});
|
||||
|
||||
this.grid.clear();
|
||||
this.grid.loading = true;
|
||||
this.model.getSettings(this.track)
|
||||
.then(this.fill.bind(this));
|
||||
}
|
||||
|
||||
fill(payload) {
|
||||
this.grid.clear();
|
||||
this.grid.loading = false;
|
||||
let index = 0;
|
||||
for(let item of payload.domains) {
|
||||
this.grid.addRow(item.domain, [
|
||||
app.meta.toString('accelerator-tracks', item.domain),
|
||||
`<input type="hidden" data-path="domains[${index}].domain" value="${item.domain}" />
|
||||
<input eicinput type="number" min="${item.tokens - item.remaining}" data-path="domains[${index}].tokens" value="${item.tokens}" />`,
|
||||
item.tokens - item.remaining
|
||||
]);
|
||||
index++;
|
||||
}
|
||||
this.grid.sort(1, 'asc');
|
||||
this.form = new Form(this.find('.domains'));
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
}
|
||||
|
||||
save() {
|
||||
if(this.form.validate()) {
|
||||
this.actions.find(o => o.role == 'save').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.setSettings(this.form.value)
|
||||
.then(this.onSuccess.bind(this))
|
||||
.catch(this.onFailed.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
this.actions.find(o => o.role == 'save').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.commit(true);
|
||||
}
|
||||
|
||||
onFailed() {
|
||||
this.actions.find(o => o.role == 'save').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
//this.abort();
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('BypassTrackSetupDialog',BypassTrackSetupDialog);
|
||||
@@ -0,0 +1,51 @@
|
||||
<div class="user-dialog">
|
||||
<div>
|
||||
<label>EU login or email</label>
|
||||
<div class="cols-2 right top">
|
||||
<input eicinput type="search" data-path="euid" class="required" value="" />
|
||||
<button eicbutton icon="icon-search" class="user-search" primary ></button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Email</label>
|
||||
<input eicinput disabled value="" class="required email" data-path="email"/>
|
||||
</div>
|
||||
<div class="cols-2">
|
||||
<div>
|
||||
<label>Firstname</label>
|
||||
<input eicinput disabled value="" class="required" data-path="firstname"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Lastname</label>
|
||||
<input eicinput disabled value="" class="required" data-path="lastname"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Gender</label>
|
||||
<select eicselect class="required" data-path="gender">
|
||||
<option></option>
|
||||
<option value="M">Male</option>
|
||||
<option value="F">Female</option>
|
||||
<option value="I">Rather not say</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div hidden class="roleSelector">
|
||||
<label>Role</label>
|
||||
<select eicselect class="required" class="required" data-path="role">
|
||||
<option></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="cols-3">
|
||||
<div hidden class="trackSelector">
|
||||
<label>Track</label>
|
||||
<select eicselect data-path="track"></select>
|
||||
</div>
|
||||
<div hidden class="domainSelector">
|
||||
<label>Domain</label>
|
||||
<select eicselect data-path="domain"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,252 @@
|
||||
class BypassUserProfileDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'Cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
label: 'Add user',
|
||||
onclick: this.create.bind(this),
|
||||
severity: 'primary',
|
||||
role: 'add',
|
||||
disabled: true
|
||||
},
|
||||
]
|
||||
|
||||
async DOMContentLoaded(options) {
|
||||
this.mode = options.mode ? options.mode: 'create';
|
||||
this.model = options.model;
|
||||
this.profile = options.profile;
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.euid = components.find(component => component.el.dataset.path == 'euid');
|
||||
this.firstname = components.find(component => component.el.dataset.path == 'firstname');
|
||||
this.lastname = components.find(component => component.el.dataset.path == 'lastname');
|
||||
this.email = components.find(component => component.el.dataset.path == 'email');
|
||||
|
||||
this.genderSelect = components.find(component => component.el.dataset.path == 'gender');
|
||||
this.roleSelect = components.find(component => component.el.dataset.path == 'role');
|
||||
this.roleSelect.block = this.find('.roleSelector')
|
||||
this.trackSelect = components.find(component => component.el.dataset.path == 'track');
|
||||
this.trackSelect.block = this.find('.trackSelector')
|
||||
this.domainSelect = components.find(component => component.el.dataset.path == 'domain');
|
||||
this.domainSelect.block = this.find('.domainSelector');
|
||||
|
||||
this.userSearcButton = components.find(component => component.el.classList.contains('user-search'));
|
||||
|
||||
this.genderSelect.change(this.changeRoleAvailable.bind(this));
|
||||
|
||||
this.roleSelect.change(this.onRoleChange.bind(this));
|
||||
this.roleSelect.disabled = true;
|
||||
this.trackSelect.change(this.onTrackChange.bind(this));
|
||||
this.trackSelect.disabled = true;
|
||||
this.domainSelect.change(this.onDomainChange.bind(this));
|
||||
this.domainSelect.disabled = true;
|
||||
|
||||
this.form = new Form(this.find('.user-dialog'));
|
||||
|
||||
let tracks = app.meta.toOptions(app.meta.getCollection('accelerator-tracks'), null, true);
|
||||
tracks.forEach(item => this.trackSelect.el.append(item));
|
||||
|
||||
this.roleDelegations = {}
|
||||
this.roleDelegations = await app.Assets.loadJson({name:'global/bypass/roleDelegations.json'})
|
||||
|
||||
this.userSearcButton.el.addEventListener('click', this.onUserSearch.bind(this));
|
||||
}
|
||||
|
||||
changeRoleAvailable() {
|
||||
if( this.form.validateField(this.euid).valid &&
|
||||
this.form.validateField(this.firstname).valid &&
|
||||
this.form.validateField(this.lastname).valid &&
|
||||
this.form.validateField(this.email).valid &&
|
||||
this.form.validateField(this.genderSelect).valid
|
||||
) {
|
||||
this.roleSelect.disabled = false;
|
||||
this.changeAddAvailable();
|
||||
let roles4select = this.getDelegationRules().map(x=>x.role);
|
||||
let options = app.meta.toOptions(roles4select.map(x=>({id:x, label:x})), '', true)
|
||||
this.roleSelect.empty();
|
||||
options.forEach(item => this.roleSelect.el.append(item));
|
||||
ui.show(this.roleSelect.block);
|
||||
} else {
|
||||
this.hideSelect(this.roleSelect);
|
||||
}
|
||||
}
|
||||
|
||||
changeAddAvailable() {
|
||||
if(this.form.validate() && this.validateDelegation()) {
|
||||
this.actions.find(o => o.role == 'add').button.disabled = false;
|
||||
} else {
|
||||
this.actions.find(o => o.role == 'add').button.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
getDelegationRules() {
|
||||
/* In delegations, which of my roles shall I use as master... (no role hierarchy)
|
||||
For now priority is top-down in the roleDelegation masters order.
|
||||
Bad, not 100% reliable, but in our case, there should be no conflicts (mutually exclusive in delegations)
|
||||
*/
|
||||
let delegationsRules = [];
|
||||
for(let roleDeleg of this.roleDelegations){
|
||||
if(app.User.hasRole(roleDeleg.master)) {
|
||||
delegationsRules = roleDeleg.surrogates;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return(delegationsRules)
|
||||
}
|
||||
|
||||
hideSelect(sel) {
|
||||
sel.disabled = true;
|
||||
sel.value = '';
|
||||
ui.hide(sel.block);
|
||||
}
|
||||
|
||||
ruleSelect(rule, sel, inheritValue) {
|
||||
if(rule=='inherit') { // Forced to my own value
|
||||
sel.disabled = true;
|
||||
sel.value = inheritValue;
|
||||
ui.show(sel.block);
|
||||
} else if(rule=='set') { // Can choose
|
||||
sel.disabled = false;
|
||||
ui.show(sel.block);
|
||||
} else if(rule=='ignore'){ // Not applicable
|
||||
this.hideSelect(sel);
|
||||
} else { // Forced to fixed value
|
||||
sel.disabled = true;
|
||||
sel.value = rule;
|
||||
ui.show(sel.block);
|
||||
}
|
||||
}
|
||||
|
||||
validateDelegation(){
|
||||
let rules = this.getDelegationRules().find(x => x.role==this.roleSelect.value);
|
||||
if(!rules) return(false)
|
||||
|
||||
const validateSel = (rule, sel, inheritValue) => {
|
||||
let ok = false;
|
||||
if(rule=='inherit') ok = (sel.value == inheritValue)
|
||||
else if(rule=='set') ok = Boolean(sel.value)
|
||||
else if(rule=='ignore') ok = (true)
|
||||
else ok = (sel.value == rule)
|
||||
if(ok) sel.el.nextSibling.classList.remove('validation-failed');
|
||||
else sel.el.nextSibling.classList.add('validation-failed');
|
||||
return(ok)
|
||||
}
|
||||
return( validateSel(rules.track, this.trackSelect, this.profile.track) &&
|
||||
validateSel(rules.domain, this.domainSelect, this.profile.domain)
|
||||
)
|
||||
}
|
||||
|
||||
onRoleChange(event) {
|
||||
if(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
// pas de role: pas de track, pas de track : pas de domaine, pas de domaine: pas de palais, etc
|
||||
if(!this.roleSelect.value){
|
||||
this.hideSelect(this.trackSelect);
|
||||
this.hideSelect(this.domainSelect);
|
||||
return
|
||||
}
|
||||
|
||||
let rules = this.getDelegationRules().find(x => x.role==this.roleSelect.value);
|
||||
|
||||
if(!rules) {
|
||||
console.warn('Delegation not found for role '+this.roleSelect.value);
|
||||
this.hideSelect(this.trackSelect);
|
||||
this.hideSelect(this.domainSelect);
|
||||
return
|
||||
}
|
||||
|
||||
this.ruleSelect(rules.track, this.trackSelect, this.profile.track);
|
||||
this.onTrackChange();
|
||||
this.ruleSelect(rules.domain, this.domainSelect, this.profile.domain);
|
||||
|
||||
this.changeAddAvailable();
|
||||
}
|
||||
|
||||
|
||||
onUserSearch(event) {
|
||||
this.userSearcButton.loading = true;
|
||||
this.model.search(this.euid.value).then(
|
||||
(userList => {
|
||||
this.userSearcButton.loading = false;
|
||||
if(userList.length==0) return
|
||||
// TODO better manage if several (take most complete?)
|
||||
|
||||
let user = userList[0];
|
||||
this.euid.value = user.uid;
|
||||
this.firstname.value = user.given_name;
|
||||
this.lastname.value = user.family_name;
|
||||
this.email.value = user.email;
|
||||
this.changeRoleAvailable();
|
||||
}),
|
||||
(err) => {
|
||||
this.userSearcButton.loading = false;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
onTrackChange(event) {
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
if(this.trackSelect.value) {
|
||||
this.fillDomain(this.trackSelect.value);
|
||||
} else {
|
||||
this.domainSelect.empty();
|
||||
this.hideSelect(this.domainSelect);
|
||||
}
|
||||
|
||||
this.changeAddAvailable();
|
||||
}
|
||||
|
||||
onDomainChange(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.changeAddAvailable();
|
||||
}
|
||||
|
||||
fillDomain(track, domain) {
|
||||
let domains = track ? app.meta.toOptions(app.meta.getItem('accelerator-tracks', track).children, domain, true): [];
|
||||
this.domainSelect.empty();
|
||||
domains.forEach(item => this.domainSelect.el.append(item));
|
||||
}
|
||||
|
||||
DOMContentFocused() {
|
||||
|
||||
}
|
||||
|
||||
create(event) {
|
||||
if(this.form.validate() && this.validateDelegation()) {
|
||||
this.actions.find(o => o.role == 'add').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.create(this.form.value)
|
||||
.then( this.onSuccess.bind(this), this.onFailed.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
this.actions.find(o => o.role == 'add').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.commit(true);
|
||||
}
|
||||
|
||||
onFailed() {
|
||||
this.actions.find(o => o.role == 'add').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.abort();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
app.registerClass('BypassUserProfileDialog', BypassUserProfileDialog);
|
||||
Reference in New Issue
Block a user