diff --git a/client/src/app/core/repositories/motions/category-repository.service.ts b/client/src/app/core/repositories/motions/category-repository.service.ts index 96114c361..7ebc8b2a3 100644 --- a/client/src/app/core/repositories/motions/category-repository.service.ts +++ b/client/src/app/core/repositories/motions/category-repository.service.ts @@ -11,7 +11,6 @@ import { DataSendService } from '../../core-services/data-send.service'; import { DataStoreService } from '../../core-services/data-store.service'; import { HttpService } from '../../core-services/http.service'; 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 { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; @@ -81,22 +80,6 @@ export class CategoryRepositoryService extends BaseRepository = []; - 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 * @param category_id category ID diff --git a/client/src/app/shared/components/sorting-list/sorting-list.component.html b/client/src/app/shared/components/sorting-list/sorting-list.component.html index b1429f91b..a26cfc724 100644 --- a/client/src/app/shared/components/sorting-list/sorting-list.component.html +++ b/client/src/app/shared/components/sorting-list/sorting-list.component.html @@ -1,12 +1,15 @@ -
+
+
+ No data +
drag_indicator
- - {{ i+1 }}.  - {{ item }} + + {{ i + 1 }}.  + {{ item.getTitle() }}
diff --git a/client/src/app/shared/components/sorting-list/sorting-list.component.scss b/client/src/app/shared/components/sorting-list/sorting-list.component.scss index b05c67942..3eb0fc8bf 100644 --- a/client/src/app/shared/components/sorting-list/sorting-list.component.scss +++ b/client/src/app/shared/components/sorting-list/sorting-list.component.scss @@ -1,9 +1,3 @@ -.list { - width: 100%; - display: block; - overflow: hidden; -} - .box { width: 100%; border-bottom: solid 1px #ccc; diff --git a/client/src/app/shared/components/sorting-list/sorting-list.component.ts b/client/src/app/shared/components/sorting-list/sorting-list.component.ts index 1ad57045f..ed304f72d 100644 --- a/client/src/app/shared/components/sorting-list/sorting-list.component.ts +++ b/client/src/app/shared/components/sorting-list/sorting-list.component.ts @@ -5,7 +5,6 @@ import { TranslateService } from '@ngx-translate/core'; import { Observable, Subscription } from 'rxjs'; import { Selectable } from '../selectable'; -import { EmptySelectable } from '../empty-selectable'; /** * Reusable Sorting List @@ -133,8 +132,8 @@ export class SortingListComponent implements OnInit, OnDestroy { if (this.array.length !== newValues.length || this.live) { this.array = []; this.array = newValues.map(val => val); - } else if (this.array.length === 0) { - this.array.push(new EmptySelectable(this.translate)); + } else { + this.array = this.array.map(arrayValue => newValues.find(val => val.id === arrayValue.id)); } } diff --git a/client/src/app/site/motions/components/category-list/category-list.component.html b/client/src/app/site/motions/components/category-list/category-list.component.html index a9ff1d63e..d43b8e4d9 100644 --- a/client/src/app/site/motions/components/category-list/category-list.component.html +++ b/client/src/app/site/motions/components/category-list/category-list.component.html @@ -4,27 +4,28 @@

Categories

-
- - +
+ Create new category -
-

- - - - - - - - - - Required - - -

+ + + + + + + + + + Required + +
@@ -39,80 +40,94 @@
- - + + + + + + + +
+
+
+ {{ category.prefixedName }} +
+
+
+ {{ motionsInCategory(category).length }} +
+
+
+
- - - -
-
-
- {{ category.prefix }} + +
+
+
+ + + + + + + Required + +
-
- {{ updateForm.get('prefix').value }} +
+ + +
-
-
-
- {{ category.name }} -
-
- {{ updateForm.get('name').value }} -
-
-
- {{ motionsInCategory(category).length }} + +
+ +
+ Motions: +
+
    +
  • {{ motion.getListTitle() }}
  • +
- - - - -
- Edit category:
- - - - - - - - Required - - -
- - -
- Motions: -
-
    -
  • {{ motion }}
  • -
-
-
- -
-
- - - - - - - - - - - + + + + diff --git a/client/src/app/site/motions/components/category-list/category-list.component.scss b/client/src/app/site/motions/components/category-list/category-list.component.scss index 24a96547c..85e080af4 100644 --- a/client/src/app/site/motions/components/category-list/category-list.component.scss +++ b/client/src/app/site/motions/components/category-list/category-list.component.scss @@ -1,33 +1,50 @@ .header-container { display: grid; grid-template-rows: auto; - grid-template-columns: 33.333% 33.333% 33.333%; + grid-template-columns: 75% 25%; width: 100%; > div { grid-row-start: 1; grid-row-end: span 1; - grid-column-end: span 3; - } - - .header-prefix { - grid-column-start: 1; + grid-column-end: span 2; } .header-name { - grid-column-start: 2; + grid-column-start: 1; color: lightslategray; } .header-size { - grid-column-start: 3; + grid-column-start: 2; } } -#updateForm { - margin-bottom: 20px; +mat-expansion-panel { + max-width: 770px; + margin: auto; } -.half-width { - width: 50%; +.flex-spaced { + 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; } diff --git a/client/src/app/site/motions/components/category-list/category-list.component.ts b/client/src/app/site/motions/components/category-list/category-list.component.ts index 21bdcd4fb..77425a61c 100644 --- a/client/src/app/site/motions/components/category-list/category-list.component.ts +++ b/client/src/app/site/motions/components/category-list/category-list.component.ts @@ -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 { TranslateService } from '@ngx-translate/core'; +import { BaseViewComponent } from '../../../base/base-view'; import { Category } from 'app/shared/models/motions/category'; import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service'; -import { ViewCategory } from '../../models/view-category'; -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 { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; -import { BaseViewComponent } from '../../../base/base-view'; -import { MatSnackBar } from '@angular/material'; +import { ViewCategory } from '../../models/view-category'; +import { ViewMotion } from '../../models/view-motion'; /** * List view for the categories. @@ -28,15 +28,10 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit { public categoryToCreate: Category | null; /** - * Determine which category to edit + * Determine which category is opened */ public editId: number | null; - /** - * Determine which category is opened. - */ - public openId: number | null; - /** * Source of the data */ @@ -52,12 +47,6 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit { */ public updateForm: FormGroup; - /** - * The MultiSelect Component - */ - @ViewChild('sorter') - public sortSelector: SortingListComponent; - /** * The usual component constructor * @param titleService @@ -72,6 +61,7 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit { translate: TranslateService, matSnackBar: MatSnackBar, private repo: CategoryRepositoryService, + private motionRepo: MotionRepositoryService, private formBuilder: FormBuilder, 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 viewCategory */ @@ -154,47 +145,33 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit { } /** - * Saves the category - * - * 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 + * Saves a category + * TODO: Some feedback * * @param viewCategory */ public async onSaveButton(viewCategory: ViewCategory): Promise { - // get the sorted motions. Save them before updating the category. - let sortedMotionIds; - if (this.sortSelector) { - sortedMotionIds = this.sortSelector.array.map(selectable => selectable.id); - this.repo.numberMotionsInCategory(viewCategory.category, sortedMotionIds); - } - - if (this.updateForm.valid) { + if (this.updateForm.dirty && this.updateForm.valid) { const cat: Partial = { name: this.updateForm.get('name').value }; if (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); - this.onCancelButton(); - - if (this.sortSelector) { - this.repo.numberMotionsInCategory(viewCategory.category, sortedMotionIds); - } + this.updateForm.markAsPristine(); } } /** - * executed on cancel button - * @param viewCategory + * Trigger after cancelling an edit. The updateForm is reset to an original + * value, which might belong to a different category */ public onCancelButton(): void { - this.editId = null; + this.updateForm.markAsPristine(); } /** * is executed, when the delete button is pressed + * * @param viewCategory The category to delete */ public async onDeleteButton(viewCategory: ViewCategory): Promise { @@ -206,23 +183,36 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit { /** * Returns the motions corresponding to a category + * * @param category target * @returns all motions in the category */ - public motionsInCategory(category: Category): Motion[] { - const motions = this.repo.getMotionsOfCategory(category); - motions.sort((motion1, motion2) => (motion1 > motion2 ? 1 : -1)); - return motions; + public motionsInCategory(category: Category): ViewMotion[] { + return this.motionRepo + .getViewModelList() + .filter(m => m.category_id === category.id) + .sort((motion1, motion2) => motion1.identifier.localeCompare(motion2.identifier)); } /** - * Is executed when a mat-extension-panel is closed - * @param viewCategory the category in the panel + * Fetch the correct URL for a detail sort view + * + * @param viewCategory */ - public panelClosed(viewCategory: ViewCategory): void { - this.openId = null; - if (this.editId) { - this.onSaveButton(viewCategory); - } + public getSortUrl(viewCategory: ViewCategory): string { + return `/motions/category/${viewCategory.id}`; + } + + /** + * 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 + }); } } diff --git a/client/src/app/site/motions/components/category-sort/category-sort.component.html b/client/src/app/site/motions/components/category-sort/category-sort.component.html new file mode 100644 index 000000000..d67f2235f --- /dev/null +++ b/client/src/app/site/motions/components/category-sort/category-sort.component.html @@ -0,0 +1,24 @@ + + + +

Sort motions

+
+ + +

{{ categoryName }}

+
+ + Drag and drop motions to reorder the category. Then click the button to renumber. + +
+ + +
diff --git a/client/src/app/site/motions/components/category-sort/category-sort.component.scss b/client/src/app/site/motions/components/category-sort/category-sort.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/app/site/motions/components/category-sort/category-sort.component.spec.ts b/client/src/app/site/motions/components/category-sort/category-sort.component.spec.ts new file mode 100644 index 000000000..5e3682989 --- /dev/null +++ b/client/src/app/site/motions/components/category-sort/category-sort.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/client/src/app/site/motions/components/category-sort/category-sort.component.ts b/client/src/app/site/motions/components/category-sort/category-sort.component.ts new file mode 100644 index 000000000..a41b7a652 --- /dev/null +++ b/client/src/app/site/motions/components/category-sort/category-sort.component.ts @@ -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([]); + + /** + * Counter indicating the amount of motions currently in the category + */ + public motionsCount = 0; + + /** + * @returns an observable for the {@link motionsSubject} + */ + public get motionObservable(): Observable { + 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 { + 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); + } + } + } +} diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.html b/client/src/app/site/motions/components/motion-list/motion-list.component.html index 3c06d3571..bd319cad8 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.html @@ -74,7 +74,7 @@
- + by {{ motion.submitters }}
diff --git a/client/src/app/site/motions/motions-routing.module.ts b/client/src/app/site/motions/motions-routing.module.ts index 549309f15..958cc129d 100644 --- a/client/src/app/site/motions/motions-routing.module.ts +++ b/client/src/app/site/motions/motions-routing.module.ts @@ -4,6 +4,7 @@ import { Routes, RouterModule } from '@angular/router'; import { AmendmentCreateWizardComponent } from './components/amendment-create-wizard/amendment-create-wizard.component'; import { CallListComponent } from './components/call-list/call-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 { MotionBlockDetailComponent } from './components/motion-block-detail/motion-block-detail.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 = [ { path: '', component: MotionListComponent }, { path: 'category', component: CategoryListComponent }, + { path: 'category/:id', component: CategorySortComponent }, { path: 'comment-section', component: MotionCommentSectionListComponent }, { path: 'statute-paragraphs', component: StatuteParagraphListComponent }, { path: 'statute-paragraphs/import', component: StatuteImportListComponent }, diff --git a/client/src/app/site/motions/motions.module.ts b/client/src/app/site/motions/motions.module.ts index 366d43863..a1656d09b 100644 --- a/client/src/app/site/motions/motions.module.ts +++ b/client/src/app/site/motions/motions.module.ts @@ -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 { WorkflowListComponent } from './components/workflow-list/workflow-list.component'; import { WorkflowDetailComponent } from './components/workflow-detail/workflow-detail.component'; +import { CategorySortComponent } from './components/category-sort/category-sort.component'; @NgModule({ imports: [CommonModule, MotionsRoutingModule, SharedModule], @@ -50,7 +51,8 @@ import { WorkflowDetailComponent } from './components/workflow-detail/workflow-d MotionExportDialogComponent, StatuteImportListComponent, WorkflowListComponent, - WorkflowDetailComponent + WorkflowDetailComponent, + CategorySortComponent ], entryComponents: [ MotionChangeRecommendationComponent, diff --git a/client/src/styles.scss b/client/src/styles.scss index 00181aa9a..6e4ea4dff 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -640,3 +640,9 @@ button.mat-menu-item.selected { margin: 10px 0; } } + +.ellipsis-overflow { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +}