Add motion blocks
This commit is contained in:
parent
3b72e720b3
commit
03508c903f
@ -10,6 +10,16 @@ interface ContentObject {
|
||||
collection: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine visibility states for agenda items
|
||||
* Coming from "OpenSlidesConfigVariables" property "agenda_hide_internal_items_on_projector"
|
||||
*/
|
||||
export const itemVisibilityChoices = [
|
||||
{ key: 1, name: 'Public item' },
|
||||
{ key: 2, name: 'Internal item' },
|
||||
{ key: 3, name: 'Hidden item' }
|
||||
];
|
||||
|
||||
/**
|
||||
* Representations of agenda Item
|
||||
* @ignore
|
||||
|
@ -17,7 +17,12 @@ export class MotionBlock extends AgendaBaseModel {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the motion block
|
||||
*
|
||||
* @returns the URL as string
|
||||
*/
|
||||
public getDetailStateURL(): string {
|
||||
return 'TODO';
|
||||
return `/motions/blocks/${this.id}`;
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@
|
||||
}
|
||||
|
||||
.topic-title {
|
||||
padding: 40px;
|
||||
padding-left: 25px;
|
||||
padding: 40px 0 40px 25px;
|
||||
line-height: 180%;
|
||||
font-size: 120%;
|
||||
color: #317796; // TODO: put in theme as $primary
|
||||
|
@ -66,7 +66,7 @@
|
||||
{{ updateForm.get('name').value }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-size">
|
||||
<div class="header-size os-amount-chip">
|
||||
{{ motionsInCategory(category).length }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,13 +31,6 @@
|
||||
|
||||
.header-size {
|
||||
grid-column-start: 3;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 3px;
|
||||
background: lightgray;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,124 @@
|
||||
<os-head-bar
|
||||
mainButtonIcon="edit"
|
||||
[nav]="false"
|
||||
[mainButton]="true"
|
||||
[editMode]="editBlock"
|
||||
(mainEvent)="toggleEditMode()"
|
||||
(saveEvent)="saveBlock()"
|
||||
>
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 *ngIf="block && !editBlock">{{ 'Motion block' | translate }} {{ block.id }}</h2>
|
||||
|
||||
<form [formGroup]="blockEditForm" (ngSubmit)="saveBlock()" (keydown)="onKeyDown($event)" *ngIf="editBlock">
|
||||
<mat-form-field>
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
osAutofocus
|
||||
required
|
||||
formControlName="title"
|
||||
placeholder="{{ 'Title' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Menu -->
|
||||
<div class="menu-slot">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="motionBlockMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<!-- Title -->
|
||||
<div *ngIf="block" class="block-title on-transition-fade">
|
||||
<h2 *ngIf="!editBlock">{{ block.title }}</h2>
|
||||
<h2 *ngIf="editBlock">{{ blockEditForm.get('title').value }}</h2>
|
||||
</div>
|
||||
|
||||
<mat-card class="block-card">
|
||||
<button mat-raised-button color="primary" (click)="onFollowRecButton()" [disabled]="isFollowingProhibited()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
<span translate>Follow recommendations for all motions</span>
|
||||
</button>
|
||||
|
||||
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource" matSort>
|
||||
<!-- title column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <span translate>Motion</span> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion" (click)="onClickMotionTitle(motion)"> {{ motion.title }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- state column -->
|
||||
<ng-container matColumnDef="state">
|
||||
<mat-header-cell *matHeaderCellDef> <span translate>State</span> </mat-header-cell>
|
||||
<mat-cell class="chip-container" *matCellDef="let motion">
|
||||
<mat-basic-chip
|
||||
disableRipple
|
||||
[ngClass]="{
|
||||
green: motion.state.css_class === 'success',
|
||||
red: motion.state.css_class === 'danger',
|
||||
grey: motion.state.css_class === 'default',
|
||||
lightblue: motion.state.css_class === 'primary'
|
||||
}"
|
||||
>
|
||||
{{ motion.state.name | translate }}
|
||||
</mat-basic-chip>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Recommendation column -->
|
||||
<ng-container matColumnDef="recommendation">
|
||||
<mat-header-cell *matHeaderCellDef> <span translate>Recommendation</span> </mat-header-cell>
|
||||
<mat-cell class="chip-container" *matCellDef="let motion">
|
||||
<mat-basic-chip disableRipple class="bluegrey">
|
||||
{{
|
||||
motion.recommendation
|
||||
? (motion.recommendation.recommendation_label | translate)
|
||||
: ('not set' | translate)
|
||||
}}
|
||||
</mat-basic-chip>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Remove motion column -->
|
||||
<ng-container matColumnDef="remove">
|
||||
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
matTooltip="{{ 'Remove from motion block' | translate }}"
|
||||
(click)="onRemoveMotionButton(motion)"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: getColumnDefinition()"> </mat-row>
|
||||
</table>
|
||||
</mat-card>
|
||||
|
||||
<!-- The menu content -->
|
||||
<mat-menu #motionBlockMenu="matMenu">
|
||||
<button mat-menu-item [routerLink]="getSpeakerLink()">
|
||||
<mat-icon>mic</mat-icon>
|
||||
<span translate>List of speakers</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item>
|
||||
<mat-icon>videocam</mat-icon>
|
||||
<span translate>Project</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<button mat-menu-item class="red-warning-text" (click)="onDeleteBlockButton()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
</button>
|
||||
</mat-menu>
|
@ -0,0 +1,52 @@
|
||||
.block-title {
|
||||
padding: 40px;
|
||||
padding-left: 25px;
|
||||
line-height: 180%;
|
||||
font-size: 120%;
|
||||
color: #317796; // TODO: put in theme as $primary
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.block-card {
|
||||
margin: 0 20px 0 20px;
|
||||
padding: 25px;
|
||||
|
||||
button {
|
||||
.mat-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chip-container {
|
||||
display: block;
|
||||
height: 5em;
|
||||
line-height: 5em;
|
||||
}
|
||||
|
||||
.os-headed-listview-table {
|
||||
// Title
|
||||
.mat-column-title {
|
||||
flex: 4 0 0;
|
||||
}
|
||||
|
||||
// State
|
||||
.mat-column-state {
|
||||
flex: 2 0 0;
|
||||
}
|
||||
|
||||
// Recommendation
|
||||
.mat-column-recommendation {
|
||||
flex: 2 0 0;
|
||||
}
|
||||
|
||||
// Remove
|
||||
.mat-column-remove {
|
||||
flex: 1 0 0;
|
||||
justify-content: flex-end !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MotionBlockDetailComponent } from './motion-block-detail.component';
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
describe('MotionBlockDetailComponent', () => {
|
||||
let component: MotionBlockDetailComponent;
|
||||
let fixture: ComponentFixture<MotionBlockDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [MotionBlockDetailComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MotionBlockDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,209 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
||||
import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service';
|
||||
import { MotionRepositoryService } from '../../services/motion-repository.service';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
import { ViewMotionBlock } from '../../models/view-motion-block';
|
||||
import { ViewMotion } from '../../models/view-motion';
|
||||
import { PromptService } from 'app/core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* Detail component to display one motion block
|
||||
*/
|
||||
@Component({
|
||||
selector: 'os-motion-block-detail',
|
||||
templateUrl: './motion-block-detail.component.html',
|
||||
styleUrls: ['./motion-block-detail.component.scss']
|
||||
})
|
||||
export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion> implements OnInit {
|
||||
/**
|
||||
* Determines the block id from the given URL
|
||||
*/
|
||||
public block: ViewMotionBlock;
|
||||
|
||||
/**
|
||||
* All motions in this block
|
||||
*/
|
||||
public motions: ViewMotion[];
|
||||
|
||||
/**
|
||||
* Determine the edit mode
|
||||
*/
|
||||
public editBlock = false;
|
||||
|
||||
/**
|
||||
* The form to edit blocks
|
||||
*/
|
||||
@ViewChild('blockEditForm')
|
||||
public blockEditForm: FormGroup;
|
||||
|
||||
/**
|
||||
* Constructor for motion block details
|
||||
*
|
||||
* @param titleService Setting the title
|
||||
* @param translate translations
|
||||
* @param matSnackBar showing errors
|
||||
* @param router navigating
|
||||
* @param route determine the blocks ID by the route
|
||||
* @param repo the motion blocks repository
|
||||
* @param motionRepo the motion repository
|
||||
* @param promptService the displaying prompts before deleting
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private repo: MotionBlockRepositoryService,
|
||||
private motionRepo: MotionRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
* Sets the title, observes the block and the motions belonging in this block
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
super.setTitle('Motion Block');
|
||||
this.initTable();
|
||||
|
||||
this.blockEditForm = new FormGroup({
|
||||
title: new FormControl('', Validators.required)
|
||||
});
|
||||
|
||||
const blockId = +this.route.snapshot.params.id;
|
||||
this.block = this.repo.getViewModel(blockId);
|
||||
|
||||
this.repo.getViewModelObservable(blockId).subscribe(newBlock => {
|
||||
// necessary since the subscription can return undefined
|
||||
if (newBlock) {
|
||||
this.block = newBlock;
|
||||
|
||||
// set the blocks title in the form
|
||||
this.blockEditForm.get('title').setValue(this.block.title);
|
||||
|
||||
this.repo.getViewMotionsByBlock(this.block.motionBlock).subscribe(newMotions => {
|
||||
this.motions = newMotions;
|
||||
this.dataSource.data = this.motions;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get link to the list of speakers of the corresponding agenda item
|
||||
*
|
||||
* @returns the link to the list of speakers as string
|
||||
*/
|
||||
public getSpeakerLink(): string {
|
||||
if (this.block) {
|
||||
return `/agenda/${this.block.agenda_item_id}/speakers`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the columns that should be shown in the table
|
||||
*
|
||||
* @returns an array of strings building the column definition
|
||||
*/
|
||||
public getColumnDefinition(): string[] {
|
||||
return ['title', 'state', 'recommendation', 'remove'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for recommendation button
|
||||
*/
|
||||
public async onFollowRecButton(): Promise<void> {
|
||||
const content = this.translate.instant(
|
||||
`Are you sure you want to override the state of all motions of this motion block?`
|
||||
);
|
||||
if (await this.promptService.open(this.block.title, content)) {
|
||||
for (const motion of this.motions) {
|
||||
if (!motion.isInFinalState()) {
|
||||
this.motionRepo.setState(motion, motion.recommendation_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for the motion title cell in the table
|
||||
* Navigate to the motion that was clicked on
|
||||
*
|
||||
* @param motion the selected ViewMotion
|
||||
*/
|
||||
public onClickMotionTitle(motion: ViewMotion): void {
|
||||
this.router.navigate([`/motions/${motion.id}`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler to delete motion blocks
|
||||
*/
|
||||
public async onDeleteBlockButton(): Promise<void> {
|
||||
const content = this.translate.instant('Are you sure you want to delete this motion block?');
|
||||
if (await this.promptService.open(this.block.title, content)) {
|
||||
await this.repo.delete(this.block);
|
||||
this.router.navigate(['../'], { relativeTo: this.route });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for the delete button on the table
|
||||
*
|
||||
* @param motion the corresponding motion
|
||||
*/
|
||||
public async onRemoveMotionButton(motion: ViewMotion): Promise<void> {
|
||||
const content = this.translate.instant('Are you sure you want to remove this motion from motion block?');
|
||||
if (await this.promptService.open(motion.title, content)) {
|
||||
this.repo.removeMotionFromBlock(motion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicking escape while in editForm should deactivate edit mode.
|
||||
*
|
||||
* @param event The key that was pressed
|
||||
*/
|
||||
public onKeyDown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Escape') {
|
||||
this.editBlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if following the recommendations should be possible.
|
||||
* Following a recommendation implies, that a valid recommendation exists.
|
||||
*/
|
||||
public isFollowingProhibited(): boolean {
|
||||
if (this.motions) {
|
||||
return this.motions.every(motion => motion.isInFinalState() || !motion.recommendation_id);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save event handler
|
||||
*/
|
||||
public saveBlock(): void {
|
||||
this.editBlock = false;
|
||||
this.repo.update(this.blockEditForm.value as MotionBlock, this.block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for the edit button
|
||||
*/
|
||||
public toggleEditMode(): void {
|
||||
this.editBlock = !this.editBlock;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
<os-head-bar [nav]="false" [mainButton]="true" (mainEvent)="onPlusButton()">
|
||||
<!-- Title -->
|
||||
<div class="title-slot"><h2 translate>Motion blocks</h2></div>
|
||||
</os-head-bar>
|
||||
|
||||
<!-- Creating a new motion block -->
|
||||
<mat-card class="os-card" *ngIf="blockToCreate">
|
||||
<mat-card-title translate>Create new motion block</mat-card-title>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="createBlockForm">
|
||||
<!-- Title -->
|
||||
<p>
|
||||
<mat-form-field>
|
||||
<input
|
||||
formControlName="title"
|
||||
matInput
|
||||
placeholder="{{ 'Title' | translate }}"
|
||||
required
|
||||
/>
|
||||
<mat-error *ngIf="createBlockForm.get('title').hasError('required')" translate>
|
||||
A name is required
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
|
||||
<!-- Parent item -->
|
||||
<p>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
listname="{{ 'Parent Item' | translate }}"
|
||||
[form]="createBlockForm"
|
||||
[formControl]="createBlockForm.get('agenda_parent_id')"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
[InputListValues]="items"
|
||||
></os-search-value-selector>
|
||||
</p>
|
||||
|
||||
<!-- Visibility -->
|
||||
<mat-form-field>
|
||||
<mat-select formControlName="agenda_type" placeholder="{{ 'Agenda visibility' | translate }}">
|
||||
<mat-option *ngFor="let type of itemVisibility" [value]="type.key">
|
||||
<span>{{ type.name | translate }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
|
||||
<!-- Save and Cancel buttons -->
|
||||
<mat-card-actions>
|
||||
<button mat-button (click)="onSaveNewButton()"><span translate>Save</span></button>
|
||||
<button mat-button (click)="blockToCreate = null"><span translate>Cancel</span></button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
<!-- Table -->
|
||||
<mat-card class="os-card">
|
||||
<table class="os-headed-listview-table on-transition-fade" mat-table [dataSource]="dataSource" matSort>
|
||||
<!-- title column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <span translate>Name</span> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let block"> {{ block.title }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- amount column -->
|
||||
<ng-container matColumnDef="amount">
|
||||
<mat-header-cell *matHeaderCellDef> <span translate>Motions</span> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let block">
|
||||
<span class="os-amount-chip">{{ getMotionAmount(block.motionBlock) }}</span>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||
<mat-row (click)="onSelectRow(row)" *matRowDef="let row; columns: getColumnDefinition()"> </mat-row>
|
||||
</table>
|
||||
</mat-card>
|
@ -0,0 +1,11 @@
|
||||
.os-headed-listview-table {
|
||||
// Title
|
||||
.mat-column-title {
|
||||
flex: 3 0 0;
|
||||
}
|
||||
|
||||
// Amount
|
||||
.mat-column-amount {
|
||||
flex: 1 0 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MotionBlockListComponent } from './motion-block-list.component';
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
describe('MotionBlockListComponent', () => {
|
||||
let component: MotionBlockListComponent;
|
||||
let fixture: ComponentFixture<MotionBlockListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [MotionBlockListComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MotionBlockListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,169 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { DataStoreService } from 'app/core/services/data-store.service';
|
||||
import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service';
|
||||
import { ViewMotionBlock } from '../../models/view-motion-block';
|
||||
|
||||
/**
|
||||
* Table for the motion blocks
|
||||
*/
|
||||
@Component({
|
||||
selector: 'os-motion-block-list',
|
||||
templateUrl: './motion-block-list.component.html',
|
||||
styleUrls: ['./motion-block-list.component.scss']
|
||||
})
|
||||
export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBlock> implements OnInit {
|
||||
/**
|
||||
* Holds the create form
|
||||
*/
|
||||
public createBlockForm: FormGroup;
|
||||
|
||||
/**
|
||||
* The new motion block to create
|
||||
*/
|
||||
public blockToCreate: MotionBlock | null;
|
||||
|
||||
/**
|
||||
* Holds the agenda items to select the parent item
|
||||
*/
|
||||
public items: BehaviorSubject<Item[]>;
|
||||
|
||||
/**
|
||||
* Determine the default agenda visibility
|
||||
*/
|
||||
public defaultVisibility: number;
|
||||
|
||||
/**
|
||||
* Determine visibility states for the agenda that will be created implicitly
|
||||
*/
|
||||
public itemVisibility = itemVisibilityChoices;
|
||||
|
||||
/**
|
||||
* Constructor for the motion block list view
|
||||
*
|
||||
* @param titleService sets the title
|
||||
* @param translate translations
|
||||
* @param matSnackBar display errors in the snack bar
|
||||
* @param router routing to children
|
||||
* @param route determine the local route
|
||||
* @param repo the motion block repository
|
||||
* @param DS the dataStore
|
||||
* @param formBuilder creates forms
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private repo: MotionBlockRepositoryService,
|
||||
private DS: DataStoreService,
|
||||
private formBuilder: FormBuilder
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
|
||||
this.createBlockForm = this.formBuilder.group({
|
||||
title: ['', Validators.required],
|
||||
agenda_type: ['', Validators.required],
|
||||
agenda_parent_id: ['']
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe the agendaItems for changes.
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
super.setTitle('Motion Blocks');
|
||||
this.initTable();
|
||||
|
||||
this.items = new BehaviorSubject(this.DS.getAll(Item));
|
||||
|
||||
this.DS.changeObservable.subscribe(model => {
|
||||
if (model instanceof Item) {
|
||||
this.items.next(this.DS.getAll(Item));
|
||||
}
|
||||
});
|
||||
|
||||
this.repo.getViewModelListObservable().subscribe(newMotionblocks => {
|
||||
this.dataSource.data = newMotionblocks;
|
||||
});
|
||||
|
||||
this.repo.getDefaultAgendaVisibility().subscribe(visibility => (this.defaultVisibility = visibility));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the columns that should be shown in the table
|
||||
*
|
||||
* @returns an array of strings building the column definition
|
||||
*/
|
||||
public getColumnDefinition(): string[] {
|
||||
return ['title', 'amount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Action while clicking on a row. Navigate to the detail page of given block
|
||||
*
|
||||
* @param block the given motion block
|
||||
*/
|
||||
public onSelectRow(block: ViewMotionBlock): void {
|
||||
this.router.navigate([`${block.id}`], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
/**
|
||||
* return the amount of motions in a motion block
|
||||
*
|
||||
* @param motionBlock the block to determine the amount of motions for
|
||||
* @returns a number that indicates how many motions are in the given block
|
||||
*/
|
||||
public getMotionAmount(motionBlock: MotionBlock): number {
|
||||
return this.repo.getMotionAmountByBlock(motionBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function reset the form and set the default values
|
||||
*/
|
||||
public resetForm(): void {
|
||||
this.createBlockForm.reset();
|
||||
this.createBlockForm.get('agenda_type').setValue(this.defaultVisibility);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for the plus button
|
||||
*/
|
||||
public onPlusButton(): void {
|
||||
if (!this.blockToCreate) {
|
||||
this.resetForm();
|
||||
this.blockToCreate = new MotionBlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for the save button.
|
||||
* Sends the block to create to the repository and resets the form.
|
||||
*/
|
||||
public onSaveNewButton(): void {
|
||||
if (this.createBlockForm.valid) {
|
||||
const blockPatch = this.createBlockForm.value;
|
||||
if (!blockPatch.agenda_parent_id) {
|
||||
delete blockPatch.agenda_parent_id;
|
||||
}
|
||||
|
||||
this.blockToCreate.patchValues(blockPatch);
|
||||
this.repo.create(this.blockToCreate);
|
||||
this.resetForm();
|
||||
this.blockToCreate = null;
|
||||
}
|
||||
// set a form control as "touched" to trigger potential error messages
|
||||
this.createBlockForm.get('title').markAsTouched();
|
||||
}
|
||||
}
|
@ -274,8 +274,12 @@
|
||||
<div *ngIf="motion && !editMotion">
|
||||
<h4 translate>Category</h4>
|
||||
<mat-menu #categoryMenu='matMenu'>
|
||||
<button *ngFor='let category of categoryObserver.value' mat-menu-item
|
||||
(click)=setCategory(category.id)>{{ category }}
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngFor="let category of categoryObserver.value"
|
||||
(click)="setCategory(category.id)"
|
||||
>
|
||||
{{ category }}
|
||||
</button>
|
||||
<button mat-menu-item (click)=setCategory(null)>
|
||||
---
|
||||
@ -286,6 +290,27 @@
|
||||
</mat-basic-chip>
|
||||
</div>
|
||||
|
||||
<!-- Block -->
|
||||
<div *ngIf="motion && !editMotion">
|
||||
<h4 translate>Motion block</h4>
|
||||
|
||||
<mat-menu #blockMenu='matMenu'>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngFor="let block of blockObserver.value"
|
||||
(click)="setBlock(block.id)"
|
||||
>
|
||||
{{ block }}
|
||||
</button>
|
||||
<button mat-menu-item (click)="setBlock(null)">
|
||||
---
|
||||
</button>
|
||||
</mat-menu>
|
||||
<mat-basic-chip [matMenuTriggerFor]='blockMenu' class="grey">
|
||||
{{ motion.motion_block ? motion.motion_block : ('not set' | translate) }}
|
||||
</mat-basic-chip>
|
||||
</div>
|
||||
|
||||
<!-- Workflow -->
|
||||
<div *ngIf="editMotion">
|
||||
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
||||
|
@ -31,6 +31,7 @@ import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators';
|
||||
import { LocalPermissionsService } from '../../services/local-permissions.service';
|
||||
import { ViewCreateMotion } from '../../models/view-create-motion';
|
||||
import { CreateMotion } from '../../models/create-motion';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
|
||||
/**
|
||||
* Component for the motion detail view
|
||||
@ -178,6 +179,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
*/
|
||||
public supporterObserver: BehaviorSubject<User[]>;
|
||||
|
||||
/**
|
||||
* Subject for the motion blocks
|
||||
*/
|
||||
public blockObserver: BehaviorSubject<MotionBlock[]>;
|
||||
|
||||
/**
|
||||
* Determine if the name of supporters are visible
|
||||
*/
|
||||
@ -249,6 +255,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
this.supporterObserver = new BehaviorSubject(DS.getAll(User));
|
||||
this.categoryObserver = new BehaviorSubject(DS.getAll(Category));
|
||||
this.workflowObserver = new BehaviorSubject(DS.getAll(Workflow));
|
||||
this.blockObserver = new BehaviorSubject(DS.getAll(MotionBlock));
|
||||
|
||||
// Make sure the subjects are updated, when a new Model for the type arrives
|
||||
this.DS.changeObservable.subscribe(newModel => {
|
||||
@ -259,6 +266,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
this.categoryObserver.next(DS.getAll(Category));
|
||||
} else if (newModel instanceof Workflow) {
|
||||
this.workflowObserver.next(DS.getAll(Workflow));
|
||||
} else if (newModel instanceof MotionBlock) {
|
||||
this.blockObserver.next(DS.getAll(MotionBlock));
|
||||
}
|
||||
});
|
||||
// load config variables
|
||||
@ -762,6 +771,15 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
this.repo.setCatetory(this.motion, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the current motion to a motion block
|
||||
*
|
||||
* @param id Motion block id
|
||||
*/
|
||||
public setBlock(id: number): void {
|
||||
this.repo.setBlock(this.motion, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes the repository for changes in the motion recommender
|
||||
*/
|
||||
|
@ -72,7 +72,16 @@
|
||||
<ng-container matColumnDef="state">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<div *ngIf="motion.category" class="small"><mat-icon>device_hub</mat-icon>{{ motion.category }}</div>
|
||||
<div class="innerTable">
|
||||
<div class="small" *ngIf="motion.category">
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
{{ motion.category }}
|
||||
</div>
|
||||
<div class="small" *ngIf="motion.motion_block">
|
||||
<mat-icon>widgets</mat-icon>
|
||||
{{ motion.motion_block.title }}
|
||||
</div>
|
||||
</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
@ -117,6 +126,15 @@
|
||||
<mat-icon>device_hub</mat-icon>
|
||||
<span translate>Categories</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="blocks">
|
||||
<!-- possible icons:
|
||||
dashboard
|
||||
widgets
|
||||
view_module
|
||||
-->
|
||||
<mat-icon>widgets</mat-icon>
|
||||
<span translate>Motion blocks</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
|
||||
<mat-icon>account_balance</mat-icon>
|
||||
<span translate>Statute</span>
|
||||
@ -138,7 +156,7 @@
|
||||
<mat-icon>sort</mat-icon>
|
||||
<span translate>Move to agenda item</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setStatus(selectedRows))">
|
||||
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setStateOfMultiple(selectedRows))">
|
||||
<mat-icon>label</mat-icon>
|
||||
<span translate>Set status</span>
|
||||
</button>
|
||||
|
@ -35,7 +35,6 @@
|
||||
/** State */
|
||||
.mat-column-state {
|
||||
flex: 0 0 160px;
|
||||
justify-content:flex-end !important;
|
||||
|
||||
mat-icon {
|
||||
font-size: 150%;
|
||||
|
39
client/src/app/site/motions/models/view-motion-block.ts
Normal file
39
client/src/app/site/motions/models/view-motion-block.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
|
||||
/**
|
||||
* ViewModel for motion blocks.
|
||||
* @ignore
|
||||
*/
|
||||
export class ViewMotionBlock extends BaseViewModel {
|
||||
private _motionBlock: MotionBlock;
|
||||
|
||||
public get motionBlock(): MotionBlock {
|
||||
return this._motionBlock;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.motionBlock ? this.motionBlock.id : null;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.motionBlock ? this.motionBlock.title : null;
|
||||
}
|
||||
|
||||
public get agenda_item_id(): number {
|
||||
return this.motionBlock ? this.motionBlock.agenda_item_id : null;
|
||||
}
|
||||
|
||||
public constructor(motionBlock: MotionBlock) {
|
||||
super();
|
||||
this._motionBlock = motionBlock;
|
||||
}
|
||||
|
||||
public updateValues(update: MotionBlock): void {
|
||||
this._motionBlock = update;
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return this.title
|
||||
}
|
||||
}
|
@ -347,6 +347,13 @@ export class ViewMotion extends BaseViewModel {
|
||||
return !!this.statute_paragraph_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the motion is in its final workflow state
|
||||
*/
|
||||
public isInFinalState(): boolean {
|
||||
return this.nextStates.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a paragraph-based amendments if only one paragraph is to be changed,
|
||||
* specified by amendment_paragraphs-array
|
||||
|
@ -8,6 +8,8 @@ import { StatuteParagraphListComponent } from './components/statute-paragraph-li
|
||||
import { SpeakerListComponent } from '../agenda/components/speaker-list/speaker-list.component';
|
||||
import { CallListComponent } from './components/call-list/call-list.component';
|
||||
import { AmendmentCreateWizardComponent } from './components/amendment-create-wizard/amendment-create-wizard.component';
|
||||
import { MotionBlockListComponent } from './components/motion-block-list/motion-block-list.component';
|
||||
import { MotionBlockDetailComponent } from './components/motion-block-detail/motion-block-detail.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: MotionListComponent },
|
||||
@ -15,6 +17,8 @@ const routes: Routes = [
|
||||
{ path: 'comment-section', component: MotionCommentSectionListComponent },
|
||||
{ path: 'statute-paragraphs', component: StatuteParagraphListComponent },
|
||||
{ path: 'call-list', component: CallListComponent },
|
||||
{ path: 'blocks', component: MotionBlockListComponent },
|
||||
{ path: 'blocks/:id', component: MotionBlockDetailComponent },
|
||||
{ path: 'new', component: MotionDetailComponent },
|
||||
{ path: ':id', component: MotionDetailComponent },
|
||||
{ path: ':id/speakers', component: SpeakerListComponent },
|
||||
|
@ -16,6 +16,8 @@ import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-b
|
||||
import { PersonalNoteComponent } from './components/personal-note/personal-note.component';
|
||||
import { CallListComponent } from './components/call-list/call-list.component';
|
||||
import { AmendmentCreateWizardComponent } from './components/amendment-create-wizard/amendment-create-wizard.component';
|
||||
import { MotionBlockListComponent } from './components/motion-block-list/motion-block-list.component';
|
||||
import { MotionBlockDetailComponent } from './components/motion-block-detail/motion-block-detail.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
||||
@ -32,7 +34,9 @@ import { AmendmentCreateWizardComponent } from './components/amendment-create-wi
|
||||
MetaTextBlockComponent,
|
||||
PersonalNoteComponent,
|
||||
CallListComponent,
|
||||
AmendmentCreateWizardComponent
|
||||
AmendmentCreateWizardComponent,
|
||||
MotionBlockListComponent,
|
||||
MotionBlockDetailComponent
|
||||
],
|
||||
entryComponents: [
|
||||
MotionChangeRecommendationComponent,
|
||||
|
@ -0,0 +1,15 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MotionBlockRepositoryService } from './motion-block-repository.service';
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
describe('MotionBlockRepositoryService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: MotionBlockRepositoryService = TestBed.get(MotionBlockRepositoryService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,127 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
import { ViewMotionBlock } from '../models/view-motion-block';
|
||||
import { BaseRepository } from 'app/site/base/base-repository';
|
||||
import { DataStoreService } from 'app/core/services/data-store.service';
|
||||
import { CollectionStringModelMapperService } from 'app/core/services/collectionStringModelMapper.service';
|
||||
import { DataSendService } from 'app/core/services/data-send.service';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { Motion } from 'app/shared/models/motions/motion';
|
||||
import { ViewMotion } from '../models/view-motion';
|
||||
import { Observable } from 'rxjs';
|
||||
import { MotionRepositoryService } from './motion-repository.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ConfigService } from 'app/core/services/config.service';
|
||||
|
||||
/**
|
||||
* Repository service for motion blocks
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock, MotionBlock> {
|
||||
/**
|
||||
* Constructor for the motion block repository
|
||||
*
|
||||
* @param DS Data Store
|
||||
* @param mapperService Mapping collection strings to classes
|
||||
* @param dataSend Send models to the server
|
||||
* @param motionRepo Accessing the motion repository
|
||||
* @param config To access config variables
|
||||
*/
|
||||
public constructor(
|
||||
DS: DataStoreService,
|
||||
mapperService: CollectionStringModelMapperService,
|
||||
private dataSend: DataSendService,
|
||||
private motionRepo: MotionRepositoryService,
|
||||
private config: ConfigService
|
||||
) {
|
||||
super(DS, mapperService, MotionBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a given motion block
|
||||
*
|
||||
* @param update a partial motion block containing the update data
|
||||
* @param viewBlock the motion block to update
|
||||
*/
|
||||
public async update(update: Partial<MotionBlock>, viewBlock: ViewMotionBlock): Promise<void> {
|
||||
const updateMotionBlock = new MotionBlock();
|
||||
updateMotionBlock.patchValues(viewBlock.motionBlock);
|
||||
updateMotionBlock.patchValues(update);
|
||||
return await this.dataSend.updateModel(updateMotionBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a motion block from the server
|
||||
*
|
||||
* @param newBlock the motion block to delete
|
||||
*/
|
||||
public async delete(newBlock: ViewMotionBlock): Promise<void> {
|
||||
return await this.dataSend.deleteModel(newBlock.motionBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new motion block to the server
|
||||
*
|
||||
* @param newBlock The new block to create
|
||||
* @returns the ID of the created model as promise
|
||||
*/
|
||||
public async create(newBlock: MotionBlock): Promise<Identifiable> {
|
||||
return await this.dataSend.createModel(newBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given motion block into a ViewModel
|
||||
*
|
||||
* @param block a motion block
|
||||
* @returns a new ViewMotionBlock
|
||||
*/
|
||||
protected createViewModel(block: MotionBlock): ViewMotionBlock {
|
||||
return new ViewMotionBlock(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the motion block id from the given motion
|
||||
*
|
||||
* @param viewMotion The motion to alter
|
||||
*/
|
||||
public removeMotionFromBlock(viewMotion: ViewMotion): void {
|
||||
const updateMotion = viewMotion.motion;
|
||||
updateMotion.motion_block_id = null;
|
||||
this.motionRepo.update(updateMotion, viewMotion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the DataStore by Motions and returns the
|
||||
*
|
||||
* @param block the motion block
|
||||
* @returns the number of motions inside a motion block
|
||||
*/
|
||||
public getMotionAmountByBlock(block: MotionBlock): number {
|
||||
return this.DS.filter(Motion, motion => motion.motion_block_id === block.id).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agenda visibility from the config
|
||||
*
|
||||
* @return An observable to the default agenda visibility
|
||||
*/
|
||||
public getDefaultAgendaVisibility(): Observable<number> {
|
||||
return this.config.get('agenda_new_items_default_visibility').pipe(map(key => +key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe the motion repository and return the motions belonging to the given
|
||||
* block as observable
|
||||
*
|
||||
* @param block a motion block
|
||||
* @returns an observable to view motions
|
||||
*/
|
||||
public getViewMotionsByBlock(block: MotionBlock): Observable<ViewMotion[]> {
|
||||
return this.motionRepo
|
||||
.getViewModelListObservable()
|
||||
.pipe(map(viewMotions => viewMotions.filter(viewMotion => viewMotion.motion_block_id === block.id)));
|
||||
}
|
||||
}
|
@ -82,7 +82,7 @@ export class MotionMultiselectService {
|
||||
*
|
||||
* @param motions The motions to change
|
||||
*/
|
||||
public async setStatus(motions: ViewMotion[]): Promise<void> {
|
||||
public async setStateOfMultiple(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('This will set the state of all selected motions to:');
|
||||
const choices = this.workflowRepo.getAllWorkflowStates().map(workflowState => ({
|
||||
id: workflowState.id,
|
||||
|
@ -26,6 +26,7 @@ import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree
|
||||
import { TreeService } from 'app/core/services/tree.service';
|
||||
import { ViewMotionAmendedParagraph } from '../models/view-motion-amended-paragraph';
|
||||
import { CreateMotion } from '../models/create-motion';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
|
||||
/**
|
||||
* Repository Services for motions (and potentially categories)
|
||||
@ -41,7 +42,6 @@ import { CreateMotion } from '../models/create-motion';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion> {
|
||||
|
||||
/**
|
||||
* Creates a MotionRepository
|
||||
*
|
||||
@ -64,7 +64,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
private readonly diff: DiffService,
|
||||
private treeService: TreeService
|
||||
) {
|
||||
super(DS, mapperService, Motion, [Category, User, Workflow, Item]);
|
||||
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,11 +81,12 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
const supporters = this.DS.getMany(User, motion.supporters_id);
|
||||
const workflow = this.DS.get(Workflow, motion.workflow_id);
|
||||
const item = this.DS.get(Item, motion.agenda_item_id);
|
||||
const block = this.DS.get(MotionBlock, motion.motion_block_id);
|
||||
let state: WorkflowState = null;
|
||||
if (workflow) {
|
||||
state = workflow.getStateById(motion.state_id);
|
||||
}
|
||||
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item);
|
||||
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item, block);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,6 +180,18 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
await this.update(motion, viewMotion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the motion to a motion block
|
||||
*
|
||||
* @param viewMotion the motion to add
|
||||
* @param blockId the ID of the motion block
|
||||
*/
|
||||
public async setBlock(viewMotion: ViewMotion, blockId: number): Promise<void> {
|
||||
const motion = viewMotion.motion;
|
||||
motion.motion_block_id = blockId;
|
||||
await this.update(motion, viewMotion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the changed nodes to the server.
|
||||
*
|
||||
|
@ -16,6 +16,29 @@
|
||||
|
||||
@import '~angular-tree-component/dist/angular-tree-component.css';
|
||||
|
||||
// Shared scss definitions
|
||||
%os-table {
|
||||
width: 100%;
|
||||
|
||||
/** size of the mat row */
|
||||
mat-row {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
mat-row:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
|
||||
mat-row.selected {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.055);
|
||||
}
|
||||
mat-row.lg {
|
||||
height: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: Roboto, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
@ -98,29 +121,16 @@ body {
|
||||
}
|
||||
|
||||
.os-listview-table {
|
||||
width: 100%;
|
||||
@extend %os-table;
|
||||
|
||||
/** hide mat header row */
|
||||
.mat-header-row {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/** size of the mat row */
|
||||
mat-row {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
mat-row:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
mat-row.selected {
|
||||
cursor: pointer;
|
||||
background-color: rgba(0, 0, 0, 0.055);
|
||||
}
|
||||
mat-row.lg {
|
||||
height: 90px;
|
||||
}
|
||||
.os-headed-listview-table {
|
||||
@extend %os-table;
|
||||
}
|
||||
|
||||
.card-plus-distance {
|
||||
@ -204,6 +214,18 @@ mat-panel-title mat-icon {
|
||||
margin: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
// to display quantities. Use in span or div
|
||||
.os-amount-chip {
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
padding: 3px;
|
||||
background: lightgray;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mat-chip:focus,
|
||||
.mat-basic-chip:focus {
|
||||
outline: none;
|
||||
|
Loading…
Reference in New Issue
Block a user