diff --git a/client/package-lock.json b/client/package-lock.json index eb8bf9398..3e88d00f8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4645,12 +4645,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4665,17 +4667,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4792,7 +4797,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4804,6 +4810,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4818,6 +4825,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4825,12 +4833,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4849,6 +4859,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4929,7 +4940,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4941,6 +4953,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5062,6 +5075,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/client/src/app/shared/directives/os-perms.directive.ts b/client/src/app/shared/directives/os-perms.directive.ts index 95f107f94..02e896325 100644 --- a/client/src/app/shared/directives/os-perms.directive.ts +++ b/client/src/app/shared/directives/os-perms.directive.ts @@ -78,7 +78,6 @@ export class OsPermsDirective extends OpenSlidesComponent { private updateView(): void { if (this.checkPermissions()) { // will just render the page normally - console.log('do show: ', this.template, ' - ', this.viewContainer); this.viewContainer.createEmbeddedView(this.template); } else { // will remove the content of the container diff --git a/client/src/app/shared/models/motions/motion.ts b/client/src/app/shared/models/motions/motion.ts index 955b76776..ef639c476 100644 --- a/client/src/app/shared/models/motions/motion.ts +++ b/client/src/app/shared/models/motions/motion.ts @@ -6,6 +6,7 @@ import { Config } from '../core/config'; import { Workflow } from './workflow'; import { User } from '../users/user'; import { Category } from './category'; +import { WorkflowState } from './workflow-state'; /** * Representation of Motion. @@ -123,7 +124,7 @@ export class Motion extends BaseModel { get submitterName() { const mainSubmitter = this.DS.get(User, this.submitters[0].user_id) as User; if (mainSubmitter) { - return mainSubmitter.username; + return mainSubmitter; } else { return ''; } @@ -132,40 +133,74 @@ export class Motion extends BaseModel { /** * get the category of a motion as object */ - get category() { + get category(): any { if (this.category_id) { const motionCategory = this.DS.get(Category, this.category_id); - return motionCategory; + return motionCategory as Category; } else { return 'none'; } } + /** + * Set the category in the motion + */ + set category(newCategory: any) { + this.category_id = newCategory.id; + } + + /** + * Returns the workflow + * + * TODO this is the default workflow, not yet the coresponding for the motion + */ + get workflow(): Workflow { + const motionsWorkflowConfig = this.DS.filter(Config, config => config.key === 'motions_workflow')[0] as Config; + //make sure this is a number + const workflowId = +motionsWorkflowConfig.value; + //get the workflow for our motion + const selectedWorkflow = this.DS.get(Workflow, workflowId) as Workflow; + return selectedWorkflow; + } + /** * return the workflow state * * Right now only the default workflow is assumed * TODO: Motion workflow needs to be specific on the server */ - get stateName() { - //get the default workflow - const motionsWorkflowConfig = this.DS.filter(Config, config => config.key === 'motions_workflow')[0] as Config; - //make sure this is a number - const workflowId = +motionsWorkflowConfig.value; - //get the workflow for out motion - const selectedWorkflow = this.DS.get(Workflow, workflowId) as Workflow; - const stateName = selectedWorkflow.getStateNameById(this.state_id); - if (stateName !== 'NULL') { - return stateName; - } else { - return ''; - } + get state() { + const workflow = this.workflow; + const state = workflow.state_by_id(this.state_id); + return state; } - get possibleStates() { - return ''; + /** + * returns possible states for the motion + */ + get possible_states(): WorkflowState[] { + return this.workflow.states; } + /** + * Returns possible "initial" states for a motion. + * + * Will filter "submitted" + */ + // get initial_states(): WorkflowState[] { + // const states = this.workflow.states; + + // //find index of 'submitted' + // const submitted = states.findIndex(state => state.name === 'submitted'); + + // //if found a valid index, remove "submitted" from array + // if (typeof submitted === 'number' && submitted >= 0) { + // states.splice(submitted, 1); + // } + + // return states; + // } + /** * Returns the name of the recommendation. * @@ -173,13 +208,10 @@ export class Motion extends BaseModel { * TODO: Motion workflow needs to be specific on the server */ get recommendation() { - //get the default workflow - const motionsWorkflowConfig = this.DS.filter(Config, config => config.key === 'motions_workflow')[0] as Config; - const workflowId = +motionsWorkflowConfig.value; - const selectedWorkflow = this.DS.get(Workflow, workflowId) as Workflow; - const stateName = selectedWorkflow.getStateNameById(this.recommendation_id); - if (stateName !== 'NULL') { - return stateName; + // const stateName = this.workflow.getStateNameById(this.recommendation_id); + const state = this.workflow.state_by_id(this.recommendation_id); + if (state) { + return state.recommendation_label; } else { return ''; } diff --git a/client/src/app/shared/models/motions/workflow-state.ts b/client/src/app/shared/models/motions/workflow-state.ts index 8cd689202..acc85b614 100644 --- a/client/src/app/shared/models/motions/workflow-state.ts +++ b/client/src/app/shared/models/motions/workflow-state.ts @@ -83,4 +83,8 @@ export class WorkflowState implements Deserializable { Object.assign(this, input); return this; } + + public toString = (): string => { + return this.name; + }; } diff --git a/client/src/app/shared/models/motions/workflow.ts b/client/src/app/shared/models/motions/workflow.ts index 0487fb07b..f1f8bb20f 100644 --- a/client/src/app/shared/models/motions/workflow.ts +++ b/client/src/app/shared/models/motions/workflow.ts @@ -21,14 +21,14 @@ export class Workflow extends BaseModel { this.first_state = first_state; } - getStateNameById(id: number): string { - let stateName = 'NULL'; + state_by_id(id: number): WorkflowState { + let targetState; this.states.forEach(state => { if (id === state.id) { - stateName = state.name; + targetState = state; } }); - return stateName; + return targetState as WorkflowState; } deserialize(input: any): this { diff --git a/client/src/app/shared/models/users/user.ts b/client/src/app/shared/models/users/user.ts index 31240e015..135f30412 100644 --- a/client/src/app/shared/models/users/user.ts +++ b/client/src/app/shared/models/users/user.ts @@ -64,4 +64,39 @@ export class User extends BaseModel { getGroups(): BaseModel | BaseModel[] { return this.DS.get('users/group', ...this.groups_id); } + + //TODO get full_name + + // TODO read config values for "users_sort_by" + get short_name(): string { + const title = this.title.trim(); + const firstName = this.first_name.trim(); + const lastName = this.last_name.trim(); + let shortName = ''; + + // TODO need DS adjustment first first + // if (this.DS.getConfig('users_sort_by').value === 'last_name') { + // if (lastName && firstName) { + // shortName += `${lastName}, ${firstName}`; + // } else { + // shortName += lastName || firstName; + // } + // } + + shortName += `${firstName} ${lastName}`; + + if (shortName.trim() === '') { + shortName = this.username; + } + + if (title) { + shortName = `${title} ${shortName}`; + } + + return shortName.trim(); + } + + public toString = (): string => { + return this.short_name; + }; } diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 886a37795..f596c455f 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -20,6 +20,8 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatListModule } from '@angular/material/list'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatMenuModule } from '@angular/material/menu'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; // FontAwesome modules import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; @@ -48,6 +50,8 @@ library.add(fas); imports: [ CommonModule, FormsModule, + MatFormFieldModule, + MatSelectModule, ReactiveFormsModule, MatButtonModule, MatCheckboxModule, @@ -68,6 +72,8 @@ library.add(fas); ], exports: [ FormsModule, + MatFormFieldModule, + MatSelectModule, ReactiveFormsModule, MatButtonModule, MatCheckboxModule, diff --git a/client/src/app/site/motions/motion-detail/motion-detail.component.html b/client/src/app/site/motions/motion-detail/motion-detail.component.html index 5437fb598..9ba5c33b7 100644 --- a/client/src/app/site/motions/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/motion-detail/motion-detail.component.html @@ -1,11 +1,11 @@ - - -
Motion {{motion.identifier}}: {{motion.currentTitle}}
@@ -16,37 +16,63 @@ - - + + + Meta information -
-
+ +
+ + +
+
+

Identifier

+ {{motion.identifier}} +
+ + + +
+ + +

Submitters

{{motion.submitterName}}
-
-

translate>Supporters

+ +
+

Supporters

-
-

- - Status - - -

- {{motion.stateName}} + +
+
+

State

+ {{motion.state.name}} +
+ + + {{state}} + + + + Reset State + + +
-
+ + + -
-

- - Category - - -

- {{motion.category}} -
- -
-

- Origin - -

- {{motion.origin}} - -
- - + +
+
+

Category

+ {{motion.category}} +
+ + + None + + {{cat}} +
-
-

Voting

+ +
+
+

Origin

+ {{motion.origin}} +
+ + +
-
+ + + + + - + + @@ -94,7 +126,9 @@ TEST - + + + @@ -102,11 +136,38 @@ -

The assembly may decide:

-
+
+ +
+
+

{{motion.currentTitle}}

+
+ + + +
+ + +

The assembly may decide:

+
+
+
+ + + + + +
+
+

Reason

+
+
+ + + +
+
-

Reason

-
@@ -117,8 +178,3 @@ - - - - - \ No newline at end of file diff --git a/client/src/app/site/motions/motion-detail/motion-detail.component.scss b/client/src/app/site/motions/motion-detail/motion-detail.component.scss index 9909432a8..8ab6f5fc2 100644 --- a/client/src/app/site/motions/motion-detail/motion-detail.component.scss +++ b/client/src/app/site/motions/motion-detail/motion-detail.component.scss @@ -52,17 +52,33 @@ mat-panel-title { margin-left: 5px; } } +} - .meta-info-panel-body { +.wide-text { + width: 95%; + + textarea { + height: 200px; + } +} + +mat-expansion-panel { + .expansion-panel-custom-body { padding-left: 55px; } } .content-panel { + h2 { + display: block; + font-weight: bold; + font-size: 120%; + } + h3 { + display: block; font-weight: initial; font-size: 100%; - display: block; } h4 { diff --git a/client/src/app/site/motions/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/motion-detail/motion-detail.component.ts index 409cb469c..08eefd9cb 100644 --- a/client/src/app/site/motions/motion-detail/motion-detail.component.ts +++ b/client/src/app/site/motions/motion-detail/motion-detail.component.ts @@ -1,8 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { OpenSlidesComponent } from '../../../openslides.component'; import { BaseComponent } from '../../../base.component'; import { Motion } from '../../../shared/models/motions/motion'; +import { Category } from '../../../shared/models/motions/category'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { MatExpansionPanel } from '@angular/material'; @Component({ selector: 'app-motion-detail', @@ -10,33 +12,73 @@ import { Motion } from '../../../shared/models/motions/motion'; styleUrls: ['./motion-detail.component.scss'] }) export class MotionDetailComponent extends BaseComponent implements OnInit { + @ViewChild('metaInfoPanel') metaInfoPanel: MatExpansionPanel; + @ViewChild('contentPanel') contentPanel: MatExpansionPanel; + motion: Motion; + metaInfoForm: FormGroup; + editMotion = false; - constructor(private route: ActivatedRoute) { + // categoryFormControl: FormControl; + + constructor(private route: ActivatedRoute, private formBuilder: FormBuilder) { super(); + this.createForm(); this.route.params.subscribe(params => { - console.log(params.id); - // has the motion of the DataStore was initialized before. - // Otherwise we need to observe DS this.motion = this.DS.get(Motion, params.id) as Motion; + if (this.motion) { + this.patchForm(); + } // Observe motion to get the motion in the parameter and also get the changes this.DS.getObservable().subscribe(newModel => { if (newModel instanceof Motion) { if (newModel.id === +params.id) { this.motion = newModel as Motion; - console.log('this.motion = ', this.motion); - // console.log('motion state name: ', this.motion.stateName); + this.patchForm(); } } }); }); } + /** Parches the Form with content from the dataStore */ + patchForm() { + this.metaInfoForm.patchValue({ categoryFormControl: this.motion.category }); + this.metaInfoForm.patchValue({ state: this.motion.state }); + } + + /** Create the whole Form with empty or default values */ + createForm() { + this.metaInfoForm = this.formBuilder.group({ + categoryFormControl: [''], + state: [''] + }); + } + + saveMotion() { + console.log('Save motion: ', this.metaInfoForm.value); + } + + getMotionCategories(): Category[] { + const categories = this.DS.get(Category); + return categories as Category[]; + } + + editMotionButton() { + this.editMotion ? (this.editMotion = false) : (this.editMotion = true); + if (this.editMotion) { + this.metaInfoPanel.open(); + this.contentPanel.open(); + } + + // console.log('this.motion.possible_states: ', this.motion.possible_states); + } + ngOnInit() { console.log('(init)the motion: ', this.motion); - console.log('motion state name: ', this.motion.stateName); + console.log('motion state name: ', this.motion.state); } downloadSingleMotionButton() { diff --git a/client/src/app/site/motions/motion-list/motion-list.component.html b/client/src/app/site/motions/motion-list/motion-list.component.html index 4a60dbbaf..3c0a34dda 100644 --- a/client/src/app/site/motions/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/motion-list/motion-list.component.html @@ -53,8 +53,8 @@ State -
- +
+
@@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/client/src/app/site/motions/motion-list/motion-list.component.ts b/client/src/app/site/motions/motion-list/motion-list.component.ts index 51200d068..364f96150 100644 --- a/client/src/app/site/motions/motion-list/motion-list.component.ts +++ b/client/src/app/site/motions/motion-list/motion-list.component.ts @@ -77,23 +77,24 @@ export class MotionListComponent extends BaseComponent implements OnInit { } selectMotion(motion) { - console.log('clicked a row, :', motion); - this.router.navigate(['./' + motion.id], { relativeTo: this.route }); } /** * Get the icon to the coresponding Motion Status * TODO Needs to be more accessible (Motion workflow needs adjustment on the server) - * @param stateName the name of the state + * @param state the name of the state */ - getStateIcon(stateName) { + getStateIcon(state) { + const stateName = state.name; if (stateName === 'accepted') { return 'thumbs-up'; } else if (stateName === 'rejected') { return 'thumbs-down'; } else if (stateName === 'not decided') { return 'question'; + } else { + return ''; } } diff --git a/client/src/app/site/site.component.ts b/client/src/app/site/site.component.ts index 9401e0daa..c1880146f 100644 --- a/client/src/app/site/site.component.ts +++ b/client/src/app/site/site.component.ts @@ -66,9 +66,9 @@ export class SiteComponent extends BaseComponent implements OnInit { }); //get a translation via code: use the translation service - this.translate.get('Motions').subscribe((res: string) => { - console.log('translation of motions in the target language: ' + res); - }); + // this.translate.get('Motions').subscribe((res: string) => { + // console.log('translation of motions in the target language: ' + res); + // }); //start autoupdate if the user is logged in: this.operator.whoAmI().subscribe(resp => {