Skip to content

Commit

Permalink
feat(sessionsRegistrations): download spreadsheet
Browse files Browse the repository at this point in the history
  • Loading branch information
uatisdeproblem committed Mar 23, 2024
1 parent 803dcae commit c93c1cb
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 24 deletions.
53 changes: 45 additions & 8 deletions back-end/src/handlers/registrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { DynamoDB, HandledError, ResourceController } from 'idea-aws';

import { Session } from '../models/session.model';
import { SessionRegistration } from '../models/sessionRegistration.model';
import { SessionRegistration, SessionRegistrationExportable } from '../models/sessionRegistration.model';
import { User } from '../models/user.model';
import { Configurations } from '../models/configurations.model';

Expand Down Expand Up @@ -67,9 +67,16 @@ class SessionRegistrationsRC extends ResourceController {
}
}

protected async getResources(): Promise<SessionRegistration[]> {
if (this.queryParams.sessionId) return this.getSessionRegistrations(this.queryParams.sessionId);
else return await this.getUsersRegistrations(this.principalId);
protected async getResources(): Promise<SessionRegistration[] | SessionRegistrationExportable[]> {
if (this.queryParams.sessionId) {
if (this.queryParams.export && this.user.permissions.canManageContents)
return await this.getExportableSessionRegistrations(this.queryParams.sessionId);
else return this.getRegistrationsOfSessionById(this.queryParams.sessionId);
} else {
if (this.queryParams.export && this.user.permissions.canManageContents)
return await this.getExportableSessionRegistrations();
else return await this.getRegistrationsOfUserById(this.principalId);
}
}

protected async postResource(): Promise<any> {
Expand Down Expand Up @@ -118,7 +125,7 @@ class SessionRegistrationsRC extends ResourceController {

private async putSafeResource(): Promise<SessionRegistration> {
const { sessionId, userId } = this.registration;
const session = new Session(await ddb.get({ TableName: DDB_TABLES.sessions, Key: { sessionId } }));
const session = await this.getSessionById(sessionId);
const isValid = await this.validateRegistration(session, userId);

if (!isValid) throw new HandledError("User can't sign up for this session!");
Expand Down Expand Up @@ -151,7 +158,7 @@ class SessionRegistrationsRC extends ResourceController {
if (!session.requiresRegistration) throw new HandledError("User can't sign up for this session!");
if (session.isFull()) throw new HandledError('Session is full! Refresh your page.');

const userRegistrations = await this.getUsersRegistrations(userId);
const userRegistrations = await this.getRegistrationsOfUserById(userId);
if (!userRegistrations.length) return true;

const sessions = (
Expand Down Expand Up @@ -185,7 +192,7 @@ class SessionRegistrationsRC extends ResourceController {
return true;
}

private async getUsersRegistrations(userId: string): Promise<SessionRegistration[]> {
private async getRegistrationsOfUserById(userId: string): Promise<SessionRegistration[]> {
try {
const registrationsOfUser: SessionRegistration[] = await ddb.query({
TableName: DDB_TABLES.registrations,
Expand All @@ -198,7 +205,7 @@ class SessionRegistrationsRC extends ResourceController {
throw new HandledError('Could not load registrations for this user');
}
}
private async getSessionRegistrations(sessionId: string): Promise<SessionRegistration[]> {
private async getRegistrationsOfSessionById(sessionId: string): Promise<SessionRegistration[]> {
try {
const registrationsOfSession: SessionRegistration[] = await ddb.query({
TableName: DDB_TABLES.registrations,
Expand All @@ -210,4 +217,34 @@ class SessionRegistrationsRC extends ResourceController {
throw new HandledError('Could not load registrations for this session');
}
}
private async getSessionById(sessionId: string): Promise<Session> {
try {
return new Session(await ddb.get({ TableName: DDB_TABLES.sessions, Key: { sessionId } }));
} catch (err) {
throw new HandledError('Session not found');
}
}
private async getExportableSessionRegistrations(sessionId?: string): Promise<SessionRegistrationExportable[]> {
let sessions: Session[], registrations: SessionRegistration[];

if (sessionId) {
sessions = [await this.getSessionById(sessionId)];
registrations = await this.getRegistrationsOfSessionById(sessionId);
} else {
sessions = (await ddb.scan({ TableName: DDB_TABLES.sessions })).map(x => new Session(x));
registrations = (await ddb.scan({ TableName: DDB_TABLES.registrations })).map(x => new SessionRegistration(x));
}

const list: SessionRegistrationExportable[] = [];
sessions.map(session =>
list.push(
...SessionRegistration.export(
session,
registrations.filter(r => r.sessionId === session.sessionId)
)
)
);

return list;
}
}
36 changes: 36 additions & 0 deletions back-end/src/models/sessionRegistration.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Resource } from 'idea-toolbox';

import { Session } from './session.model';

export class SessionRegistration extends Resource {
/**
* The session ID.
Expand Down Expand Up @@ -30,4 +32,38 @@ export class SessionRegistration extends Resource {
this.name = this.clean(x.name, String);
if (x.sectionCountry) this.sectionCountry = this.clean(x.sectionCountry, String);
}

/**
* Get the exportable version of the list of registrations for a session.
*/
static export(session: Session, registrations: SessionRegistration[]): SessionRegistrationExportable[] {
if (!registrations.length)
return [
{
Session: session.name,
Code: session.code ?? null,
Type: session.type,
Participant: null,
'ESN Country': null
}
];
return registrations.map(r => ({
Session: session.name,
Code: session.code ?? null,
Type: session.type,
Participant: r.name,
'ESN Country': r.sectionCountry ?? null
}));
}
}

/**
* An exportable version of a session registration.
*/
export interface SessionRegistrationExportable {
Session: string;
Code: string | null;
Type: string;
Participant: string | null;
'ESN Country': string | null;
}
5 changes: 5 additions & 0 deletions back-end/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,11 @@ paths:
in: query
schema:
type: string
- name: export
in: query
description: If set, returns an exportable version of the registrations; it requires to be content manager
schema:
type: boolean
responses:
200:
$ref: '#/components/responses/Registrations'
Expand Down
8 changes: 8 additions & 0 deletions front-end/src/app/tabs/manage/manage.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ <h2>{{ 'MANAGE.CONTENTS' | translate }}</h2>
<ion-item button detail (click)="addSession()">
<ion-icon slot="start" icon="flame-outline"></ion-icon>
<ion-label>{{ 'MANAGE.SESSIONS' | translate }}</ion-label>
<ion-button
slot="end"
fill="clear"
[title]="'MANAGE.DOWNLOAD_SESSIONS_REGISTRATIONS' | translate"
(click)="downloadSessionsRegistrations($event)"
>
<ion-icon icon="download" slot="icon-only" />
</ion-button>
</ion-item>
</ng-container>
<ng-container *ngIf="app.user.permissions.isAdmin">
Expand Down
16 changes: 15 additions & 1 deletion front-end/src/app/tabs/manage/manage.page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { IDEALoadingService, IDEAMessageService } from '@idea-ionic/common';
import { IDEALoadingService, IDEAMessageService, IDEATranslationsService } from '@idea-ionic/common';

import { EmailTemplateComponent } from './configurations/emailTemplate/emailTemplate.component';
import { ManageUsefulLinkStandaloneComponent } from '@app/common/usefulLinks/manageUsefulLink.component';
Expand All @@ -12,6 +12,7 @@ import { ManageSessionComponent } from '../sessions/manageSession.component';

import { AppService } from '@app/app.service';
import { UsefulLinksService } from '@app/common/usefulLinks/usefulLinks.service';
import { SessionRegistrationsService } from '../sessionRegistrations/sessionRegistrations.service';

import { EmailTemplates, DocumentTemplates } from '@models/configurations.model';
import { UsefulLink } from '@models/usefulLink.model';
Expand All @@ -36,7 +37,9 @@ export class ManagePage {
private modalCtrl: ModalController,
private loading: IDEALoadingService,
private message: IDEAMessageService,
private t: IDEATranslationsService,
private _usefulLinks: UsefulLinksService,
private _sessionRegistrations: SessionRegistrationsService,
public app: AppService
) {}
async ionViewWillEnter(): Promise<void> {
Expand Down Expand Up @@ -122,4 +125,15 @@ export class ManagePage {
});
await modal.present();
}
async downloadSessionsRegistrations(event?: Event): Promise<void> {
if (event) event.stopPropagation();
try {
await this.loading.show();
await this._sessionRegistrations.downloadSpreadsheet(this.t._('SESSIONS.SESSION_REGISTRATIONS'));
} catch (error) {
this.message.error('COMMON.OPERATION_FAILED');
} finally {
this.loading.hide();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Injectable } from '@angular/core';
import { WorkBook, utils, writeFile } from 'xlsx';
import { IDEAApiService } from '@idea-ionic/common';

import { SessionRegistration } from '@models/sessionRegistration.model';
import { Session } from '@models/session.model';
import { SessionRegistration, SessionRegistrationExportable } from '@models/sessionRegistration.model';

@Injectable({ providedIn: 'root' })
export class SessionRegistrationsService {
Expand Down Expand Up @@ -35,4 +37,17 @@ export class SessionRegistrationsService {
async delete(registration: SessionRegistration): Promise<void> {
await this.api.deleteResource(['registrations', registration.sessionId]);
}

/**
* Download a spreadsheet containing the sessions registrations selected.
*/
async downloadSpreadsheet(title: string, session?: Session): Promise<void> {
const params: any = { export: true };
if (session) params.sessionId = session.sessionId;
const list: SessionRegistrationExportable[] = await this.api.getResource('registrations', { params });

const workbook: WorkBook = { SheetNames: [], Sheets: {}, Props: { Title: title } };
utils.book_append_sheet(workbook, utils.json_to_sheet(list), '1');
writeFile(workbook, title.concat('.xlsx'));
}
}
21 changes: 15 additions & 6 deletions front-end/src/app/tabs/sessions/session.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,22 @@
<ion-icon slot="icon-only" name="arrow-back" />
</ion-button>
</ion-buttons>
<ion-buttons slot="end" *ngIf="session && app.user.permissions.canManageContents">
<ng-container>
<ion-button color="ESNgreen" (click)="manageSession()">
<ion-icon slot="icon-only" icon="hammer"></ion-icon>
</ion-button>
</ng-container>
@if(session && app.user.permissions.canManageContents) {
<ion-buttons slot="end">
<ion-button color="ESNgreen" (click)="manageSession()">
<ion-icon slot="icon-only" icon="hammer"></ion-icon>
</ion-button>
<ion-button
slot="end"
fill="clear"
color="ESNgreen"
[title]="'SESSIONS.DOWNLOAD_REGISTRATIONS' | translate"
(click)="downloadSessionsRegistrations()"
>
<ion-icon icon="download" slot="icon-only" />
</ion-button>
</ion-buttons>
}
</app-session-detail>
</div>
</ion-content>
13 changes: 13 additions & 0 deletions front-end/src/app/tabs/sessions/session.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IDEALoadingService, IDEAMessageService, IDEATranslationsService } from
import { ManageSessionComponent } from './manageSession.component';

import { SessionsService } from './sessions.service';
import { SessionRegistrationsService } from '../sessionRegistrations/sessionRegistrations.service';

import { Session } from '@models/session.model';
import { ActivatedRoute } from '@angular/router';
Expand All @@ -28,6 +29,7 @@ export class SessionPage implements OnInit {
private loading: IDEALoadingService,
private message: IDEAMessageService,
public _sessions: SessionsService,
private _sessionRegistrations: SessionRegistrationsService,
public t: IDEATranslationsService,
public app: AppService
) {}
Expand Down Expand Up @@ -128,4 +130,15 @@ export class SessionPage implements OnInit {
});
await modal.present();
}

async downloadSessionsRegistrations(): Promise<void> {
try {
await this.loading.show();
await this._sessionRegistrations.downloadSpreadsheet(this.t._('SESSIONS.SESSION_REGISTRATIONS'), this.session);
} catch (error) {
this.message.error('COMMON.OPERATION_FAILED');
} finally {
this.loading.hide();
}
}
}
21 changes: 15 additions & 6 deletions front-end/src/app/tabs/sessions/sessions.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,22 @@
(favorite)="toggleFavorite($event, selectedSession)"
(register)="toggleRegister($event, selectedSession)"
>
<ion-buttons slot="end" *ngIf="selectedSession && app.user.permissions.canManageContents">
<ng-container>
<ion-button color="ESNgreen" (click)="manageSession()">
<ion-icon slot="icon-only" icon="hammer"></ion-icon>
</ion-button>
</ng-container>
@if(selectedSession && app.user.permissions.canManageContents) {
<ion-buttons slot="end">
<ion-button color="ESNgreen" (click)="manageSession()">
<ion-icon slot="icon-only" icon="hammer"></ion-icon>
</ion-button>
<ion-button
slot="end"
fill="clear"
color="ESNgreen"
[title]="'SESSIONS.DOWNLOAD_REGISTRATIONS' | translate"
(click)="downloadSessionsRegistrations()"
>
<ion-icon icon="download" slot="icon-only" />
</ion-button>
</ion-buttons>
}
</app-session-detail>
</ion-col>
</ion-row>
Expand Down
16 changes: 16 additions & 0 deletions front-end/src/app/tabs/sessions/sessions.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IDEALoadingService, IDEAMessageService, IDEATranslationsService } from
import { ManageSessionComponent } from './manageSession.component';

import { SessionsService } from './sessions.service';
import { SessionRegistrationsService } from '../sessionRegistrations/sessionRegistrations.service';

import { Session } from '@models/session.model';

Expand All @@ -32,6 +33,7 @@ export class SessionsPage {
private loading: IDEALoadingService,
private message: IDEAMessageService,
public _sessions: SessionsService,
private _sessionRegistrations: SessionRegistrationsService,
public t: IDEATranslationsService,
public app: AppService
) {}
Expand Down Expand Up @@ -150,4 +152,18 @@ export class SessionsPage {
});
await modal.present();
}

async downloadSessionsRegistrations(): Promise<void> {
try {
await this.loading.show();
await this._sessionRegistrations.downloadSpreadsheet(
this.t._('SESSIONS.SESSION_REGISTRATIONS'),
this.selectedSession
);
} catch (error) {
this.message.error('COMMON.OPERATION_FAILED');
} finally {
this.loading.hide();
}
}
}
7 changes: 5 additions & 2 deletions front-end/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@
"OTHER_CONFIGURATIONS": "Other configurations",
"LIST_OF_ESN_COUNTRIES": "List of current ESN countries",
"USEFUL_LINKS": "Useful links",
"USEFUL_LINKS_I": "Manage the links you want to make available to all users for quick access."
"USEFUL_LINKS_I": "Manage the links you want to make available to all users for quick access.",
"DOWNLOAD_SESSIONS_REGISTRATIONS": "Download a spreadsheet with all the sessions registrations"
},
"STRIPE": {
"BEFORE_YOU_PROCEED": "Before you proceed",
Expand Down Expand Up @@ -437,6 +438,8 @@
"SPEAKERS": "Speakers",
"NO_SPEAKERS": "No speakers yet...",
"ADD_SPEAKER": "Add speaker",
"ROOM": "Room"
"ROOM": "Room",
"DOWNLOAD_REGISTRATIONS": "Download a spreadsheet with all the session registrations",
"SESSION_REGISTRATIONS": "Session registrations"
}
}

0 comments on commit c93c1cb

Please sign in to comment.