rework category list and sorting
This commit is contained in:
parent
841d80a35b
commit
66f9c81564
@ -11,7 +11,6 @@ import { DataSendService } from '../../core-services/data-send.service';
|
|||||||
import { DataStoreService } from '../../core-services/data-store.service';
|
import { DataStoreService } from '../../core-services/data-store.service';
|
||||||
import { HttpService } from '../../core-services/http.service';
|
import { HttpService } from '../../core-services/http.service';
|
||||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||||
import { Motion } from 'app/shared/models/motions/motion';
|
|
||||||
import { ViewCategory } from 'app/site/motions/models/view-category';
|
import { ViewCategory } from 'app/site/motions/models/view-category';
|
||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
|
|
||||||
@ -81,22 +80,6 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
|
|||||||
await this.dataSend.deleteModel(category);
|
await this.dataSend.deleteModel(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all motions belonging to a category
|
|
||||||
* @param category category
|
|
||||||
*/
|
|
||||||
public getMotionsOfCategory(category: Category): Motion[] {
|
|
||||||
const motList = this.DS.getAll(Motion);
|
|
||||||
const retList: Array<Motion> = [];
|
|
||||||
motList.forEach(motion => {
|
|
||||||
if (motion.category_id && motion.category_id === category.id) {
|
|
||||||
retList.push(motion);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// TODO: Sorting the return List?!
|
|
||||||
return retList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the category for the ID
|
* Returns the category for the ID
|
||||||
* @param category_id category ID
|
* @param category_id category ID
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
<div cdkDropList class="list" (cdkDropListDropped)="drop($event)">
|
<div cdkDropList class="os-card" (cdkDropListDropped)="drop($event)">
|
||||||
|
<div class= "box line" *ngIf="!array.length">
|
||||||
|
<span translate>No data</span>
|
||||||
|
</div>
|
||||||
<div class="box line" *ngFor="let item of array; let i = index" cdkDrag>
|
<div class="box line" *ngFor="let item of array; let i = index" cdkDrag>
|
||||||
<div class="section-one" cdkDragHandle>
|
<div class="section-one" cdkDragHandle>
|
||||||
<!-- TODO: drag_indicator after icon update -->
|
<!-- TODO: drag_indicator after icon update -->
|
||||||
<mat-icon>unfold_more</mat-icon>
|
<mat-icon>unfold_more</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="section-two">
|
<div class="section-two">
|
||||||
<!-- {number}. {item.toString()} -->
|
<!-- {number}. {item.getTitle()} -->
|
||||||
<span *ngIf="count">{{ i+1 }}. </span>
|
<span *ngIf="count">{{ i + 1 }}. </span>
|
||||||
<span>{{ item }}</span>
|
<span>{{ item.getTitle() }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="section-three">
|
<div class="section-three">
|
||||||
<!-- Extra controls slot using implicit template references -->
|
<!-- Extra controls slot using implicit template references -->
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
.list {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: solid 1px #ccc;
|
border-bottom: solid 1px #ccc;
|
||||||
|
@ -5,7 +5,6 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { Selectable } from '../selectable';
|
import { Selectable } from '../selectable';
|
||||||
import { EmptySelectable } from '../empty-selectable';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable Sorting List
|
* Reusable Sorting List
|
||||||
@ -133,8 +132,8 @@ export class SortingListComponent implements OnInit, OnDestroy {
|
|||||||
if (this.array.length !== newValues.length || this.live) {
|
if (this.array.length !== newValues.length || this.live) {
|
||||||
this.array = [];
|
this.array = [];
|
||||||
this.array = newValues.map(val => val);
|
this.array = newValues.map(val => val);
|
||||||
} else if (this.array.length === 0) {
|
} else {
|
||||||
this.array.push(new EmptySelectable(this.translate));
|
this.array = this.array.map(arrayValue => newValues.find(val => val.id === arrayValue.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,27 +4,28 @@
|
|||||||
<h2 translate>Categories</h2>
|
<h2 translate>Categories</h2>
|
||||||
</div>
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
<div class="custom-table-header"></div>
|
|
||||||
|
|
||||||
<!-- Creating a new category -->
|
<div class="spacer-top-20"></div>
|
||||||
<mat-card *ngIf="categoryToCreate">
|
<mat-card class="os-card" *ngIf="categoryToCreate">
|
||||||
<mat-card-title translate>Create new category</mat-card-title>
|
<mat-card-title translate>Create new category</mat-card-title>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<form [formGroup]="createForm" (keydown)="keyDownFunction($event)">
|
<form
|
||||||
<p>
|
class="full-width-form flex-spaced"
|
||||||
<!-- Prefix field -->
|
id="createForm"
|
||||||
<mat-form-field>
|
[formGroup]="createForm"
|
||||||
<input formControlName="prefix" matInput placeholder="{{'Prefix' | translate}}">
|
(keydown)="keyDownFunction($event)"
|
||||||
|
>
|
||||||
|
<!-- prefix input -->
|
||||||
|
<mat-form-field class="short-input">
|
||||||
|
<input formControlName="prefix" matInput placeholder="{{ 'Prefix' | translate }}" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<!-- name input -->
|
||||||
<!-- Name field -->
|
<mat-form-field class="long-input">
|
||||||
<mat-form-field>
|
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
||||||
<input formControlName="name" matInput placeholder="{{'Name' | translate}}" required>
|
<mat-hint *ngIf="!updateForm.controls.name.valid">
|
||||||
<mat-hint *ngIf="!createForm.controls.name.valid">
|
|
||||||
<span translate>Required</span>
|
<span translate>Required</span>
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|
||||||
@ -39,28 +40,22 @@
|
|||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
<mat-accordion class="os-form-card">
|
<mat-card class="os-card">
|
||||||
<mat-expansion-panel *ngFor="let category of categories" (opened)="openId = category.id" (closed)="panelClosed(category)"
|
<mat-accordion displayMode="flat">
|
||||||
[expanded]="openId === category.id" multiple="false">
|
<ng-container *ngFor="let category of categories">
|
||||||
|
<mat-expansion-panel
|
||||||
|
class="os-card-expandion-panel"
|
||||||
|
(opened)="setValues(category)"
|
||||||
|
[expanded]="editId === category.id"
|
||||||
|
(closed)="onCancelButton()"
|
||||||
|
>
|
||||||
<!-- Header shows Prefix and name -->
|
<!-- Header shows Prefix and name -->
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<div class="header-prefix">
|
|
||||||
<div *ngIf="editId !== category.id">
|
|
||||||
{{ category.prefix }}
|
|
||||||
</div>
|
|
||||||
<div *ngIf="editId === category.id">
|
|
||||||
{{ updateForm.get('prefix').value }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="header-name">
|
<div class="header-name">
|
||||||
<div *ngIf="editId !== category.id">
|
<div>
|
||||||
{{ category.name }}
|
{{ category.prefixedName }}
|
||||||
</div>
|
|
||||||
<div *ngIf="editId === category.id">
|
|
||||||
{{ updateForm.get('name').value }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-size os-amount-chip">
|
<div class="header-size os-amount-chip">
|
||||||
@ -71,48 +66,68 @@
|
|||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
<!-- Edit form shows during the edit event -->
|
<!-- Edit form shows during the edit event -->
|
||||||
<form id="updateForm" [formGroup]='updateForm' *ngIf="editId === category.id" (keydown)="keyDownFunction($event, category)">
|
<div class="full-width-form">
|
||||||
<span translate>Edit category</span>:<br>
|
<form
|
||||||
|
class="full-width-form"
|
||||||
<mat-form-field>
|
id="updateForm"
|
||||||
<input formControlName="prefix" matInput placeholder="{{'Prefix' | translate}}">
|
[formGroup]="updateForm"
|
||||||
|
*ngIf="editId === category.id"
|
||||||
|
(keydown)="keyDownFunction($event, category)"
|
||||||
|
>
|
||||||
|
<div class="flex-spaced">
|
||||||
|
<mat-form-field class="short-input">
|
||||||
|
<input formControlName="prefix" matInput placeholder="{{ 'Prefix' | translate }}" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field class="long-input">
|
||||||
<input formControlName="name" matInput placeholder="{{'Name' | translate}}" required>
|
<input
|
||||||
|
formControlName="name"
|
||||||
|
matInput
|
||||||
|
placeholder="{{ 'Name' | translate }}"
|
||||||
|
required
|
||||||
|
/>
|
||||||
<mat-hint *ngIf="!updateForm.controls.name.valid">
|
<mat-hint *ngIf="!updateForm.controls.name.valid">
|
||||||
<span translate>Required</span>
|
<span translate>Required</span>
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="inline-form-submit" *osPerms="'motions.can_manage'">
|
||||||
|
<button
|
||||||
|
[disabled]="!updateForm.dirty"
|
||||||
|
mat-button
|
||||||
|
class="on-transition-fade"
|
||||||
|
(click)="onSaveButton(category)"
|
||||||
|
>
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
mat-button
|
||||||
|
class="on-transition-fade"
|
||||||
|
[routerLink]="getSortUrl(category)"
|
||||||
|
>
|
||||||
|
<span translate>Sort motions</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
mat-button
|
||||||
|
class="on-transition-fade"
|
||||||
|
(click)="onDeleteButton(category)"
|
||||||
|
>
|
||||||
|
<span translate>Delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
<!-- Show and sort corresponding motions-->
|
<!-- Show and sort corresponding motions-->
|
||||||
<div *ngIf="motionsInCategory(category).length > 0">
|
<div *ngIf="motionsInCategory(category).length > 0">
|
||||||
<span translate>Motions</span>:
|
<span translate>Motions</span>:
|
||||||
<div *ngIf="editId !== category.id">
|
<div>
|
||||||
<ul *ngFor="let motion of motionsInCategory(category)">
|
<ul *ngFor="let motion of motionsInCategory(category)">
|
||||||
<li>{{ motion }}</li>
|
<li class="ellipsis-overflow">{{ motion.getListTitle() }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="editId === category.id" class="half-width">
|
|
||||||
<os-sorting-list [input]="motionsInCategory(category)" #sorter></os-sorting-list>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Buttons to edit, delete, save ... -->
|
|
||||||
<mat-action-row>
|
|
||||||
<button mat-icon-button *ngIf="editId !== category.id" class='on-transition-fade' (click)=onEditButton(category)>
|
|
||||||
<mat-icon>edit</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button *ngIf="editId === category.id" class='on-transition-fade' (click)=onCancelButton()>
|
|
||||||
<mat-icon>close</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button *ngIf="editId === category.id" class='on-transition-fade' (click)=onSaveButton(category)>
|
|
||||||
<mat-icon>save</mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button mat-button class='on-transition-fade' (click)=onDeleteButton(category)>
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
</button>
|
|
||||||
</mat-action-row>
|
|
||||||
|
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</ng-container>
|
||||||
|
</mat-accordion>
|
||||||
|
</mat-card>
|
||||||
|
@ -1,33 +1,50 @@
|
|||||||
.header-container {
|
.header-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
grid-template-columns: 33.333% 33.333% 33.333%;
|
grid-template-columns: 75% 25%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
grid-row-start: 1;
|
grid-row-start: 1;
|
||||||
grid-row-end: span 1;
|
grid-row-end: span 1;
|
||||||
grid-column-end: span 3;
|
grid-column-end: span 2;
|
||||||
}
|
|
||||||
|
|
||||||
.header-prefix {
|
|
||||||
grid-column-start: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-name {
|
.header-name {
|
||||||
grid-column-start: 2;
|
grid-column-start: 1;
|
||||||
color: lightslategray;
|
color: lightslategray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-size {
|
.header-size {
|
||||||
grid-column-start: 3;
|
grid-column-start: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateForm {
|
mat-expansion-panel {
|
||||||
margin-bottom: 20px;
|
max-width: 770px;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.half-width {
|
.flex-spaced {
|
||||||
width: 50%;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width-form {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-content: space-between;
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.short-input {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
.long-input {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
.inline-form-submit {
|
||||||
|
justify-content: end;
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseViewComponent } from '../../../base/base-view';
|
||||||
import { Category } from 'app/shared/models/motions/category';
|
import { Category } from 'app/shared/models/motions/category';
|
||||||
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
|
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
|
||||||
import { ViewCategory } from '../../models/view-category';
|
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
|
||||||
import { Motion } from 'app/shared/models/motions/motion';
|
|
||||||
import { SortingListComponent } from 'app/shared/components/sorting-list/sorting-list.component';
|
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { BaseViewComponent } from '../../../base/base-view';
|
import { ViewCategory } from '../../models/view-category';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { ViewMotion } from '../../models/view-motion';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List view for the categories.
|
* List view for the categories.
|
||||||
@ -28,15 +28,10 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
public categoryToCreate: Category | null;
|
public categoryToCreate: Category | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine which category to edit
|
* Determine which category is opened
|
||||||
*/
|
*/
|
||||||
public editId: number | null;
|
public editId: number | null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine which category is opened.
|
|
||||||
*/
|
|
||||||
public openId: number | null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source of the data
|
* Source of the data
|
||||||
*/
|
*/
|
||||||
@ -52,12 +47,6 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public updateForm: FormGroup;
|
public updateForm: FormGroup;
|
||||||
|
|
||||||
/**
|
|
||||||
* The MultiSelect Component
|
|
||||||
*/
|
|
||||||
@ViewChild('sorter')
|
|
||||||
public sortSelector: SortingListComponent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The usual component constructor
|
* The usual component constructor
|
||||||
* @param titleService
|
* @param titleService
|
||||||
@ -72,6 +61,7 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private repo: CategoryRepositoryService,
|
private repo: CategoryRepositoryService,
|
||||||
|
private motionRepo: MotionRepositoryService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private promptService: PromptService
|
private promptService: PromptService
|
||||||
) {
|
) {
|
||||||
@ -89,7 +79,8 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event on key-down in form
|
* Event on key-down in form. Submits the current form if the 'enter' button is pressed
|
||||||
|
*
|
||||||
* @param event
|
* @param event
|
||||||
* @param viewCategory
|
* @param viewCategory
|
||||||
*/
|
*/
|
||||||
@ -154,47 +145,33 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the category
|
* Saves a category
|
||||||
*
|
* TODO: Some feedback
|
||||||
* TODO: Do not number the motions. This needs to be a separate button (maybe with propting for confirmation), because
|
|
||||||
* not every body uses this and this would destroy their own order in motion identifiers.
|
|
||||||
* See issue #3969
|
|
||||||
*
|
*
|
||||||
* @param viewCategory
|
* @param viewCategory
|
||||||
*/
|
*/
|
||||||
public async onSaveButton(viewCategory: ViewCategory): Promise<void> {
|
public async onSaveButton(viewCategory: ViewCategory): Promise<void> {
|
||||||
// get the sorted motions. Save them before updating the category.
|
if (this.updateForm.dirty && this.updateForm.valid) {
|
||||||
let sortedMotionIds;
|
|
||||||
if (this.sortSelector) {
|
|
||||||
sortedMotionIds = this.sortSelector.array.map(selectable => selectable.id);
|
|
||||||
this.repo.numberMotionsInCategory(viewCategory.category, sortedMotionIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.updateForm.valid) {
|
|
||||||
const cat: Partial<Category> = { name: this.updateForm.get('name').value };
|
const cat: Partial<Category> = { name: this.updateForm.get('name').value };
|
||||||
if (this.updateForm.get('prefix').value) {
|
if (this.updateForm.get('prefix').value) {
|
||||||
cat.prefix = this.updateForm.get('prefix').value;
|
cat.prefix = this.updateForm.get('prefix').value;
|
||||||
}
|
}
|
||||||
// wait for the category to update; then the (maybe) changed prefix can be applied to the motions
|
|
||||||
await this.repo.update(cat, viewCategory);
|
await this.repo.update(cat, viewCategory);
|
||||||
this.onCancelButton();
|
this.updateForm.markAsPristine();
|
||||||
|
|
||||||
if (this.sortSelector) {
|
|
||||||
this.repo.numberMotionsInCategory(viewCategory.category, sortedMotionIds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* executed on cancel button
|
* Trigger after cancelling an edit. The updateForm is reset to an original
|
||||||
* @param viewCategory
|
* value, which might belong to a different category
|
||||||
*/
|
*/
|
||||||
public onCancelButton(): void {
|
public onCancelButton(): void {
|
||||||
this.editId = null;
|
this.updateForm.markAsPristine();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is executed, when the delete button is pressed
|
* is executed, when the delete button is pressed
|
||||||
|
*
|
||||||
* @param viewCategory The category to delete
|
* @param viewCategory The category to delete
|
||||||
*/
|
*/
|
||||||
public async onDeleteButton(viewCategory: ViewCategory): Promise<void> {
|
public async onDeleteButton(viewCategory: ViewCategory): Promise<void> {
|
||||||
@ -206,23 +183,36 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the motions corresponding to a category
|
* Returns the motions corresponding to a category
|
||||||
|
*
|
||||||
* @param category target
|
* @param category target
|
||||||
* @returns all motions in the category
|
* @returns all motions in the category
|
||||||
*/
|
*/
|
||||||
public motionsInCategory(category: Category): Motion[] {
|
public motionsInCategory(category: Category): ViewMotion[] {
|
||||||
const motions = this.repo.getMotionsOfCategory(category);
|
return this.motionRepo
|
||||||
motions.sort((motion1, motion2) => (motion1 > motion2 ? 1 : -1));
|
.getViewModelList()
|
||||||
return motions;
|
.filter(m => m.category_id === category.id)
|
||||||
|
.sort((motion1, motion2) => motion1.identifier.localeCompare(motion2.identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is executed when a mat-extension-panel is closed
|
* Fetch the correct URL for a detail sort view
|
||||||
* @param viewCategory the category in the panel
|
*
|
||||||
|
* @param viewCategory
|
||||||
*/
|
*/
|
||||||
public panelClosed(viewCategory: ViewCategory): void {
|
public getSortUrl(viewCategory: ViewCategory): string {
|
||||||
this.openId = null;
|
return `/motions/category/${viewCategory.id}`;
|
||||||
if (this.editId) {
|
|
||||||
this.onSaveButton(viewCategory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set/reset the initial values and the referenced category of the update form
|
||||||
|
*
|
||||||
|
* @param category
|
||||||
|
*/
|
||||||
|
public setValues(category: ViewCategory): void {
|
||||||
|
this.editId = category.id;
|
||||||
|
this.updateForm.setValue({
|
||||||
|
prefix: category.prefix,
|
||||||
|
name: category.name
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<!-- TODO permission -->
|
||||||
|
<os-head-bar [nav]="false">
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="title-slot"><h2 translate>Sort motions</h2></div>
|
||||||
|
</os-head-bar>
|
||||||
|
|
||||||
|
<mat-card class="os-form-card">
|
||||||
|
<h3>{{ categoryName }}</h3>
|
||||||
|
<br />
|
||||||
|
<span translate>
|
||||||
|
Drag and drop motions to reorder the category. Then click the button to renumber.
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<button
|
||||||
|
mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
(click)="onNumberMotions()"
|
||||||
|
class="spacer-top-10"
|
||||||
|
[disabled]="!motionsCount"
|
||||||
|
>
|
||||||
|
<span translate>Number motions</span>
|
||||||
|
</button>
|
||||||
|
<os-sorting-list [input]="motionObservable" #sorter></os-sorting-list>
|
||||||
|
</mat-card>
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CategorySortComponent } from './category-sort.component';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('CategorySortComponent', () => {
|
||||||
|
let component: CategorySortComponent;
|
||||||
|
let fixture: ComponentFixture<CategorySortComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [CategorySortComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CategorySortComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,119 @@
|
|||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
|
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
|
import { SortingListComponent } from 'app/shared/components/sorting-list/sorting-list.component';
|
||||||
|
import { ViewCategory } from '../../models/view-category';
|
||||||
|
import { ViewMotion } from '../../models/view-motion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View for rearranging and renumbering the motions of a category. The {@link onNumberMotions}
|
||||||
|
* method sends a request to the server to re-number the given motions in the order
|
||||||
|
* as displayed in this view
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-category-sort',
|
||||||
|
templateUrl: './category-sort.component.html',
|
||||||
|
styleUrls: ['./category-sort.component.scss']
|
||||||
|
})
|
||||||
|
export class CategorySortComponent extends BaseViewComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The current category. Determined by the route
|
||||||
|
*/
|
||||||
|
public category: ViewCategory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A behaviorSubject emitting the currently asigned motions on change
|
||||||
|
*/
|
||||||
|
public motionsSubject = new BehaviorSubject<ViewMotion[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter indicating the amount of motions currently in the category
|
||||||
|
*/
|
||||||
|
public motionsCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns an observable for the {@link motionsSubject}
|
||||||
|
*/
|
||||||
|
public get motionObservable(): Observable<ViewMotion[]> {
|
||||||
|
return this.motionsSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns the name and (if present) prefix of the category
|
||||||
|
*/
|
||||||
|
public get categoryName(): string {
|
||||||
|
if (!this.category) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return this.category.prefix ? `${this.category.name} (${this.category.prefix})` : this.category.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Sort Component
|
||||||
|
*/
|
||||||
|
@ViewChild('sorter')
|
||||||
|
public sortSelector: SortingListComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. Calls parents
|
||||||
|
*
|
||||||
|
* @param title
|
||||||
|
* @param translate
|
||||||
|
* @param matSnackBar
|
||||||
|
* @param promptService
|
||||||
|
* @param repo
|
||||||
|
* @param route
|
||||||
|
* @param motionRepo
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
title: Title,
|
||||||
|
translate: TranslateService,
|
||||||
|
matSnackBar: MatSnackBar,
|
||||||
|
private promptService: PromptService,
|
||||||
|
private repo: CategoryRepositoryService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private motionRepo: MotionRepositoryService
|
||||||
|
) {
|
||||||
|
super(title, translate, matSnackBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to the category and motions of this category.
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
const category_id: number = +this.route.snapshot.params.id;
|
||||||
|
this.repo.getViewModelObservable(category_id).subscribe(cat => {
|
||||||
|
this.category = cat;
|
||||||
|
});
|
||||||
|
this.motionRepo.getViewModelListObservable().subscribe(motions => {
|
||||||
|
const filtered = motions.filter(m => m.category_id === category_id);
|
||||||
|
this.motionsCount = filtered.length;
|
||||||
|
this.motionsSubject.next(filtered);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a (re-)numbering of the motions after a configmarion dialog
|
||||||
|
*
|
||||||
|
* @param category
|
||||||
|
*/
|
||||||
|
public async onNumberMotions(): Promise<void> {
|
||||||
|
if (this.sortSelector) {
|
||||||
|
const content = this.translate.instant('This will change the identifier for the motions of this category.');
|
||||||
|
if (await this.promptService.open('Are you sure?', content)) {
|
||||||
|
const sortedMotionIds = this.sortSelector.array.map(selectable => selectable.id);
|
||||||
|
await this.repo
|
||||||
|
.numberMotionsInCategory(this.category.category, sortedMotionIds)
|
||||||
|
.then(null, this.raiseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- submitters line -->
|
<!-- submitters line -->
|
||||||
<div class="motion-list">
|
<div class="motion-list">
|
||||||
<span class="motion-list-from" *ngIf="motion.submitters.length">
|
<span class="motion-list-from ellipsis-overflow" *ngIf="motion.submitters.length">
|
||||||
<span translate>by</span> {{ motion.submitters }}
|
<span translate>by</span> {{ motion.submitters }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,6 +4,7 @@ import { Routes, RouterModule } from '@angular/router';
|
|||||||
import { AmendmentCreateWizardComponent } from './components/amendment-create-wizard/amendment-create-wizard.component';
|
import { AmendmentCreateWizardComponent } from './components/amendment-create-wizard/amendment-create-wizard.component';
|
||||||
import { CallListComponent } from './components/call-list/call-list.component';
|
import { CallListComponent } from './components/call-list/call-list.component';
|
||||||
import { CategoryListComponent } from './components/category-list/category-list.component';
|
import { CategoryListComponent } from './components/category-list/category-list.component';
|
||||||
|
import { CategorySortComponent } from './components/category-sort/category-sort.component';
|
||||||
import { MotionBlockListComponent } from './components/motion-block-list/motion-block-list.component';
|
import { MotionBlockListComponent } from './components/motion-block-list/motion-block-list.component';
|
||||||
import { MotionBlockDetailComponent } from './components/motion-block-detail/motion-block-detail.component';
|
import { MotionBlockDetailComponent } from './components/motion-block-detail/motion-block-detail.component';
|
||||||
import { MotionCommentSectionListComponent } from './components/motion-comment-section-list/motion-comment-section-list.component';
|
import { MotionCommentSectionListComponent } from './components/motion-comment-section-list/motion-comment-section-list.component';
|
||||||
@ -19,6 +20,7 @@ import { WorkflowDetailComponent } from './components/workflow-detail/workflow-d
|
|||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: MotionListComponent },
|
{ path: '', component: MotionListComponent },
|
||||||
{ path: 'category', component: CategoryListComponent },
|
{ path: 'category', component: CategoryListComponent },
|
||||||
|
{ path: 'category/:id', component: CategorySortComponent },
|
||||||
{ path: 'comment-section', component: MotionCommentSectionListComponent },
|
{ path: 'comment-section', component: MotionCommentSectionListComponent },
|
||||||
{ path: 'statute-paragraphs', component: StatuteParagraphListComponent },
|
{ path: 'statute-paragraphs', component: StatuteParagraphListComponent },
|
||||||
{ path: 'statute-paragraphs/import', component: StatuteImportListComponent },
|
{ path: 'statute-paragraphs/import', component: StatuteImportListComponent },
|
||||||
|
@ -25,6 +25,7 @@ import { MotionExportDialogComponent } from './components/motion-export-dialog/m
|
|||||||
import { StatuteImportListComponent } from './components/statute-paragraph-list/statute-import-list/statute-import-list.component';
|
import { StatuteImportListComponent } from './components/statute-paragraph-list/statute-import-list/statute-import-list.component';
|
||||||
import { WorkflowListComponent } from './components/workflow-list/workflow-list.component';
|
import { WorkflowListComponent } from './components/workflow-list/workflow-list.component';
|
||||||
import { WorkflowDetailComponent } from './components/workflow-detail/workflow-detail.component';
|
import { WorkflowDetailComponent } from './components/workflow-detail/workflow-detail.component';
|
||||||
|
import { CategorySortComponent } from './components/category-sort/category-sort.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
||||||
@ -50,7 +51,8 @@ import { WorkflowDetailComponent } from './components/workflow-detail/workflow-d
|
|||||||
MotionExportDialogComponent,
|
MotionExportDialogComponent,
|
||||||
StatuteImportListComponent,
|
StatuteImportListComponent,
|
||||||
WorkflowListComponent,
|
WorkflowListComponent,
|
||||||
WorkflowDetailComponent
|
WorkflowDetailComponent,
|
||||||
|
CategorySortComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
MotionChangeRecommendationComponent,
|
MotionChangeRecommendationComponent,
|
||||||
|
@ -639,3 +639,9 @@ button.mat-menu-item.selected {
|
|||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ellipsis-overflow {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user