unclean SPARC
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<style>
|
||||
.applicant-dashboard > header {
|
||||
background: url('/app/assets/images/cards/applicant-dashboard.jpg');
|
||||
background-position: center;
|
||||
background-size: cover !important;
|
||||
background-repeat: no-repeat;
|
||||
border-bottom: 4px solid white !important;
|
||||
}
|
||||
.applicant-dashboard .metrics {
|
||||
min-width: 320px;
|
||||
overflow: visible !important;
|
||||
padding: 0 var(--eicui-base-spacing-m) 0 0;
|
||||
margin: 0 var(--eicui-base-spacing-s) var(--eicui-base-spacing-s) 0;
|
||||
}
|
||||
.applicant-dashboard .list {
|
||||
display: grid;
|
||||
grid-gap: var(--eicui-base-spacing-m);
|
||||
grid-template-columns: repeat(auto-fill, 240px);
|
||||
}
|
||||
.applicant-dashboard .members .props {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.applicant-dashboard .company menu {
|
||||
max-height: 280px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.applicant-dashboard .company menu label span.pic {
|
||||
font-size: .75em;
|
||||
color: var(--eicui-base-color-grey-50);
|
||||
}
|
||||
|
||||
</style>
|
||||
<article eiccard media class="applicant-dashboard">
|
||||
<header>
|
||||
<h1>My EIC</h1>
|
||||
<h2>Innovation First</h2>
|
||||
</header>
|
||||
<section>
|
||||
<div class="cols-2 left">
|
||||
<article class="metrics">
|
||||
<div>
|
||||
<div class="cols-2 right middle center noflex">
|
||||
<label>My organisation profile</label>
|
||||
|
||||
<div eicdropdown class="company">
|
||||
<button eicbutton primary xsmall rounded icon="icon-exchange"></button>
|
||||
<menu eicmenu></menu>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div xlarge class="company-name"><b></b></div>
|
||||
<div small class="company-pic"></div>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
<article eiccard class="members" collapsable>
|
||||
<header>
|
||||
<h1>Members</h1>
|
||||
<h2></h2>
|
||||
</header>
|
||||
<section>
|
||||
<ul nonbulleted></ul>
|
||||
</section>
|
||||
<footer><div class="cols-1 right"><button eicbutton xsmall primary class="member-add">add member</button></div></footer>
|
||||
</article>
|
||||
<article eiccard danger class="activities">
|
||||
<header>
|
||||
<h1>TODO List</h1>
|
||||
</header>
|
||||
<section>
|
||||
<ul bulleted></ul>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard primary>
|
||||
<header>
|
||||
<h1>Upcoming events</h1>
|
||||
</header>
|
||||
<section>
|
||||
<ul nonbulleted>
|
||||
<li class="cols-2 left center"><span class="icon-calendar-failed" danger></span><span>No events foreseen</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
</article>
|
||||
</article>
|
||||
<div>
|
||||
<article eiccard class="fundings" collapsable>
|
||||
<header>
|
||||
<h1>My Fundings</h1>
|
||||
</header>
|
||||
<section>
|
||||
<div eicalert info closable>This list only contains projects you have access to. </div>
|
||||
<div class="list"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="coachings" collapsable collapsed>
|
||||
<header>
|
||||
<h1>My Coachings</h1>
|
||||
</header>
|
||||
<section>
|
||||
<div eicalert info closable>This list only contains coachings you have access to. </div>
|
||||
<div class="list"></div>
|
||||
</section>
|
||||
</article>
|
||||
<article eiccard class="proposals" collapsable>
|
||||
<header>
|
||||
<h1>My Proposals</h1>
|
||||
<h2></h2>
|
||||
</header>
|
||||
<section>
|
||||
<div eicalert info closable>This list only contains proposals you have access to. </div>
|
||||
<div class="list">
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-1 right">
|
||||
<div class="cols-2">
|
||||
<button eicbutton small primary class="proposal-search">search existing proposals</button>
|
||||
<button eicbutton small primary class="proposal-create">create a new proposal</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
@@ -0,0 +1,487 @@
|
||||
class ApplicantDashboardView extends EICDomContent {
|
||||
|
||||
constructor() { super(); }
|
||||
|
||||
version = 'r2';
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
|
||||
this.url = options.url;
|
||||
this.pic = options.pic;
|
||||
this.profile = options.profile || { admin: true };
|
||||
|
||||
for(let model in options.models) this[model] = options.models[model];
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.orgSelector = components.find(item => item.el.hasAttribute('eicdropdown'))
|
||||
|
||||
this.memberAdd = components.find(item => item.el.classList.contains('member-add'))
|
||||
this.memberAdd.addEventListener('click', this.onMemberAdd.bind(this))
|
||||
|
||||
this.proposalCreate = components.find(item => item.el.classList.contains('proposal-create'))
|
||||
this.proposalCreate.addEventListener('click', this.onProposalCreate.bind(this));
|
||||
this.proposalSearch = components.find(item => item.el.classList.contains('proposal-search'))
|
||||
this.proposalSearch.addEventListener('click', this.onProposalSearch.bind(this));
|
||||
|
||||
this.fillDashboard(this.pic);
|
||||
|
||||
// registering SP Form event triggering
|
||||
app.events.channel.addEventListener('proposal_updated', this.onProposalUpdate.bind(this))
|
||||
}
|
||||
|
||||
fillDashboard(pic) {
|
||||
|
||||
ui.lock();
|
||||
|
||||
this.pic = pic;
|
||||
this.todos = [];
|
||||
this.memberAdd.disabled = true;
|
||||
this.proposalCreate.disabled = true;
|
||||
|
||||
this.applicant.read(pic).then(this.fill.bind(this));
|
||||
this.myOrganisations.list().then(this.fillOrganisations.bind(this));
|
||||
this.members.list(pic).then(this.fillMembers.bind(this));
|
||||
this.proposals.list(pic).then(this.fillProposals.bind(this));
|
||||
this.coachings.list(pic).then(this.fillCoachings.bind(this));
|
||||
|
||||
ui.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the registered organisations menu
|
||||
* @param {*} results
|
||||
*/
|
||||
fillOrganisations(results) {
|
||||
let items = [];
|
||||
this.orgSelector.menu.clear();
|
||||
|
||||
results.sort((a,b) => a.legalName.toLowerCase() > b.legalName.toLowerCase() ? 1: -1)
|
||||
|
||||
for(let result of results) {
|
||||
items.push({
|
||||
id: result.pic,
|
||||
label: `${result.legalName} <span class="pic">(${result.pic})</span>`,
|
||||
icon: "icon-company",
|
||||
onclick: this.onCompanySwitch.bind(this,result.pic)
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: "Register another organisation",
|
||||
icon: "icon-plus",
|
||||
severity: "danger",
|
||||
onclick: this.onCompanyRegister.bind(this)
|
||||
});
|
||||
|
||||
this.orgSelector.menu.parse(items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the organisation information
|
||||
* @param {*} data
|
||||
*/
|
||||
fill(data) {
|
||||
this.find('.company-name b').innerHTML = data.legalName;
|
||||
this.find('.company-pic').innerHTML = data.pic;
|
||||
ui.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the organisation members list
|
||||
* @param {*} data
|
||||
*/
|
||||
fillMembers(data) {
|
||||
let members = this.find('.members ul');
|
||||
members.innerHTML = '';
|
||||
this.find('.members header h2').innerHTML= "";
|
||||
this.memberAdd.disabled = !this.members.hasPrivilege('add');
|
||||
|
||||
let memberMetrics = { active: 0, pending: 0 }
|
||||
this.todos = this.todos.filter(item => item.scope != 'members');
|
||||
|
||||
for(let item of data) {
|
||||
let actions = [];
|
||||
switch(item.status) {
|
||||
case 'active':
|
||||
break;
|
||||
case 'pending':
|
||||
actions.push(new Chip(null, {label:'pending', severity: 'warning', size: 'xsmall'}))
|
||||
break;
|
||||
}
|
||||
|
||||
memberMetrics[item.status]++;
|
||||
|
||||
let li = ui.create(`<li class="cols-2 right middle">
|
||||
<div>
|
||||
<div><b>${item.firstname} ${item.lastname}</b></div>
|
||||
<div xsmall class="props">
|
||||
${item.position}
|
||||
${item.email ? `<a href="mailto:${item.email}">${item.email}</a>`:''}
|
||||
${item.phone ? `<a href="tel:${item.phone}">${item.phone}</a>`:''}
|
||||
</div>
|
||||
</div>
|
||||
<span class="actions"></span>
|
||||
</li>`);
|
||||
|
||||
for(let action of actions)
|
||||
li.querySelector('.actions').append(action.el);
|
||||
|
||||
li.addEventListener('click', this.onMemberDetail.bind(this, item.uid))
|
||||
members.append(li);
|
||||
}
|
||||
|
||||
this.find('.members header h2').innerHTML = '';
|
||||
|
||||
if(memberMetrics.active > 0) {
|
||||
this.find('.members header h2').append((new Chip(null,{ label: `${memberMetrics.active} active`, size: 'xsmall', severity: 'success'})).el)
|
||||
}
|
||||
|
||||
if(memberMetrics.pending > 0) {
|
||||
this.find('.members header h2').append((new Chip(null,{ label: `${memberMetrics.pending} pending`, size: 'xsmall', severity: 'warning'})).el)
|
||||
this.todos.push(
|
||||
{ scope: 'members', message: `${memberMetrics.pending} member${memberMetrics.pending > 1 ? 's':''} requesting access` }
|
||||
)
|
||||
}
|
||||
|
||||
this.fillTodos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the proposals list
|
||||
* @param {*} data
|
||||
*/
|
||||
fillProposals(data) {
|
||||
/** Proposals list */
|
||||
|
||||
this.find('.proposals > header h2').innerHTML = "";
|
||||
let proposals = this.find('.proposals .list');
|
||||
proposals.innerHTML = '';
|
||||
let proposalMetrics = {submitted: 0, draft: 0, accessRequests: 0 };
|
||||
|
||||
this.todos = this.todos.filter(item => item.scope != 'proposals');
|
||||
|
||||
for(let item of data) {
|
||||
let card = ui.create(`<article eiccard>
|
||||
<header>
|
||||
<h1>${item.acronym || '(unnamed)'}</h1>
|
||||
<h2>
|
||||
${item.proposalNumber}
|
||||
${item.accessRequests > 0 ? `<span eicchip xsmall warning>${item.accessRequests} access request${item.accessRequests > 1 ? 's':''}</span>`:''}
|
||||
</h2>
|
||||
</header>
|
||||
<section>
|
||||
<span eicchip small primary>short ${item.version}</span>
|
||||
<span eicchip small info>${item.status}</span>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-2 center noflex"></div>
|
||||
</footer>
|
||||
</article>`);
|
||||
|
||||
let button = new Button(null, {label:'view', severity: 'primary', size: 'xsmall'});
|
||||
button.el.addEventListener('click', this.onProposalView.bind(this, item.proposalNumber, item.version))
|
||||
card.querySelector('footer div').append(button.el)
|
||||
|
||||
if(item.version != 'legacy' && item.status == 'draft') {
|
||||
button = new Button(null, {label:'edit', severity: 'primary', size: 'xsmall'});
|
||||
button.el.addEventListener('click', this.onProposalEdit.bind(this, item.proposalNumber, item.version))
|
||||
card.querySelector('footer div').append(button.el)
|
||||
}
|
||||
|
||||
proposalMetrics[item.status]++;
|
||||
proposalMetrics.accessRequests += item.accessRequests;
|
||||
proposals.append(card);
|
||||
}
|
||||
|
||||
if(proposalMetrics.draft > 0) {
|
||||
this.find('.proposals > header h2').append((new Chip(null,{ label: `${proposalMetrics.draft} draft`, size: 'xsmall', severity: 'warning'})).el)
|
||||
this.todos.push(
|
||||
{ scope: 'proposals', message: `${proposalMetrics.draft} draft proposal${proposalMetrics.draft > 1 ? 's':''} to be submitted` }
|
||||
)
|
||||
}
|
||||
|
||||
if(proposalMetrics.accessRequests > 0) {
|
||||
this.todos.push(
|
||||
{ scope: 'proposals', message: `${proposalMetrics.accessRequests} proposal access request${proposalMetrics.accessRequests > 1 ? 's':''} pending` }
|
||||
)
|
||||
}
|
||||
|
||||
this.proposalCreate.disabled = !this.proposals.hasPrivilege('create');
|
||||
|
||||
this.fillTodos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the proposals list
|
||||
* @param {*} data
|
||||
*/
|
||||
fillCoachings(data) {
|
||||
/** Proposals list */
|
||||
|
||||
// this.find('.coachings > header h2').innerHTML = "";
|
||||
let coachings = this.find('.coachings .list');
|
||||
coachings.innerHTML = '';
|
||||
let coachingMetrics = { };
|
||||
|
||||
this.todos = this.todos.filter(item => item.scope != 'coachings');
|
||||
|
||||
for(let item of data) {
|
||||
let card = ui.create(`<article eiccard>
|
||||
<header>
|
||||
<h1>${item.acronym || '(unnamed)'}</h1>
|
||||
<h2>
|
||||
${item.proposalNumber}
|
||||
</h2>
|
||||
</header>
|
||||
<section>
|
||||
<span eicchip small primary>short ${item.version}</span>
|
||||
<span eicchip small info>${item.status}</span>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="cols-2 center noflex"></div>
|
||||
</footer>
|
||||
</article>`);
|
||||
|
||||
let button = new Button(null, {label:'view', severity: 'primary', size: 'xsmall'});
|
||||
button.el.addEventListener('click', this.onCoachingView.bind(this, item.proposalNumber, item.version))
|
||||
card.querySelector('footer div').append(button.el)
|
||||
|
||||
coachings.append(card);
|
||||
}
|
||||
|
||||
// unused for now
|
||||
//this.fillTodos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TODO list
|
||||
*/
|
||||
fillTodos() {
|
||||
let activities = this.find('.activities ul')
|
||||
activities.innerHTML = '';
|
||||
if(this.todos.length > 0) {
|
||||
ui.show(activities);
|
||||
for(let activity of this.todos) {
|
||||
activities.append(ui.create(`<li>${activity.message}</li>`))
|
||||
}
|
||||
} else {
|
||||
ui.hide(activities);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for adding an organisation memnber
|
||||
* @param {*} event
|
||||
*/
|
||||
async onMemberAdd(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
let member = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'applicants/dialogs/ApplicantMemberDialog',
|
||||
{ title: 'Add a member' },
|
||||
{
|
||||
model: this.members,
|
||||
mode: 'create',
|
||||
pic: this.pic
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(member) {
|
||||
ui.growl.append('member added', 'success');
|
||||
this.members.list(this.pic).then(this.fillMembers.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for getting organisation member details
|
||||
* @param {*} event
|
||||
*/
|
||||
async onMemberDetail(uid, event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
let member = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'applicants/dialogs/ApplicantMemberDialog',
|
||||
{ title: 'edit a member' },
|
||||
{
|
||||
model: this.members,
|
||||
mode: this.members.hasPrivilege('update') ? 'edit': 'read',
|
||||
pic: this.pic,
|
||||
uid: uid
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(member) {
|
||||
if(member.status == 'removed')
|
||||
ui.growl.append('member removed', 'danger');
|
||||
else
|
||||
ui.growl.append('member updated', 'success');
|
||||
|
||||
this.members.list(this.pic).then(this.fillMembers.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for calling short proposal form on edit
|
||||
* @param {*} number
|
||||
* @param {*} version
|
||||
* @param {*} event
|
||||
*/
|
||||
onProposalEdit(number, version, event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
app.Router.route(`/organisations/${this.pic}/proposals/${number}`, { mode: 'edit', version: version});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} number
|
||||
* @param {*} version
|
||||
* @param {*} event
|
||||
*/
|
||||
onProposalView(number, version, event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
app.Router.route(`/organisations/${this.pic}/proposals/${number}`, { mode: 'read', version: version});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} number
|
||||
* @param {*} version
|
||||
* @param {*} event
|
||||
*/
|
||||
onProposalCreate(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.proposalCreate.loading = true;
|
||||
|
||||
this.proposals.create(this.pic)
|
||||
.then( async payload => {
|
||||
let number = payload.proposalNumber;
|
||||
this.proposalCreate.loading = false;
|
||||
this.proposals.list(this.pic).then(this.fillProposals.bind(this));
|
||||
app.Router.route(`/organisations/${this.pic}/proposals/${number}`, { mode: 'edit'});
|
||||
},
|
||||
(err)=>{ this.proposalCreate.loading = false })
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for getting organisation member details
|
||||
* @param {*} event
|
||||
*/
|
||||
async onProposalSearch(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
await this.openDialog(
|
||||
await this.loadContent(
|
||||
'applicants/dialogs/ApplicantProposalSearchDialog',
|
||||
{ title: 'search for existing proposals' },
|
||||
{
|
||||
model: this.proposals,
|
||||
pic: this.pic
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
onProposalUpdate(event) {
|
||||
this.proposals.list(this.pic).then(this.fillProposals.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} number
|
||||
* @param {*} version
|
||||
* @param {*} event
|
||||
*/
|
||||
onCoachingView(number, version, event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.coachings.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for changing organisation profile
|
||||
* @param {*} pic
|
||||
* @param {*} event
|
||||
*/
|
||||
onCompanySwitch(pic, event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.orgSelector.collapse();
|
||||
ui.lock();
|
||||
|
||||
app.User.getBusinessPermissions([
|
||||
'/organisations',
|
||||
'/organisations/' + pic,
|
||||
'/organisations/' + pic + '/members',
|
||||
'/organisations/' + pic + '/proposals'
|
||||
], 'Org_Member')
|
||||
.then(async payload => {
|
||||
ui.unlock();
|
||||
|
||||
if(payload['/organisations/' + pic].permissions.includes('read')) {
|
||||
|
||||
this.applicant.setPrivileges('/organisations/{pic}', payload['/organisations/' + pic].permissions);
|
||||
this.myOrganisations.setPrivileges('/organisations', payload['/organisations'].permissions);
|
||||
this.members.setPrivileges('/organisations/{pic}/members', payload['/organisations/' + pic + '/members'].permissions);
|
||||
this.proposals.setPrivileges('/organisations/{pic}/proposals', payload['/organisations/' + pic + '/proposals'].permissions);
|
||||
|
||||
this.fillDashboard(pic);
|
||||
this._controller.changeUrl(this, this.url.replace(':pic', pic))
|
||||
|
||||
} else {
|
||||
|
||||
ui.unlock();
|
||||
ui.growl.append('You don\'t have access to this organisation', 'danger' );
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for registering new organisation
|
||||
* Using the onboarding as applicant scenario
|
||||
* @param {*} event
|
||||
*/
|
||||
async onCompanyRegister(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.orgSelector.collapse();
|
||||
|
||||
app.User.getBusinessPermissions(['/organisations'])
|
||||
.then(async payload => {
|
||||
let membership = await this.openDialog(
|
||||
await this.loadContent(
|
||||
'common/onboarding/dialogs/onboardingApplicantDialog',
|
||||
{ title: 'Register to an organisation' },
|
||||
{
|
||||
models: { user: new onboardingUserModel(payload['/organisations'].permissions) },
|
||||
cancelLabel: 'cancel'
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if(membership) {
|
||||
ui.growl.append('membership request sent', 'success');
|
||||
}
|
||||
},
|
||||
() => {
|
||||
ui.growl.append('Sorry, you don\'t have access to this feature', 'danger');
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('ApplicantDashboardView', ApplicantDashboardView);
|
||||
@@ -0,0 +1,48 @@
|
||||
<div class="applicant-member-dialog">
|
||||
<div eicalert class="status"></div>
|
||||
<div class="lookup">
|
||||
<label>Please identify your member</label>
|
||||
<input eicinput type="search" data-path="" name="search" value="" placeholder="Enter member's EU Login ID or email and press enter" />
|
||||
</div>
|
||||
<div class="member-form">
|
||||
<div class="cols-3">
|
||||
<input eicinput type="hidden" name="uid" data-type="ignore" value="" class="required" />
|
||||
<div>
|
||||
<label>Title</label>
|
||||
<select eicselect class="required" name="gender" data-path=""></select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Firstname</label>
|
||||
<input eicinput disabled value="" class="required" data-path="" name="firstname" data-type="ignore" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Lastname</label>
|
||||
<input eicinput disabled value="" class="required" data-path="" name="lastname" data-type="ignore" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="cols-2">
|
||||
<div>
|
||||
<label>Email</label>
|
||||
<input eicinput disabled type="email" value="" data-path="" name="email" data-type="ignore" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Phone</label>
|
||||
<input eicinput type="tel" value="" class="" data-path="" name="phone"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>Position</label>
|
||||
<select eicselect lookup class="required" class="required" name="position" data-path=""></select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Rights</label>
|
||||
<select eicselect data-path="" name="admin" data-type="boolean" class="required">
|
||||
<option value="false"><h5>User Level</h5> Can view organisation information, request access or be invited as contributor to proposal(s)</option>
|
||||
<option value="true"><h5>Admin Level</h5> Can manage organisation members (add, remove and update) and handle membership requests</option>
|
||||
</select>
|
||||
</div>
|
||||
<div eicalert danger class="confirmation">
|
||||
<input eiccheckbox type="checkbox" name="confirmDelete" data-type="ignore" label="Yes, I want to remove this member from my organisation" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,268 @@
|
||||
class ApplicantMemberDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
role: 'cancel'
|
||||
},
|
||||
{
|
||||
label: 'remove',
|
||||
onclick: this.remove.bind(this),
|
||||
severity: 'danger',
|
||||
role: 'delete'
|
||||
},
|
||||
{
|
||||
label: 'add',
|
||||
onclick: this.create.bind(this),
|
||||
severity: 'primary',
|
||||
role: 'create'
|
||||
},
|
||||
{
|
||||
label: 'accept',
|
||||
onclick: this.accept.bind(this),
|
||||
severity: 'success',
|
||||
role: 'accept'
|
||||
},
|
||||
{
|
||||
label: 'reject',
|
||||
onclick: this.revoke.bind(this),
|
||||
severity: 'danger',
|
||||
role: 'reject'
|
||||
},
|
||||
{
|
||||
label: 'update',
|
||||
onclick: this.update.bind(this),
|
||||
severity: 'primary',
|
||||
role: 'update'
|
||||
},
|
||||
]
|
||||
|
||||
statuses = {
|
||||
active: {
|
||||
severity: 'success',
|
||||
label: 'This user is part of your organisation'
|
||||
},
|
||||
pending: {
|
||||
severity: 'warning',
|
||||
label: 'This user ask to be part of your organisation'
|
||||
}
|
||||
}
|
||||
async DOMContentLoaded(options) {
|
||||
|
||||
this.mode = options.mode ? options.mode: 'read';
|
||||
|
||||
app.meta.add('organisation-functions', app.Assets.Store.json['organisation-functions']);
|
||||
app.meta.add('organisation-genders', app.Assets.Store.json['organisation-genders']);
|
||||
|
||||
this.model = options.model;
|
||||
this.pic = options.pic;
|
||||
|
||||
let components = ui.eicfy(this.el);
|
||||
|
||||
this.uid = components.find(component => component.el.name == 'uid');
|
||||
this.uid.value = options.uid || '';
|
||||
this.firstname = components.find(component => component.el.name == 'firstname');
|
||||
this.lastname = components.find(component => component.el.name == 'lastname');
|
||||
this.email = components.find(component => component.el.name == 'email');
|
||||
this.phone = components.find(component => component.el.name == 'phone');
|
||||
this.gender = components.find(component => component.el.name == 'gender');
|
||||
this.position = components.find(component => component.el.name == 'position');
|
||||
this.admin = components.find(component => component.el.name == 'admin');
|
||||
this.confirmDelete = components.find(component => component.el.name == 'confirmDelete');
|
||||
this.confirmation = this.find('.confirmation');
|
||||
this.lookup = this.find('.lookup');
|
||||
this.status = this.find('.status');
|
||||
|
||||
ui.hide(this.status);
|
||||
ui.hide(this.lookup);
|
||||
ui.hide(this.confirmation);
|
||||
|
||||
app.meta.toOptions('organisation-genders', null, true).forEach(item => this.gender.el.append(item));
|
||||
app.meta.toOptions('organisation-functions', null, true).forEach(item => this.position.el.append(item));
|
||||
|
||||
this.form = new Form(this.find('.member-form'));
|
||||
|
||||
this.searchInput = components.find(component => component.el.name == 'search');
|
||||
this.searchInput.onQuery = this.onUserSearch.bind(this);
|
||||
}
|
||||
|
||||
DOMContentFocused() {
|
||||
this.actions.find(o => o.role == 'create').button.hide();
|
||||
this.actions.find(o => o.role == 'delete').button.hide();
|
||||
this.actions.find(o => o.role == 'update').button.hide();
|
||||
this.actions.find(o => o.role == 'reject').button.hide();
|
||||
this.actions.find(o => o.role == 'accept').button.hide();
|
||||
|
||||
if(this.mode != 'create') {
|
||||
this.model.read(this.pic, this.uid.value)
|
||||
.then( this.fill.bind(this));
|
||||
} else {
|
||||
this.setupMode(this.mode);
|
||||
}
|
||||
}
|
||||
|
||||
fill(data) {
|
||||
this.uid.value = data.uid;
|
||||
this.firstname.value = data.firstname;
|
||||
this.lastname.value = data.lastname;
|
||||
this.email.value = data.email;
|
||||
this.phone.value = data.phone;
|
||||
this.gender.value = data.gender;
|
||||
this.position.value = data.position;
|
||||
this.admin.value = data.admin.toString();
|
||||
|
||||
this.gender.disabled = false;
|
||||
this.phone.disabled = false;
|
||||
this.position.disabled = false;
|
||||
this.admin.disabled = false;
|
||||
|
||||
let status = this.statuses[data.status];
|
||||
this.status.setAttribute(status.severity, '');
|
||||
this.status.innerHTML = status.label;
|
||||
|
||||
// switch to review mode if user is pending
|
||||
if(this.mode == 'edit' && data.status == 'pending') this.mode = 'review';
|
||||
|
||||
this.setupMode(this.mode)
|
||||
}
|
||||
|
||||
setupMode(mode) {
|
||||
|
||||
switch(this.mode) {
|
||||
case 'read':
|
||||
ui.show(this.status);
|
||||
this.gender.disabled = true;
|
||||
this.phone.disabled = true;
|
||||
this.position.disabled = true;
|
||||
this.admin.disabled = true;
|
||||
|
||||
break;
|
||||
case 'edit':
|
||||
ui.show(this.status);
|
||||
if(this.uid.value != app.User.identity.uuid) this.actions.find(o => o.role == 'delete').button.show();
|
||||
this.actions.find(o => o.role == 'update').button.show();
|
||||
break;
|
||||
case 'review':
|
||||
ui.show(this.status);
|
||||
this.actions.find(o => o.role == 'reject').button.show();
|
||||
this.actions.find(o => o.role == 'accept').button.show();
|
||||
break;
|
||||
case 'create':
|
||||
ui.show(this.lookup);
|
||||
ui.hide(this.confirmation);
|
||||
ui.hide(this.status);
|
||||
this.actions.find(o => o.role == 'create').button.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onUserSearch() {
|
||||
this.searchInput.loading = true;
|
||||
|
||||
this.uid.value = '';
|
||||
this.firstname.value = '';
|
||||
this.lastname.value = '';
|
||||
this.email.value = '';
|
||||
this.phone.value = '';
|
||||
this.position.clear();
|
||||
this.gender.clear();
|
||||
|
||||
this.model.search(this.searchInput.value).then(
|
||||
(userList => {
|
||||
this.searchInput.loading = false;
|
||||
if(userList.length == 0) return
|
||||
// TODO better manage if several (take most complete?)
|
||||
|
||||
let user = userList[0];
|
||||
this.uid.value = user.uid;
|
||||
this.firstname.value = user.given_name;
|
||||
this.lastname.value = user.family_name;
|
||||
this.email.value = user.email;
|
||||
}),
|
||||
(err) => {
|
||||
if(err.code!=404) { // 404 is just unknown user, the display message as warning (via EIC-model) is enough then.
|
||||
ui.growl.append('User lookup service is unavailable', 'danger')
|
||||
}
|
||||
this.searchInput.loading = false;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
create(event) {
|
||||
if(this.form.validate()) {
|
||||
this.actions.find(o => o.role == 'create').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.add(this.pic, this.uid.value, this.form.value)
|
||||
.then( this.onSuccess.bind(this), this.onFailed.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
update(event) {
|
||||
if(this.form.validate()) {
|
||||
this.actions.find(o => o.role == 'update').button.loading = true;
|
||||
this.actions.find(o => o.role == 'delete').button.disabled = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.update( this.pic, this.uid.value, this.form.value )
|
||||
.then( this.onSuccess.bind(this), this.onFailed.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
remove(event) {
|
||||
|
||||
ui.show(this.confirmation);
|
||||
|
||||
if(this.confirmDelete.el.checked) {
|
||||
this.actions.find(o => o.role == 'update').button.disabled = true;
|
||||
this.actions.find(o => o.role == 'delete').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
console.log(this.pic, this.uid.value)
|
||||
this.model.revoke( this.pic, this.uid.value)
|
||||
.then( this.onSuccess.bind(this), this.onFailed.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
revoke(event) {
|
||||
|
||||
ui.hide(this.confirmation);
|
||||
|
||||
this.actions.find(o => o.role == 'accept').button.disabled = true;
|
||||
this.actions.find(o => o.role == 'reject').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.revoke( this.pic, this.uid.value)
|
||||
.then( this.onSuccess.bind(this), this.onFailed.bind(this))
|
||||
}
|
||||
|
||||
accept(event) {
|
||||
this.actions.find(o => o.role == 'reject').button.disabled = true;
|
||||
this.actions.find(o => o.role == 'accept').button.loading = true;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = true;
|
||||
this.model.grant( this.pic, this.uid.value, this.form.value)
|
||||
.then( this.onSuccess.bind(this), this.onFailed.bind(this))
|
||||
}
|
||||
|
||||
onSuccess() {
|
||||
this.actions.find(o => o.role == 'create').button.loading = false;
|
||||
this.actions.find(o => o.role == 'update').button.loading = false;
|
||||
this.actions.find(o => o.role == 'delete').button.loading = false;
|
||||
this.actions.find(o => o.role == 'accept').button.loading = false;
|
||||
this.actions.find(o => o.role == 'reject').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.commit(true);
|
||||
}
|
||||
|
||||
onFailed() {
|
||||
this.actions.find(o => o.role == 'create').button.loading = false;
|
||||
this.actions.find(o => o.role == 'update').button.loading = false;
|
||||
this.actions.find(o => o.role == 'delete').button.loading = false;
|
||||
this.actions.find(o => o.role == 'accept').button.loading = false;
|
||||
this.actions.find(o => o.role == 'reject').button.loading = false;
|
||||
this.actions.find(o => o.role == 'cancel').button.disabled = false;
|
||||
this.abort();
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('ApplicantMemberDialog', ApplicantMemberDialog);
|
||||
@@ -0,0 +1,22 @@
|
||||
<style>
|
||||
.applicant-proposal-search {
|
||||
min-width: 40vw;
|
||||
}
|
||||
.applicant-proposal-search .list .row {
|
||||
grid-template-columns: 120px auto 120px 120px 200px;
|
||||
}
|
||||
.applicant-proposal-search .list .cell:nth-child(2),
|
||||
.applicant-proposal-search .list .cell:nth-child(4),
|
||||
.applicant-proposal-search .list .cell:nth-child(5),
|
||||
.applicant-proposal-search .list .cell:nth-child(6) {
|
||||
text-align:center
|
||||
}
|
||||
</style>
|
||||
<div class="applicant-proposal-search">
|
||||
<div class="search-form">
|
||||
<input eicinput type="search" name="search" placeholder="search on proposal number or acronym" />
|
||||
</div>
|
||||
<div class="search-results">
|
||||
<div eicdatagrid class="list"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,82 @@
|
||||
class ApplicantProposalSearchDialog extends EICDialogContent {
|
||||
|
||||
actions = [
|
||||
{
|
||||
label: 'cancel',
|
||||
onclick: this.cancel.bind(this),
|
||||
severity: 'secondary',
|
||||
role: 'close'
|
||||
}
|
||||
]
|
||||
|
||||
DOMContentLoaded(options) {
|
||||
this.components = ui.eicfy(this.el);
|
||||
|
||||
this.model = options.model;
|
||||
this.pic = options.pic;
|
||||
|
||||
this.searchInput = this.components.find(component => component.el.name == 'search');
|
||||
this.searchInput.onQuery = this.search.bind(this);
|
||||
this.form = new Form(this.find('.search-form'));
|
||||
|
||||
this.list = new DataGrid(this.find('.list'), {
|
||||
height: '240px',
|
||||
headers: [
|
||||
{label: 'number'},
|
||||
{label: 'acronym'},
|
||||
{label: 'version'},
|
||||
{label: 'status'},
|
||||
]
|
||||
});
|
||||
|
||||
this.search();
|
||||
}
|
||||
|
||||
search() {
|
||||
this.searchInput.loading = true;
|
||||
this.model.search(this.pic, this.form.value)
|
||||
.then(this.fill.bind(this));
|
||||
}
|
||||
|
||||
fill(data) {
|
||||
this.searchInput.loading = false;
|
||||
this.list.clear();
|
||||
|
||||
for(let item of data) {
|
||||
|
||||
|
||||
let row = this.list.addRow(item, [ item.proposalNumber, item.acronym, item.version, item.status ])
|
||||
|
||||
switch(item.accessStatus) {
|
||||
case 'active':
|
||||
row.querySelector('.cell:last-child').append(ui.create(`<span eicchip small success>contributing</span>`));
|
||||
break;
|
||||
case 'pending':
|
||||
row.querySelector('.cell:last-child').append(ui.create(`<span eicchip small warning>request pending</span>`));
|
||||
break;
|
||||
default:
|
||||
let button = new Button(null, {label: 'request access', size: 'small', severity: 'primary'});
|
||||
button.el.dataset.number = item.proposalNumber;
|
||||
button.addEventListener('click', this.requestAccess.bind(this));
|
||||
row.querySelector('.cell:last-child').append(button.el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async requestAccess(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
let button = new Button(event.currentTarget);
|
||||
button.loading = true;
|
||||
|
||||
let number = button.el.dataset.number;
|
||||
this.model.apply(this.pic, number, app.User.identity.uuid)
|
||||
.then(async payload => {
|
||||
button.loading = false
|
||||
this.search();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
app.registerClass('ApplicantProposalSearchDialog',ApplicantProposalSearchDialog);
|
||||
Reference in New Issue
Block a user