add CSV for motions

This does the csv export for the user list

emanuelschuetze:
Uses right order and translatable labels

jsaalfeld:
fixing some documentation, rebasing to master and fixing strings
and coding style.

fixing #4035
This commit is contained in:
Fadi Abbud 2018-11-21 12:30:36 +01:00 committed by Jochen Saalfeld
parent 14b2d33d00
commit 021cc0a393
No known key found for this signature in database
GPG Key ID: 8ACD4E8264B67DF4
10 changed files with 101 additions and 50 deletions

View File

@ -1,16 +1,26 @@
import { BaseViewModel } from '../../site/base/base-view-model';
import { Injectable } from '@angular/core';
import { FileExportService } from './file-export.service';
import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../site/base/base-view-model';
import { FileExportService } from './file-export.service';
import { ConfigService } from './config.service';
@Injectable({
providedIn: 'root'
})
export class CsvExportService {
/**
* Constructor
*
* @param exporter helper to export something as file
* @param translate translation serivice
* @param config Configuration Service
*/
public constructor(protected exporter: FileExportService, private translate: TranslateService) {}
public constructor(
protected exporter: FileExportService,
private translate: TranslateService,
private config: ConfigService
) {}
/**
* Saves an array of model data to a CSV.
@ -29,11 +39,16 @@ export class CsvExportService {
assemble?: string; // (if property is further child object, the property of these to be used)
}[],
filename: string,
{ lineSeparator = '\r\n', columnSeparator = ';' }: { lineSeparator?: string; columnSeparator?: string } = {}
{
lineSeparator = '\r\n',
columnSeparator = this.config.instant('general_csv_separator')
}: {
lineSeparator?: string;
columnSeparator?: string;
} = {}
): void {
const allLines = []; // Array of arrays of entries
const usedColumns = []; // mapped properties to be included
// initial array of usable text separators. The first character not used
// in any text data or as column separator will be used as text separator
let tsList = ['"', "'", '`', '/', '\\', ';', '.'];
@ -58,15 +73,17 @@ export class CsvExportService {
// create lines
data.forEach(item => {
const line = [];
for (let i = 0; i < usedColumns.length; i++ ){
for (let i = 0; i < usedColumns.length; i++) {
const property = usedColumns[i];
let prop: any = item[property];
if (columns[i].assemble){
prop = item[property].map(subitem => this.translate.instant(subitem[columns[i].assemble])).join(',');
if (columns[i].assemble) {
prop = item[property]
.map(subitem => this.translate.instant(subitem[columns[i].assemble]))
.join(',');
}
tsList = this.checkCsvTextSafety(prop, tsList);
line.push(prop);
};
}
allLines.push(line);
});
@ -100,11 +117,13 @@ export class CsvExportService {
* Checks if a given input contains any of the characters defined in a list
* used for textseparators. The list is then returned without the 'special'
* characters, as they may not be used as text separator in this csv.
*
* @param input any input to be sent to CSV
* @param tsList The list of special characters to check.
* @returns the cleand CSV String list
*/
public checkCsvTextSafety(input: any, tsList: string[]): string[] {
if (input === null || input === undefined ) {
if (input === null || input === undefined) {
return tsList;
}
@ -112,6 +131,12 @@ export class CsvExportService {
return tsList.filter(char => inputAsString.indexOf(char) < 0);
}
/**
* Capitalizes the first letter of a string
*
* @param input String that should be capitalized
* @returns capitalized string
*/
private capitalizeTranslate(input: string): string {
const temp = input.charAt(0).toUpperCase() + input.slice(1);
return this.translate.instant(temp);

View File

@ -81,11 +81,6 @@
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
<mat-menu #motionListMenu="matMenu">
<button mat-menu-item (click)="downloadMotions()">
<mat-icon>archive</mat-icon>
<span translate>Export ...</span>
</button>
<button mat-menu-item routerLink="category">
<mat-icon>device_hub</mat-icon>
<span translate>Categories</span>
@ -105,4 +100,8 @@
<mat-icon>sort</mat-icon>
<span translate>Call list</span>
</button>
<button mat-menu-item (click)="csvExportMotionList()">
<mat-icon>archive</mat-icon>
<span translate>Export as CSV</span>
</button>
</mat-menu>

View File

@ -9,7 +9,8 @@ import { ViewMotion } from '../../models/view-motion';
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
import { ListViewBaseComponent } from '../../../base/list-view-base';
import { MatSnackBar } from '@angular/material';
import { ConfigService } from "../../../../core/services/config.service";
import { ConfigService } from '../../../../core/services/config.service';
import { CsvExportService } from 'app/core/services/csv-export.service';
/**
* Component that displays all the motions in a Table using DataSource.
@ -48,6 +49,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
* @param route Current route
* @param configService The configuration provider
* @param repo Motion Repository
* @param csvExport CSV Export Service
*/
public constructor(
titleService: Title,
@ -56,7 +58,8 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
private router: Router,
private route: ActivatedRoute,
private configService: ConfigService,
private repo: MotionRepositoryService
private repo: MotionRepositoryService,
private csvExport: CsvExportService
) {
super(titleService, translate, matSnackBar);
}
@ -79,9 +82,11 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
}
});
});
this.configService.get('motions_statutes_enabled').subscribe((enabled: boolean): void => {
this.statutesEnabled = enabled;
});
this.configService.get('motions_statutes_enabled').subscribe(
(enabled: boolean): void => {
this.statutesEnabled = enabled;
}
);
}
/**
@ -96,7 +101,9 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
/**
* Get the icon to the corresponding Motion Status
* TODO Needs to be more accessible (Motion workflow needs adjustment on the server)
*
* @param state the name of the state
* @returns the icon string
*/
public getStateIcon(state: WorkflowState): string {
const stateName = state.name;
@ -113,7 +120,9 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
/**
* Determines if an icon should be shown in the list view
* @param state
*
* @param state the workflowstate
* @returns a boolean if the icon should be shown
*/
public isDisplayIcon(state: WorkflowState): boolean {
if (state) {
@ -125,6 +134,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
/**
* Handler for the speakers button
*
* @param motion indicates the row that was clicked on
*/
public onSpeakerIcon(motion: ViewMotion): void {
@ -139,11 +149,21 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
}
/**
* Download all motions As PDF and DocX
*
* TODO: Currently does nothing
* Export all motions as CSV
*/
public downloadMotions(): void {
console.log('Download Motions Button');
public csvExportMotionList(): void {
this.csvExport.export(
this.dataSource.data,
[
{ property: 'identifier' },
{ property: 'title' },
{ property: 'text' },
{ property: 'reason' },
{ property: 'submitters' },
{ property: 'category' },
{ property: 'origin' }
],
this.translate.instant('Motions') + '.csv'
);
}
}

View File

@ -77,6 +77,6 @@
<button mat-menu-item (click)="csvExportUserList()">
<mat-icon>archive</mat-icon>
<span translate>Export as csv</span>
<span translate>Export as CSV</span>
</button>
</mat-menu>

View File

@ -19,12 +19,16 @@ import { MatSnackBar } from '@angular/material';
styleUrls: ['./user-list.component.scss']
})
export class UserListComponent extends ListViewBaseComponent<ViewUser> implements OnInit {
/**
* The usual constructor for components
*
* @param titleService Serivce for setting the title
* @param translate Service for translation handling
* @param matSnackBar Helper to diplay errors
* @param repo the user repository
* @param titleService
* @param translate
* @param router the router service
* @param route the local route
* @param csvExport CSV export Service
*/
public constructor(
titleService: Title,
@ -62,6 +66,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
/**
* Handles the click on a user row
*
* @param row selected row
*/
public selectUser(row: ViewUser): void {
@ -75,25 +80,27 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
this.router.navigate(['./new'], { relativeTo: this.route });
}
// TODO save all data from the dataSource
/**
* Export all users as CSV
*/
public csvExportUserList(): void {
this.csvExport.export(
this.dataSource.data,
[
{ property: 'title' },
{ property: 'first_name', label: 'First Name' },
{ property: 'last_name', label: 'Last Name' },
{ property: 'structure_level', label: 'Structure Level' },
{ property: 'participant_number', label: 'Participant Number' },
{ property: 'groups', assemble: 'name'},
{ property: 'first_name', label: 'Given name' },
{ property: 'last_name', label: 'Surname' },
{ property: 'structure_level', label: 'Structure level' },
{ property: 'participant_number', label: 'Participant number' },
{ property: 'groups', assemble: 'name' },
{ property: 'comment' },
{ property: 'is_active', label: 'Active' },
{ property: 'is_present', label: 'Presence' },
{ property: 'is_committee', label: 'Committee' },
{ property: 'default_password', label: 'Default password' },
{ property: 'email', label: 'E-Mail' }
{ property: 'is_active', label: 'Is active' },
{ property: 'is_present', label: 'Is present' },
{ property: 'is_committee', label: 'Is a committee' },
{ property: 'default_password', label: 'Initial password' },
{ property: 'email' }
],
'export.csv'
this.translate.instant('Participants') + '.csv'
);
}
}

View File

@ -1 +1 @@
{"About me":"O mě","Accept":"Přijmout","Accepted":"Přijato","Agenda":"Pořad jednání","All your changes are saved immediately.":"Všechny vaše změny jsou okamžitě uloženy.","Cancel":"Zrušit","Categories":"Skupiny","Category":"Obor činnosti","Change password":"Změnit heslo","Change recommendation":"Změnit doporučení","Changed version":"Změněná verze","Comment":"Poznámka","Comments":"Poznámky","Content":"Obsah","Create new category":"Vytvořit nový obor činnosti","Create new comment field":"Vytvořit nové poznámkové pole","Create new statute paragraph":"Vytvořit nový odstavec předpisu","Delete":"Smazat","Delete all files":"Smazat všechny soubory","Designates whether this user is in the room.":"Určuje, zda je tento uživatel v místnosti.","Designates whether this user should be treated as a committee.":"Určuje, zda se s tímto uživatelem má zacházet jako s výborem.","Designates whether this user should be treated as active. Unselect this instead of deleting the account.":"Určuje, zda se má s tímto uživatelem zacházet jako s činným. Zrušte vybrání namísto smazání účtu.","Diff version":"Znázornění změn","Edit":"Upravit","Edit category:":"Upravit obor činnosti:","Edit comment field:":"Upravit poznámkové pole:","Edit profile":"Upravit profil","Edit statute paragraph:":"Upravit odstavec předpisu:","Elections":"Elections","Email":"E-mail","English":"Angličtina","Export ...":"Vyvést...","Export as csv":"Vyvést jako CSV","FILTER":"FILTR","Files":"Soubory","Final version":"Konečná verze","French":"Francouzština","German":"Němčina","Given name":"Křestní jméno","Groups":"Skupiny","Groups with read permissions":"Skupiny s oprávněním pro čtení","Groups with write permissions":"Skupiny s oprávněním pro zápis","Home":"Domovská stránka","Identifier":"Identifikátor","Import ...":"Zavést...","Initial password":"Počáteční heslo","Installed plugins":"Nainstalované přídavné moduly","Is a committee":"Je výbor","Is active":"Je činná","Is present":"Je přítomná","Legal notice":"Právní upozornění","License":"Povolení","Line":"Řádek","Login":"Přihlášení","Login as Guest":"Přihlásit se jako host","Logout":"Odhlásit se","Meta information":"Popisné informace","Motion":"Návrh","Motions":"Návrhy","Name":"Název","New group name":"Název nové skupiny","New motion":"Nový návrh","New tag name":"Název nové značky","No change recommendations yet":"Dosud žádná doporučení změn","No comment":"Bez poznámky","No groups selected":"Nevybrány žádné skupiny","No personal note":"Bez osobní poznámky","No statute paragraphs":"Žádné odstavce předpisu","None":"Žádné","Note: You have to reject all change recommendations if the plenum does not follow the recommendation. This does not affect amendments.":"Poznámka: Musíte odmítnout všechna doporučení změn, pokud plenární zasedání nesleduje doporučení. Toto neovlivní dodatky.","OK":"OK","Offline mode: You can use OpenSlides but changes are not saved.":"Režim nepřipojen k internetu: Můžete OpenSlides používat, ale změny nejsou ukládány.","Only for internal notes.":"Jen pro vnitřní poznámky.","Origin":"Původ","Original version":"Původní verze","PDF":"PDF","Participant number":"Číslo účastníka","Participants":"Účastníci","Permissions":"Oprávnění","Personal note":"Osobní poznámka","Prefix":"Předpona","Present":"Přítomen","Privacy Policy":"Politika pro soukromí","Privacy policy":"Politika pro soukromí","Project":"Promítat","Projector":"Promítací přístroj","Public":"Veřejný","Reason":"Zdůvodnění","Reject":"Odmítnout","Rejected":"Odmítnuto","Required":"Požadováno","Reset recommendation":"Vynulovat doporučení","Reset state":"Obnovit stav","SORT":"TŘÍDIT","Save":"Uložit","Selected values":"Vybrané hodnoty","Settings":"Nastavení","Sort ...":"Třídit...","State":"Stav","Statute paragraph":"Odstavec předpisu","Statute paragraphs":"Odstavce předpisu","Structure level":"Úroveň rozčlenění","Submitters":"Navrhovatelé","Summary of changes":"Přehled změn","Supporters":"Podporovatel","Surname":"Příjmení","Tags":"Klíčová slova","The assembly may decide:":"Shromáždění se může usnést:","The event manager hasn't set up a privacy policy yet.":"Správce událostí ještě nenastavil politiku soukromí.","This change collides with another one.":"Tato změna se střetává s jinou.","Title":"Název","Username":"Uživatelské jméno","Welcome to OpenSlides":"Vítejte v OpenSlides","Yes":"Ano","by":"od","inline":"uvnitř","none":"žádné","outside":"vně"}
{"About me":"O mě","Accept":"Přijmout","Accepted":"Přijato","Agenda":"Pořad jednání","All your changes are saved immediately.":"Všechny vaše změny jsou okamžitě uloženy.","Cancel":"Zrušit","Categories":"Skupiny","Category":"Obor činnosti","Change password":"Změnit heslo","Change recommendation":"Změnit doporučení","Changed version":"Změněná verze","Comment":"Poznámka","Comments":"Poznámky","Content":"Obsah","Create new category":"Vytvořit nový obor činnosti","Create new comment field":"Vytvořit nové poznámkové pole","Create new statute paragraph":"Vytvořit nový odstavec předpisu","Delete":"Smazat","Delete all files":"Smazat všechny soubory","Designates whether this user is in the room.":"Určuje, zda je tento uživatel v místnosti.","Designates whether this user should be treated as a committee.":"Určuje, zda se s tímto uživatelem má zacházet jako s výborem.","Designates whether this user should be treated as active. Unselect this instead of deleting the account.":"Určuje, zda se má s tímto uživatelem zacházet jako s činným. Zrušte vybrání namísto smazání účtu.","Diff version":"Znázornění změn","Edit":"Upravit","Edit category:":"Upravit obor činnosti:","Edit comment field:":"Upravit poznámkové pole:","Edit profile":"Upravit profil","Edit statute paragraph:":"Upravit odstavec předpisu:","Elections":"Elections","Email":"E-mail","English":"Angličtina","Export ...":"Vyvést...","Export as CSV":"Vyvést jako CSV","FILTER":"FILTR","Files":"Soubory","Final version":"Konečná verze","French":"Francouzština","German":"Němčina","Given name":"Křestní jméno","Groups":"Skupiny","Groups with read permissions":"Skupiny s oprávněním pro čtení","Groups with write permissions":"Skupiny s oprávněním pro zápis","Home":"Domovská stránka","Identifier":"Identifikátor","Import ...":"Zavést...","Initial password":"Počáteční heslo","Installed plugins":"Nainstalované přídavné moduly","Is a committee":"Je výbor","Is active":"Je činná","Is present":"Je přítomná","Legal notice":"Právní upozornění","License":"Povolení","Line":"Řádek","Login":"Přihlášení","Login as Guest":"Přihlásit se jako host","Logout":"Odhlásit se","Meta information":"Popisné informace","Motion":"Návrh","Motions":"Návrhy","Name":"Název","New group name":"Název nové skupiny","New motion":"Nový návrh","New tag name":"Název nové značky","No change recommendations yet":"Dosud žádná doporučení změn","No comment":"Bez poznámky","No groups selected":"Nevybrány žádné skupiny","No personal note":"Bez osobní poznámky","No statute paragraphs":"Žádné odstavce předpisu","None":"Žádné","Note: You have to reject all change recommendations if the plenum does not follow the recommendation. This does not affect amendments.":"Poznámka: Musíte odmítnout všechna doporučení změn, pokud plenární zasedání nesleduje doporučení. Toto neovlivní dodatky.","OK":"OK","Offline mode: You can use OpenSlides but changes are not saved.":"Režim nepřipojen k internetu: Můžete OpenSlides používat, ale změny nejsou ukládány.","Only for internal notes.":"Jen pro vnitřní poznámky.","Origin":"Původ","Original version":"Původní verze","PDF":"PDF","Participant number":"Číslo účastníka","Participants":"Účastníci","Permissions":"Oprávnění","Personal note":"Osobní poznámka","Prefix":"Předpona","Present":"Přítomen","Privacy Policy":"Politika pro soukromí","Privacy policy":"Politika pro soukromí","Project":"Promítat","Projector":"Promítací přístroj","Public":"Veřejný","Reason":"Zdůvodnění","Reject":"Odmítnout","Rejected":"Odmítnuto","Required":"Požadováno","Reset recommendation":"Vynulovat doporučení","Reset state":"Obnovit stav","SORT":"TŘÍDIT","Save":"Uložit","Selected values":"Vybrané hodnoty","Settings":"Nastavení","Sort ...":"Třídit...","State":"Stav","Statute paragraph":"Odstavec předpisu","Statute paragraphs":"Odstavce předpisu","Structure level":"Úroveň rozčlenění","Submitters":"Navrhovatelé","Summary of changes":"Přehled změn","Supporters":"Podporovatel","Surname":"Příjmení","Tags":"Klíčová slova","The assembly may decide:":"Shromáždění se může usnést:","The event manager hasn't set up a privacy policy yet.":"Správce událostí ještě nenastavil politiku soukromí.","This change collides with another one.":"Tato změna se střetává s jinou.","Title":"Název","Username":"Uživatelské jméno","Welcome to OpenSlides":"Vítejte v OpenSlides","Yes":"Ano","by":"od","inline":"uvnitř","none":"žádné","outside":"vně"}

View File

@ -1,6 +1,6 @@
# Translators:
# fri, 2018
#
#
msgid ""
msgstr ""
"Last-Translator: fri, 2018\n"
@ -111,7 +111,7 @@ msgstr "Angličtina"
msgid "Export ..."
msgstr "Vyvést..."
msgid "Export as csv"
msgid "Export as CSV"
msgstr "Vyvést jako CSV"
msgid "FILTER"

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
# Translators:
# Emanuel Schütze <emanuel.schuetze@intevation.de>, 2018
#
#
msgid ""
msgstr ""
"Last-Translator: Emanuel Schütze <emanuel.schuetze@intevation.de>, 2018\n"
@ -417,7 +417,7 @@ msgstr "Export"
msgid "Export ..."
msgstr "Exportieren ..."
msgid "Export as csv"
msgid "Export as CSV"
msgstr "Exportieren als CSV"
msgid "FILTER"

View File

@ -386,7 +386,7 @@ msgstr ""
msgid "Export ..."
msgstr ""
msgid "Export as csv"
msgid "Export as CSV"
msgstr ""
msgid "FILTER"
@ -1059,4 +1059,4 @@ msgid "submitted"
msgstr ""
msgid "withdrawed"
msgstr ""
msgstr ""