Call list export
refined the export service to accept a generic map function, so arbitrary values can put into the csv
This commit is contained in:
parent
3c4936764d
commit
6a8976b8ca
@ -5,6 +5,51 @@ import { BaseViewModel } from '../../site/base/base-view-model';
|
|||||||
import { FileExportService } from './file-export.service';
|
import { FileExportService } from './file-export.service';
|
||||||
import { ConfigService } from './config.service';
|
import { ConfigService } from './config.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a csv column with a property of the model and an optional label. If this is not given, the
|
||||||
|
* translated and capitalized property name is used.
|
||||||
|
*/
|
||||||
|
interface CsvColumnDefinitionProperty<T> {
|
||||||
|
label?: string;
|
||||||
|
property: keyof T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type assertion for CsvColumnDefinitionProperty<T>
|
||||||
|
*
|
||||||
|
* @param obj Any object to test.
|
||||||
|
* @returns true, if the object is a property definition. This is also asserted for TypeScript.
|
||||||
|
*/
|
||||||
|
function isPropertyDefinition<T>(obj: any): obj is CsvColumnDefinitionProperty<T> {
|
||||||
|
return 'property' in obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a csv column with a map function. Here, the user of this service can define hat should happen with
|
||||||
|
* all the models. This map function is called for every model and the user should return a string that is
|
||||||
|
* put into the csv. Also a column label must be given, that is capitalized and translated.
|
||||||
|
*/
|
||||||
|
interface CsvColumnDefinitionMap<T> {
|
||||||
|
label: string;
|
||||||
|
map: (model: T) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type assertion for CsvColumnDefinitionMap<T>
|
||||||
|
*
|
||||||
|
* @param obj Any object to test.
|
||||||
|
* @returns true, if the objct is a map definition. This is also asserted for TypeScript.
|
||||||
|
*/
|
||||||
|
function isMapDefinition<T>(obj: any): obj is CsvColumnDefinitionMap<T> {
|
||||||
|
return 'map' in obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The definition of columns in the export. Either use a property for every model or do a custom mapping to
|
||||||
|
* a string to be put into the csv.
|
||||||
|
*/
|
||||||
|
type CsvColumnsDefinition<T> = (CsvColumnDefinitionProperty<T> | CsvColumnDefinitionMap<T>)[];
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -24,7 +69,7 @@ export class CsvExportService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves an array of model data to a CSV.
|
* Saves an array of model data to a CSV.
|
||||||
* @param data Array of Model instances to be saved
|
* @param models Array of model instances to be saved
|
||||||
* @param columns Column definitions
|
* @param columns Column definitions
|
||||||
* @param filename name of the resulting file
|
* @param filename name of the resulting file
|
||||||
* @param options optional:
|
* @param options optional:
|
||||||
@ -32,12 +77,8 @@ export class CsvExportService {
|
|||||||
* columnseparator defaults to semicolon (other usual separators are ',' '\T' (tab), ' 'whitespace)
|
* columnseparator defaults to semicolon (other usual separators are ',' '\T' (tab), ' 'whitespace)
|
||||||
*/
|
*/
|
||||||
public export<T extends BaseViewModel>(
|
public export<T extends BaseViewModel>(
|
||||||
data: T[],
|
models: T[],
|
||||||
columns: {
|
columns: CsvColumnsDefinition<T>,
|
||||||
property: keyof T; // name of the property used for export
|
|
||||||
label?: string;
|
|
||||||
assemble?: string; // (if property is further child object, the property of these to be used)
|
|
||||||
}[],
|
|
||||||
filename: string,
|
filename: string,
|
||||||
{
|
{
|
||||||
lineSeparator = '\r\n',
|
lineSeparator = '\r\n',
|
||||||
@ -47,8 +88,7 @@ export class CsvExportService {
|
|||||||
columnSeparator?: string;
|
columnSeparator?: string;
|
||||||
} = {}
|
} = {}
|
||||||
): void {
|
): void {
|
||||||
const allLines = []; // Array of arrays of entries
|
let csvContent = []; // Holds all lines as arrays with each column-value
|
||||||
const usedColumns = []; // mapped properties to be included
|
|
||||||
// initial array of usable text separators. The first character not used
|
// 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
|
// in any text data or as column separator will be used as text separator
|
||||||
let tsList = ['"', "'", '`', '/', '\\', ';', '.'];
|
let tsList = ['"', "'", '`', '/', '\\', ';', '.'];
|
||||||
@ -61,56 +101,55 @@ export class CsvExportService {
|
|||||||
tsList = this.checkCsvTextSafety(columnSeparator, tsList);
|
tsList = this.checkCsvTextSafety(columnSeparator, tsList);
|
||||||
|
|
||||||
// create header data
|
// create header data
|
||||||
const header = [];
|
const header = columns.map(column => {
|
||||||
columns.forEach(column => {
|
let label: string;
|
||||||
const rawLabel: string = column.label ? column.label : (column.property as string);
|
if (isPropertyDefinition(column)) {
|
||||||
const colLabel = this.capitalizeTranslate(rawLabel);
|
label = column.label ? column.label : (column.property as string);
|
||||||
tsList = this.checkCsvTextSafety(colLabel, tsList);
|
} else if (isMapDefinition(column)) {
|
||||||
header.push(colLabel);
|
label = column.label;
|
||||||
usedColumns.push(column.property);
|
|
||||||
});
|
|
||||||
allLines.push(header);
|
|
||||||
// create lines
|
|
||||||
data.forEach(item => {
|
|
||||||
const line = [];
|
|
||||||
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(',');
|
|
||||||
}
|
|
||||||
tsList = this.checkCsvTextSafety(prop, tsList);
|
|
||||||
line.push(prop);
|
|
||||||
}
|
}
|
||||||
allLines.push(line);
|
label = this.capitalizeTranslate(label);
|
||||||
|
tsList = this.checkCsvTextSafety(label, tsList);
|
||||||
|
return label;
|
||||||
});
|
});
|
||||||
|
csvContent.push(header);
|
||||||
|
|
||||||
|
// create lines
|
||||||
|
csvContent = csvContent.concat(models.map(model => {
|
||||||
|
return columns.map(column => {
|
||||||
|
let value: string;
|
||||||
|
|
||||||
|
if (isPropertyDefinition(column)) {
|
||||||
|
const property: any = model[column.property];
|
||||||
|
if (typeof property === 'number') {
|
||||||
|
value = property.toString(10);
|
||||||
|
} else if (!property) {
|
||||||
|
value = '';
|
||||||
|
} else if (property === true) {
|
||||||
|
value = '1';
|
||||||
|
} else if (property === false) {
|
||||||
|
value = '0';
|
||||||
|
} else {
|
||||||
|
value = property.toString();
|
||||||
|
}
|
||||||
|
} else if (isMapDefinition(column)) {
|
||||||
|
value = column.map(model);
|
||||||
|
}
|
||||||
|
tsList = this.checkCsvTextSafety(value, tsList);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
// assemble lines, putting text separator in place
|
// assemble lines, putting text separator in place
|
||||||
if (!tsList.length) {
|
if (!tsList.length) {
|
||||||
throw new Error('no usable text separator left for valid csv text');
|
throw new Error('no usable text separator left for valid csv text');
|
||||||
}
|
}
|
||||||
|
|
||||||
const allLinesAssembled = [];
|
const csvContentAsString: string = csvContent.map(line => {
|
||||||
allLines.forEach(line => {
|
return line.map(entry => tsList[0] + entry + tsList[0]).join(columnSeparator);
|
||||||
const assembledLine = [];
|
}).join(lineSeparator);
|
||||||
line.forEach(item => {
|
this.exporter.saveFile(csvContentAsString, filename);
|
||||||
if (typeof item === 'number') {
|
|
||||||
assembledLine.push(item.toString(10));
|
|
||||||
} else if (item === null || item === undefined || item === '') {
|
|
||||||
assembledLine.push('');
|
|
||||||
} else if (item === true) {
|
|
||||||
assembledLine.push('1');
|
|
||||||
} else if (item === false) {
|
|
||||||
assembledLine.push('0');
|
|
||||||
} else {
|
|
||||||
assembledLine.push(tsList[0] + item + tsList[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
allLinesAssembled.push(assembledLine.join(columnSeparator));
|
|
||||||
});
|
|
||||||
this.exporter.saveFile(allLinesAssembled.join(lineSeparator), filename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,13 +161,12 @@ export class CsvExportService {
|
|||||||
* @param tsList The list of special characters to check.
|
* @param tsList The list of special characters to check.
|
||||||
* @returns the cleand CSV String list
|
* @returns the cleand CSV String list
|
||||||
*/
|
*/
|
||||||
public checkCsvTextSafety(input: any, tsList: string[]): string[] {
|
public checkCsvTextSafety(input: string, tsList: string[]): string[] {
|
||||||
if (input === null || input === undefined) {
|
if (input === null || input === undefined) {
|
||||||
return tsList;
|
return tsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputAsString = String(input);
|
return tsList.filter(char => input.indexOf(char) < 0);
|
||||||
return tsList.filter(char => inputAsString.indexOf(char) < 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,13 @@
|
|||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 translate>Call list</h2>
|
<h2 translate>Call list</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Export -->
|
||||||
|
<div class="menu-slot">
|
||||||
|
<button mat-icon-button (click)="csvExportCallList()">
|
||||||
|
<mat-icon>archive</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<mat-card>
|
<mat-card>
|
||||||
|
@ -10,6 +10,7 @@ import { MotionRepositoryService } from '../../services/motion-repository.servic
|
|||||||
import { ViewMotion } from '../../models/view-motion';
|
import { ViewMotion } from '../../models/view-motion';
|
||||||
import { SortingListComponent } from '../../../../shared/components/sorting-list/sorting-list.component';
|
import { SortingListComponent } from '../../../../shared/components/sorting-list/sorting-list.component';
|
||||||
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
|
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
|
||||||
|
import { MotionCsvExportService } from '../../services/motion-csv-export.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort view for the call list.
|
* Sort view for the call list.
|
||||||
@ -24,6 +25,11 @@ export class CallListComponent extends BaseViewComponent {
|
|||||||
*/
|
*/
|
||||||
public motionsObservable: Observable<ViewMotion[]>;
|
public motionsObservable: Observable<ViewMotion[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds all motions for the export.
|
||||||
|
*/
|
||||||
|
private motions: ViewMotion[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits true for expand and false for collaps. Informs the sorter component about this actions.
|
* Emits true for expand and false for collaps. Informs the sorter component about this actions.
|
||||||
*/
|
*/
|
||||||
@ -46,11 +52,16 @@ export class CallListComponent extends BaseViewComponent {
|
|||||||
title: Title,
|
title: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private motionRepo: MotionRepositoryService
|
private motionRepo: MotionRepositoryService,
|
||||||
|
private motionCsvExport: MotionCsvExportService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackBar);
|
super(title, translate, matSnackBar);
|
||||||
|
|
||||||
this.motionsObservable = this.motionRepo.getViewModelListObservable();
|
this.motionsObservable = this.motionRepo.getViewModelListObservable();
|
||||||
|
this.motionsObservable.subscribe(motions => {
|
||||||
|
// Sort motions and make a copy, so it will stay sorted.
|
||||||
|
this.motions = motions.map(x => x).sort((a, b) => a.callListWeight - b.callListWeight);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,4 +83,11 @@ export class CallListComponent extends BaseViewComponent {
|
|||||||
public expandCollapseAll(expand: boolean): void {
|
public expandCollapseAll(expand: boolean): void {
|
||||||
this.expandCollapse.emit(expand);
|
this.expandCollapse.emit(expand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the full call list as csv.
|
||||||
|
*/
|
||||||
|
public csvExportCallList(): void {
|
||||||
|
this.motionCsvExport.exportCallList(this.motions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,12 +114,14 @@
|
|||||||
<mat-icon>device_hub</mat-icon>
|
<mat-icon>device_hub</mat-icon>
|
||||||
<span translate>Categories</span>
|
<span translate>Categories</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button mat-menu-item routerLink="comment-section">
|
<button mat-menu-item routerLink="comment-section">
|
||||||
<mat-icon>speaker_notes</mat-icon>
|
<mat-icon>speaker_notes</mat-icon>
|
||||||
<span translate>Comment sections</span>
|
<span translate>Comment sections</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button mat-menu-item *osPerms="'motions.can_manage'" routerLink="call-list">
|
||||||
|
<mat-icon>sort</mat-icon>
|
||||||
|
<span translate>Call list</span>
|
||||||
|
</button>
|
||||||
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
|
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
|
||||||
<mat-icon>account_balance</mat-icon>
|
<mat-icon>account_balance</mat-icon>
|
||||||
<span translate>Statute paragraphs</span>
|
<span translate>Statute paragraphs</span>
|
||||||
|
@ -9,9 +9,9 @@ import { WorkflowState } from '../../../../shared/models/motions/workflow-state'
|
|||||||
import { ListViewBaseComponent } from '../../../base/list-view-base';
|
import { ListViewBaseComponent } from '../../../base/list-view-base';
|
||||||
import { MatSnackBar } from '@angular/material';
|
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';
|
|
||||||
import { Category } from '../../../../shared/models/motions/category';
|
import { Category } from '../../../../shared/models/motions/category';
|
||||||
import { PromptService } from '../../../../core/services/prompt.service';
|
import { PromptService } from '../../../../core/services/prompt.service';
|
||||||
|
import { MotionCsvExportService } from '../../services/motion-csv-export.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays all the motions in a Table using DataSource.
|
* Component that displays all the motions in a Table using DataSource.
|
||||||
@ -62,8 +62,8 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private repo: MotionRepositoryService,
|
private repo: MotionRepositoryService,
|
||||||
private csvExport: CsvExportService,
|
private promptService: PromptService,
|
||||||
private promptService: PromptService
|
private motionCsvExport: MotionCsvExportService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
|
|
||||||
@ -160,19 +160,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
|||||||
* Export all motions as CSV
|
* Export all motions as CSV
|
||||||
*/
|
*/
|
||||||
public csvExportMotionList(): void {
|
public csvExportMotionList(): void {
|
||||||
this.csvExport.export(
|
this.motionCsvExport.exportMotionList(this.dataSource.data);
|
||||||
this.dataSource.data,
|
|
||||||
[
|
|
||||||
{ property: 'identifier' },
|
|
||||||
{ property: 'title' },
|
|
||||||
{ property: 'text' },
|
|
||||||
{ property: 'reason' },
|
|
||||||
{ property: 'submitters' },
|
|
||||||
{ property: 'category' },
|
|
||||||
{ property: 'origin' }
|
|
||||||
],
|
|
||||||
this.translate.instant('Motions') + '.csv'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,7 @@ import { BaseViewModel } from '../../base/base-view-model';
|
|||||||
import { ViewMotionCommentSection } from './view-motion-comment-section';
|
import { ViewMotionCommentSection } from './view-motion-comment-section';
|
||||||
import { MotionComment } from '../../../shared/models/motions/motion-comment';
|
import { MotionComment } from '../../../shared/models/motions/motion-comment';
|
||||||
import { Item } from 'app/shared/models/agenda/item';
|
import { Item } from 'app/shared/models/agenda/item';
|
||||||
|
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||||
|
|
||||||
export enum LineNumberingMode {
|
export enum LineNumberingMode {
|
||||||
None,
|
None,
|
||||||
@ -37,6 +38,7 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
private _workflow: Workflow;
|
private _workflow: Workflow;
|
||||||
private _state: WorkflowState;
|
private _state: WorkflowState;
|
||||||
private _item: Item;
|
private _item: Item;
|
||||||
|
private _block: MotionBlock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the LineNumberingMode Mode.
|
* Indicates the LineNumberingMode Mode.
|
||||||
@ -84,6 +86,13 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
return this.motion ? this.motion.title : null;
|
return this.motion ? this.motion.title : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get identifierOrTitle(): string {
|
||||||
|
if (!this.motion) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.identifier ? this.identifier : this.title;
|
||||||
|
}
|
||||||
|
|
||||||
public get text(): string {
|
public get text(): string {
|
||||||
return this.motion ? this.motion.text : null;
|
return this.motion ? this.motion.text : null;
|
||||||
}
|
}
|
||||||
@ -184,6 +193,14 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
return this._item;
|
return this._item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get motion_block_id(): number {
|
||||||
|
return this.motion ? this.motion.motion_block_id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get motion_block(): MotionBlock {
|
||||||
|
return this._block;
|
||||||
|
}
|
||||||
|
|
||||||
public get agendaSpeakerAmount(): number {
|
public get agendaSpeakerAmount(): number {
|
||||||
return this.item ? this.item.speakerAmount : null;
|
return this.item ? this.item.speakerAmount : null;
|
||||||
}
|
}
|
||||||
@ -195,7 +212,8 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
supporters?: User[],
|
supporters?: User[],
|
||||||
workflow?: Workflow,
|
workflow?: Workflow,
|
||||||
state?: WorkflowState,
|
state?: WorkflowState,
|
||||||
item?: Item
|
item?: Item,
|
||||||
|
block?: MotionBlock
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -206,6 +224,7 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
this._workflow = workflow;
|
this._workflow = workflow;
|
||||||
this._state = state;
|
this._state = state;
|
||||||
this._item = item;
|
this._item = item;
|
||||||
|
this._block = block;
|
||||||
|
|
||||||
// TODO: Should be set using a a config variable
|
// TODO: Should be set using a a config variable
|
||||||
this.lnMode = LineNumberingMode.Outside;
|
this.lnMode = LineNumberingMode.Outside;
|
||||||
@ -244,6 +263,8 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
this.updateCategory(update as Category);
|
this.updateCategory(update as Category);
|
||||||
} else if (update instanceof Item) {
|
} else if (update instanceof Item) {
|
||||||
this.updateItem(update as Item);
|
this.updateItem(update as Item);
|
||||||
|
} else if (update instanceof MotionBlock) {
|
||||||
|
this.updateMotionBlock(update);
|
||||||
}
|
}
|
||||||
// TODO: There is no way (yet) to add Submitters to a motion
|
// TODO: There is no way (yet) to add Submitters to a motion
|
||||||
// Thus, this feature could not be tested
|
// Thus, this feature could not be tested
|
||||||
@ -251,31 +272,41 @@ export class ViewMotion extends BaseViewModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update routine for the category
|
* Update routine for the category
|
||||||
* @param update potentially the changed category. Needs manual verification
|
* @param category potentially the changed category. Needs manual verification
|
||||||
*/
|
*/
|
||||||
public updateCategory(update: Category): void {
|
public updateCategory(category: Category): void {
|
||||||
if (this.motion && update.id === this.motion.category_id) {
|
if (this.motion && category.id === this.motion.category_id) {
|
||||||
this._category = update as Category;
|
this._category = category;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update routine for the workflow
|
* Update routine for the workflow
|
||||||
* @param update potentially the changed workflow (state). Needs manual verification
|
* @param workflow potentially the changed workflow (state). Needs manual verification
|
||||||
*/
|
*/
|
||||||
public updateWorkflow(update: Workflow): void {
|
public updateWorkflow(workflow: Workflow): void {
|
||||||
if (this.motion && update.id === this.motion.workflow_id) {
|
if (this.motion && workflow.id === this.motion.workflow_id) {
|
||||||
this._workflow = update as Workflow;
|
this._workflow = workflow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update routine for the agenda Item
|
* Update routine for the agenda Item
|
||||||
* @param update potentially the changed agenda Item. Needs manual verification
|
* @param item potentially the changed agenda Item. Needs manual verification
|
||||||
*/
|
*/
|
||||||
public updateItem(update: Item): void {
|
public updateItem(item: Item): void {
|
||||||
if (this.motion && update.id === this.motion.agenda_item_id) {
|
if (this.motion && item.id === this.motion.agenda_item_id) {
|
||||||
this._item = update as Item;
|
this._item = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update routine for the motion block
|
||||||
|
* @param block potentially the changed motion block. Needs manual verification
|
||||||
|
*/
|
||||||
|
public updateMotionBlock(block: MotionBlock): void {
|
||||||
|
if (this.motion && block.id === this.motion.motion_block_id) {
|
||||||
|
this._block = block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
import { MotionCsvExportService } from './motion-csv-export.service';
|
||||||
|
|
||||||
|
describe('MotionCsvExportService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
providers: [MotionCsvExportService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject(
|
||||||
|
[MotionCsvExportService],
|
||||||
|
(service: MotionCsvExportService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
@ -0,0 +1,64 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { CsvExportService } from 'app/core/services/csv-export.service';
|
||||||
|
import { ViewMotion } from '../models/view-motion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports CSVs for motions. Collect all CSV types here to have them in one place.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MotionCsvExportService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing.
|
||||||
|
*
|
||||||
|
* @param csvExport CsvExportService
|
||||||
|
* @param translate TranslateService
|
||||||
|
*/
|
||||||
|
public constructor(private csvExport: CsvExportService, private translate: TranslateService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export all motions as CSV
|
||||||
|
*
|
||||||
|
* @param motions Motions to export
|
||||||
|
*/
|
||||||
|
public exportMotionList(motions: ViewMotion[]): void {
|
||||||
|
this.csvExport.export(
|
||||||
|
motions,
|
||||||
|
[
|
||||||
|
{ property: 'identifier' },
|
||||||
|
{ property: 'title' },
|
||||||
|
{ property: 'text' },
|
||||||
|
{ property: 'reason' },
|
||||||
|
{ property: 'submitters' },
|
||||||
|
{ property: 'category' },
|
||||||
|
{ property: 'origin' }
|
||||||
|
],
|
||||||
|
this.translate.instant('Motions') + '.csv'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the call list.
|
||||||
|
*
|
||||||
|
* @param motions All motions in the CSV. They should be ordered by callListWeight correctly.
|
||||||
|
*/
|
||||||
|
public exportCallList(motions: ViewMotion[]): void {
|
||||||
|
this.csvExport.export(
|
||||||
|
motions,
|
||||||
|
[
|
||||||
|
{ label: 'Called', map: motion => motion.sort_parent_id ? '' : motion.identifierOrTitle },
|
||||||
|
{ label: 'Called with', map: motion => !motion.sort_parent_id ? '' : motion.identifierOrTitle },
|
||||||
|
{ label: 'submitters', map: motion => motion.submitters.map(s => s.short_name).join(',') },
|
||||||
|
{ property: 'title' },
|
||||||
|
{ label: 'recommendation', map: motion => motion.recommendation ? this.translate.instant(motion.recommendation.recommendation_label) : '' },
|
||||||
|
{ property: 'motion_block', label: 'Motion block' }
|
||||||
|
],
|
||||||
|
this.translate.instant('Call list') + '.csv'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,12 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
|||||||
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item);
|
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom hook into the observables. The motions get a virtual weight (a sequential number) for the
|
||||||
|
* call list order. One can just sort for this number instead of dealing with the sort parent id and weight.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
public getViewModelListObservable(): Observable<ViewMotion[]> {
|
public getViewModelListObservable(): Observable<ViewMotion[]> {
|
||||||
return super.getViewModelListObservable().pipe(
|
return super.getViewModelListObservable().pipe(
|
||||||
tap(motions => {
|
tap(motions => {
|
||||||
|
@ -99,7 +99,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
|||||||
{ property: 'last_name', label: 'Surname' },
|
{ property: 'last_name', label: 'Surname' },
|
||||||
{ property: 'structure_level', label: 'Structure level' },
|
{ property: 'structure_level', label: 'Structure level' },
|
||||||
{ property: 'participant_number', label: 'Participant number' },
|
{ property: 'participant_number', label: 'Participant number' },
|
||||||
{ property: 'groups', assemble: 'name' },
|
{ label: 'groups', map: user => user.groups.map(group => group.name).join(',') },
|
||||||
{ property: 'comment' },
|
{ property: 'comment' },
|
||||||
{ property: 'is_active', label: 'Is active' },
|
{ property: 'is_active', label: 'Is active' },
|
||||||
{ property: 'is_present', label: 'Is present' },
|
{ property: 'is_present', label: 'Is present' },
|
||||||
|
Loading…
Reference in New Issue
Block a user