import { MatCommonModule } from '@angular/material/core'; | import { MatCommonModule } from '@angular/material/core'; | ||||
import { MatInputModule } from '@angular/material/input'; | import { MatInputModule } from '@angular/material/input'; | ||||
import { DynamicDatabase } from './create-rule/class/dynamic-database'; | import { DynamicDatabase } from './create-rule/class/dynamic-database'; | ||||
import {MatSnackBarModule} from '@angular/material/snack-bar'; | |||||
import {MatBottomSheetModule} from '@angular/material/bottom-sheet'; | |||||
import { OptionsShetComponent } from './options-shet/options-shet.component'; | |||||
@NgModule({ | @NgModule({ | ||||
declarations: [ | declarations: [ | ||||
CreateRuleComponent | |||||
CreateRuleComponent, | |||||
OptionsShetComponent | |||||
], | ], | ||||
imports: [ | imports: [ | ||||
MatBottomSheetModule, | |||||
MatSnackBarModule, | |||||
CommonModule, | CommonModule, | ||||
MatCardModule, | MatCardModule, | ||||
MatFormFieldModule, | MatFormFieldModule, |
import { Injectable } from "@angular/core" | |||||
import { RippleRef } from "@angular/material/core"; | |||||
import { Injectable } from "@angular/core"; | |||||
import { BehaviorSubject, Subject } from "rxjs"; | import { BehaviorSubject, Subject } from "rxjs"; | ||||
import { map } from "rxjs/operators"; | |||||
import { CreateRuleDto, Rule, RulesService } from "../../../../../api"; | import { CreateRuleDto, Rule, RulesService } from "../../../../../api"; | ||||
import { environment } from "../../../../environments/environment"; | import { environment } from "../../../../environments/environment"; | ||||
import { RuleDataService } from "../../../service/rule-data.service"; | import { RuleDataService } from "../../../service/rule-data.service"; | ||||
import { RuleFlatNode } from "./rule-flat-node"; | |||||
import { EventMessage } from "./event-message"; | |||||
@Injectable({ providedIn: 'root' }) | @Injectable({ providedIn: 'root' }) | ||||
dataChange = new BehaviorSubject<Rule[]>([]); | dataChange = new BehaviorSubject<Rule[]>([]); | ||||
childAdded = new Subject<Rule>(); | childAdded = new Subject<Rule>(); | ||||
onEvent = new Subject<EventMessage>() | |||||
get data(): Rule[] { return this.dataChange.value; } | get data(): Rule[] { return this.dataChange.value; } | ||||
get count(): number { return this.dataChange.value.length }; | get count(): number { return this.dataChange.value.length }; | ||||
) | ) | ||||
), | ), | ||||
(err) => { | (err) => { | ||||
//TODO add toaster | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Error', | |||||
error: err, | |||||
message: `Fehler beim abrufen der Regeln` | |||||
} | |||||
); | |||||
} | } | ||||
} | } | ||||
getById = (ruleId: string) => { | |||||
const parentNode = this.getParentById(ruleId); | |||||
if (parentNode) { | |||||
return parentNode; | |||||
} | |||||
for (const rootRule of this.data) { | |||||
const found = this.findChildById(rootRule, ruleId); | |||||
if (found) { | |||||
return found; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
findChildById = (parent: Rule, ruleId: string) => { | |||||
if (!parent.subRule) { | |||||
return null; | |||||
} | |||||
return parent.subRule.find(( | |||||
rule => rule.id === ruleId | |||||
)) | |||||
} | |||||
getParentById = (ruleId: string) => this.data.find(rule => rule.id === ruleId) | getParentById = (ruleId: string) => this.data.find(rule => rule.id === ruleId) | ||||
insertEmptyChild(parent: Rule) { | insertEmptyChild(parent: Rule) { | ||||
; | |||||
if (!parent.subRule) { | if (!parent.subRule) { | ||||
parent.subRule = []; | parent.subRule = []; | ||||
} | } | ||||
{ | { | ||||
text: '', | text: '', | ||||
id: '0', | id: '0', | ||||
parentRule: parent, | |||||
parentRule: { | |||||
id: parent.id | |||||
}, | |||||
paragraph: this.ruleHelperService.getParagraphOfNextChild(parent), | paragraph: this.ruleHelperService.getParagraphOfNextChild(parent), | ||||
created: 'today', | created: 'today', | ||||
deletedAt: null, | deletedAt: null, | ||||
updated: null, | updated: null, | ||||
} | |||||
} as Rule | |||||
) | ) | ||||
this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
this.childAdded.next(parent); | this.childAdded.next(parent); | ||||
} | } | ||||
this.rulesService.create(createSubRule).subscribe( | this.rulesService.create(createSubRule).subscribe( | ||||
newSubRule => { | newSubRule => { | ||||
subRule.id = newSubRule.id; | |||||
this.dataChange.next(this.data); | |||||
if (typeof newSubRule.id !== 'undefined' && typeof newSubRule.parentRule?.id !== 'undefined') { | if (typeof newSubRule.id !== 'undefined' && typeof newSubRule.parentRule?.id !== 'undefined') { | ||||
const updateParentRule: CreateRuleDto = { | const updateParentRule: CreateRuleDto = { | ||||
paragraph: parent.paragraph, | paragraph: parent.paragraph, | ||||
//TODO Push updated message | //TODO Push updated message | ||||
this.update(parent.id, { | this.update(parent.id, { | ||||
paragraph: parent.paragraph, | paragraph: parent.paragraph, | ||||
subRuleIds: [ | |||||
subRule: [ | |||||
...parent.subRule | ...parent.subRule | ||||
.filter(rule => rule.id !== "0") | |||||
.map(rule => rule.id), | |||||
subRule.id, | |||||
.filter(rule => rule.id !== "0"), | |||||
subRule, | |||||
], | ], | ||||
text: parent.text | text: parent.text | ||||
}).subscribe( | |||||
(updated) => { | |||||
//todo add update message | |||||
}, | |||||
(err) => { | |||||
//TODO Push error message | |||||
console.error(err); | |||||
} | |||||
) | |||||
}); | |||||
}, | }, | ||||
(err) => { | (err) => { | ||||
//TODO Push error message | |||||
console.error(err); | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Error', | |||||
error: err, | |||||
message: `Fehler beim aktualisieren von Regel ${parent.paragraph}` | |||||
} | |||||
); | |||||
} | } | ||||
) | ) | ||||
return; | return; | ||||
} | } | ||||
//TODO Push error message | |||||
} | } | ||||
) | ) | ||||
if (!parent.subRule) { | if (!parent.subRule) { | ||||
this.childAdded.next(parent); | this.childAdded.next(parent); | ||||
} | } | ||||
update(id: string, changedData: Partial<CreateRuleDto>) { | |||||
return this.rulesService.update(id, changedData); | |||||
reload() { | |||||
this.dataChange.next(this.data); | |||||
} | |||||
update(id: string, changedData: Partial<Rule>) { | |||||
let ruleToUpdate = this.getById(id); | |||||
const updateDto = { | |||||
paragraph: !!changedData.paragraph ? changedData.paragraph : ruleToUpdate.paragraph, | |||||
text: !!changedData.text ? changedData.text : ruleToUpdate.text, | |||||
parentId: !!changedData.parentRule ? changedData.parentRule.id : null, | |||||
subRuleIds: [ | |||||
...!!changedData.subRule ? | |||||
changedData.subRule | |||||
.map(rule => rule.id) | |||||
: [] | |||||
] | |||||
} as CreateRuleDto | |||||
this.rulesService.update(id, updateDto).subscribe( | |||||
() => { | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Update', | |||||
message: `Regel ${ruleToUpdate.paragraph} wurde erfolgreich aktualisiert` | |||||
} | |||||
); | |||||
}, | |||||
(err) => { | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Error', | |||||
error: err, | |||||
message: `Fehler beim aktualisieren der Regel ${ruleToUpdate.paragraph}` | |||||
} | |||||
); | |||||
} | |||||
) | |||||
this.dataChange.next(this.data); | |||||
} | } | ||||
insertParent(newRule: Rule) { | insertParent(newRule: Rule) { | ||||
parentId: null | parentId: null | ||||
} | } | ||||
this.rulesService.create(createNewRule).subscribe( | this.rulesService.create(createNewRule).subscribe( | ||||
() => { | |||||
//TODO created message | |||||
(created) => { | |||||
newRule.id = created.id; | |||||
this.dataChange.next(this.data); | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Create', | |||||
message: `Regel ${createNewRule.paragraph} wurde erfolgreich erstellt` | |||||
} | |||||
); | |||||
}, | }, | ||||
(err) => { | (err) => { | ||||
//TODO Error message | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Error', | |||||
error: err, | |||||
message: `Fehler beim anlegen der Regel ${createNewRule.paragraph}` | |||||
} | |||||
); | |||||
} | } | ||||
) | ) | ||||
this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
parent.deletedAt = "deleted"; | parent.deletedAt = "deleted"; | ||||
this.rulesService.remove(parent.id).subscribe( | this.rulesService.remove(parent.id).subscribe( | ||||
(deleted) => { | (deleted) => { | ||||
//todo add update message | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Restore', | |||||
message: `Regel ${parent.paragraph} wurde erfolgreich deaktiviert` | |||||
} | |||||
); | |||||
}, | }, | ||||
(err) => { | (err) => { | ||||
//TODO Push error message | |||||
console.error(err); | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Error', | |||||
error: err, | |||||
message: `Fehler beim aktivieren von Regel ${parent.paragraph}` | |||||
} | |||||
); | |||||
} | } | ||||
); | ); | ||||
this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
} | } | ||||
this.rulesService.restore(rule.id).subscribe( | this.rulesService.restore(rule.id).subscribe( | ||||
(deleted) => { | (deleted) => { | ||||
//todo add restored message | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Restore', | |||||
message: `Regel ${rule.paragraph} wurde erfolgreich reaktiviert` | |||||
} | |||||
); | |||||
}, | }, | ||||
(err) => { | (err) => { | ||||
//TODO Push error message | |||||
console.error(err); | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Error', | |||||
error: err, | |||||
message: `Fehler beim aktivieren der Regel ${rule.paragraph}` | |||||
} | |||||
); | |||||
} | } | ||||
); | ); | ||||
this.dataChange.next(this.data); | this.dataChange.next(this.data); | ||||
(deleted) => { | (deleted) => { | ||||
this.update(parent.id, { | this.update(parent.id, { | ||||
paragraph: parent.paragraph, | paragraph: parent.paragraph, | ||||
subRuleIds: [ | |||||
subRule: [ | |||||
...parent.subRule | ...parent.subRule | ||||
.filter(rule => rule.id !== "0") | .filter(rule => rule.id !== "0") | ||||
.map(rule => rule.id) | |||||
], | ], | ||||
text: parent.text | text: parent.text | ||||
}).subscribe( | |||||
(updated) => { | |||||
//todo add update message | |||||
}, | |||||
(err) => { | |||||
//TODO Push error message | |||||
console.error(err); | |||||
}); | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Remove', | |||||
message: `Regel ${subRule.paragraph} wurde erfolgreich deaktiviert` | |||||
} | } | ||||
); | ); | ||||
//todo add update message | |||||
}, | }, | ||||
(err) => { | (err) => { | ||||
//TODO Push error message | |||||
console.error(err); | |||||
this.onEvent.next( | |||||
{ | |||||
event: 'Error', | |||||
error: err, | |||||
message: `Fehler beim deaktivieren von Regel ${subRule.paragraph}` | |||||
} | |||||
); | |||||
} | } | ||||
); | ); | ||||
this.dataChange.next(this.data); | this.dataChange.next(this.data); |
export class EventMessage { event: 'Error' | 'Create' | 'Update' | 'Restore' | 'Remove'; message: string; error?: Error } |
import { Rule } from "../../../../../api"; | import { Rule } from "../../../../../api"; | ||||
export class RuleFlatNode { | export class RuleFlatNode { | ||||
constructor(public item: Rule, public level = 1, public expandable = false, | |||||
public isLoading = false,public root = true) { } | |||||
constructor( | |||||
public item: Rule, | |||||
public level = 1, | |||||
public expandable = false, | |||||
public isLoading = false, | |||||
public root = true, | |||||
public editMode = false, | |||||
) { } | |||||
} | } | ||||
<mat-card class="wood-card"> | <mat-card class="wood-card"> | ||||
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> | |||||
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding> | |||||
<div> | |||||
<button mat-icon-button disabled></button> §{{ node.item.paragraph }} {{ node.item.text }} | |||||
<button *ngIf="node.root" mat-icon-button (click)="addEmptyRule(node)"> | |||||
<mat-icon>add</mat-icon> | |||||
</button> | |||||
<button mat-icon-button (click)="remove(node)"> | |||||
<mat-icon>remove</mat-icon> | |||||
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> | |||||
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding> | |||||
<div> | |||||
<button mat-icon-button disabled></button> | |||||
<span *ngIf="!node.editMode"> | |||||
§{{ node.item.paragraph }} {{ node.item.text }} | |||||
</span> | |||||
<span *ngIf="node.editMode"> | |||||
§{{ node.item.paragraph }} | |||||
<mat-form-field class="new-node-input"> | |||||
<mat-label>Updated Rule</mat-label> | |||||
<input [formControl]="text" [value]="node.item.text" matInput #itemValue placeholder="text" /> | |||||
</mat-form-field> | |||||
<button [disabled]="itemValue.value === ''" mat-button (click)="updateNode(node, itemValue.value)">Update</button> | |||||
</span> | |||||
<button mat-icon-button (click)="openOptions(node)"> | |||||
<mat-icon>more_vert</mat-icon> | |||||
</button> | </button> | ||||
</div> | |||||
</mat-tree-node> | |||||
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> | |||||
<button mat-icon-button matTreeNodeToggle [attr.aria-label]="'Toggle ' + node.text"> | |||||
<mat-icon class="mat-icon-rtl-mirror"> | |||||
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }} | |||||
</mat-icon> | |||||
</button> §{{ node.item.paragraph }} {{ node.item.text }} | |||||
<button *ngIf="node.root" mat-icon-button (click)="addEmptyRule(node)"> | |||||
<mat-icon>add</mat-icon> | |||||
</button> | |||||
<button mat-icon-button (click)="remove(node)"> | |||||
<mat-icon>remove</mat-icon> | |||||
</button> | |||||
</mat-tree-node> | |||||
<mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding> | |||||
<button mat-icon-button disabled></button> | |||||
<span>§{{ node.item.paragraph }}</span> | |||||
<mat-form-field class="new-node-input"> | |||||
<mat-label>New Rule</mat-label> | |||||
<input [formControl]="text" matInput #itemValue placeholder="text" /> | |||||
</mat-form-field> | |||||
<button mat-button (click)="addNode(node, itemValue.value)">Save</button> | |||||
</mat-tree-node> | |||||
<mat-tree-node class="disabled-node" *matTreeNodeDef="let node; when: isDeleted" matTreeNodePadding> | |||||
<button mat-icon-button (click)="restore(node)"> | |||||
<mat-icon>restore</mat-icon> | |||||
</button> | |||||
<span>§{{ node.item.paragraph }} {{node.item.text}}</span> | |||||
</mat-tree-node> | |||||
</mat-tree> | |||||
<button mat-icon-button (click)="addEmptyRule()"> | |||||
<mat-icon>add</mat-icon> | |||||
</div> | |||||
</mat-tree-node> | |||||
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> | |||||
<button mat-icon-button matTreeNodeToggle [attr.aria-label]="'Toggle ' + node.text"> | |||||
<mat-icon class="mat-icon-rtl-mirror"> | |||||
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }} | |||||
</mat-icon> | |||||
</button> | |||||
<span *ngIf="!node.editMode"> | |||||
§{{ node.item.paragraph }} {{ node.item.text }} | |||||
</span> | |||||
<span *ngIf="node.editMode"> | |||||
§{{ node.item.paragraph }} | |||||
<mat-form-field class="new-node-input"> | |||||
<mat-label>Updated Rule</mat-label> | |||||
<input [formControl]="text" [value]="node.item.text" matInput #itemValue placeholder="text" /> | |||||
</mat-form-field> | |||||
<button [disabled]="itemValue.value === ''" mat-button (click)="updateNode(node, itemValue.value)">Update</button> | |||||
</span> | |||||
<button mat-icon-button (click)="openOptions(node)"> | |||||
<mat-icon>more_vert</mat-icon> | |||||
</button> | |||||
</mat-tree-node> | |||||
<mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding> | |||||
<button mat-icon-button disabled></button> | |||||
<span>§{{ node.item.paragraph }}</span> | |||||
<mat-form-field class="new-node-input"> | |||||
<mat-label>New Rule</mat-label> | |||||
<input [formControl]="text" matInput #itemValue placeholder="text" /> | |||||
</mat-form-field> | |||||
<button [disabled]="itemValue.value === ''" mat-button (click)="addNode(node, itemValue.value)">Save</button> | |||||
</mat-tree-node> | |||||
<mat-tree-node class="disabled-node" *matTreeNodeDef="let node; when: isDeleted" matTreeNodePadding> | |||||
<button mat-icon-button (click)="restore(node)"> | |||||
<mat-icon>restore</mat-icon> | |||||
</button> | |||||
<span>§{{ node.item.paragraph }} {{node.item.text}}</span> | |||||
<button mat-icon-button (click)="openOptions(node)"> | |||||
<mat-icon>more_vert</mat-icon> | |||||
</button> | |||||
</mat-tree-node> | |||||
</mat-tree> | |||||
<button mat-icon-button (click)="addEmptyRule()"> | |||||
<mat-icon>add</mat-icon> | |||||
</button> | </button> | ||||
</mat-card> | </mat-card> |
import { FlatTreeControl } from '@angular/cdk/tree'; | import { FlatTreeControl } from '@angular/cdk/tree'; | ||||
import { Component, OnInit } from '@angular/core'; | |||||
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; | |||||
import { FormControl } from '@angular/forms'; | import { FormControl } from '@angular/forms'; | ||||
import { MatBottomSheet, MatBottomSheetConfig } from '@angular/material/bottom-sheet'; | |||||
import { MatSnackBar } from '@angular/material/snack-bar'; | |||||
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; | import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; | ||||
import { Subject } from 'rxjs'; | |||||
import { Rule } from '../../../../api/model/rule'; | import { Rule } from '../../../../api/model/rule'; | ||||
import { EventDialogComponent } from '../../event-dialog/event-dialog.component'; | |||||
import { OptionsShetComponent } from '../options-shet/options-shet.component'; | |||||
import { DynamicDatabase } from './class/dynamic-database'; | import { DynamicDatabase } from './class/dynamic-database'; | ||||
import { EventMessage } from './class/event-message'; | |||||
import { RuleFlatNode } from './class/rule-flat-node'; | import { RuleFlatNode } from './class/rule-flat-node'; | ||||
flatNodeMap = new Map<RuleFlatNode, Rule>(); | flatNodeMap = new Map<RuleFlatNode, Rule>(); | ||||
nestedNodeMap = new Map<Rule, RuleFlatNode>(); | nestedNodeMap = new Map<Rule, RuleFlatNode>(); | ||||
text = new FormControl(''); | text = new FormControl(''); | ||||
@ViewChild(TemplateRef) template: TemplateRef<any>; | |||||
private _transformer = (rule: Rule, level: number) => { | private _transformer = (rule: Rule, level: number) => { | ||||
; | |||||
const existingNode = this.nestedNodeMap.get(rule); | const existingNode = this.nestedNodeMap.get(rule); | ||||
const flatNode = existingNode && existingNode.item.id === rule.id | const flatNode = existingNode && existingNode.item.id === rule.id | ||||
? existingNode | ? existingNode | ||||
dataSource: MatTreeFlatDataSource<Rule, RuleFlatNode>; | dataSource: MatTreeFlatDataSource<Rule, RuleFlatNode>; | ||||
_database: DynamicDatabase; | _database: DynamicDatabase; | ||||
constructor(database: DynamicDatabase) { | |||||
constructor(database: DynamicDatabase, private _snackBar: MatSnackBar, readonly bottomSheet: MatBottomSheet) { | |||||
this._database = database; | this._database = database; | ||||
this.treeControl = new FlatTreeControl<RuleFlatNode>(this.getLevel, this.isExpandable); | this.treeControl = new FlatTreeControl<RuleFlatNode>(this.getLevel, this.isExpandable); | ||||
this.treeFlattener = new MatTreeFlattener<Rule, RuleFlatNode, RuleFlatNode>( | this.treeFlattener = new MatTreeFlattener<Rule, RuleFlatNode, RuleFlatNode>( | ||||
this._database.dataChange.subscribe(data => { | this._database.dataChange.subscribe(data => { | ||||
this.dataSource.data = data; | this.dataSource.data = data; | ||||
}); | }); | ||||
this._database.onEvent.subscribe( | |||||
(event) => { | |||||
if (event.error) { | |||||
console.error(event.error); | |||||
} | |||||
this.openSnackBar(event) | |||||
} | |||||
) | |||||
this._database.childAdded.subscribe(parent => this.treeControl.expand(this.nestedNodeMap.get(parent))) | this._database.childAdded.subscribe(parent => this.treeControl.expand(this.nestedNodeMap.get(parent))) | ||||
} | } | ||||
ngOnInit(): void { | ngOnInit(): void { | ||||
} | } | ||||
openOptions(node: RuleFlatNode) { | |||||
const onRemove = new Subject<RuleFlatNode>(); | |||||
const onAdd = new Subject<RuleFlatNode>(); | |||||
const onEdit = new Subject<RuleFlatNode>(); | |||||
this.bottomSheet.open(OptionsShetComponent, { | |||||
data: { | |||||
remove: onRemove, | |||||
add: onAdd, | |||||
edit: onEdit, | |||||
node: node | |||||
} | |||||
}); | |||||
onRemove.subscribe( | |||||
(node) => { | |||||
this.remove(node); | |||||
this.bottomSheet.dismiss(); | |||||
} | |||||
) | |||||
onAdd.subscribe( | |||||
(node) => { | |||||
this.addEmptyRule(node); | |||||
this.bottomSheet.dismiss(); | |||||
} | |||||
) | |||||
onEdit.subscribe( | |||||
(node) => { | |||||
this.openEdit(node); | |||||
this.bottomSheet.dismiss(); | |||||
} | |||||
) | |||||
} | |||||
openSnackBar(event: EventMessage) { | |||||
this._snackBar.openFromComponent(EventDialogComponent, { | |||||
data: event, | |||||
duration: 1000, | |||||
}); | |||||
} | |||||
getLevel = (node: RuleFlatNode) => node.level; | getLevel = (node: RuleFlatNode) => node.level; | ||||
isExpandable = (node: RuleFlatNode) => node.expandable; | isExpandable = (node: RuleFlatNode) => node.expandable; | ||||
getChildren = (node: Rule): Rule[] => node.subRule; | getChildren = (node: Rule): Rule[] => node.subRule; | ||||
hasChild = (_: number, node: RuleFlatNode) => node.expandable && !this.isDeleted(_, node); | |||||
hasChild = (_: number, node: RuleFlatNode) => node.expandable && !this.isDeleted(_, node) && !this.inEditMode(_, node); | |||||
inEditMode = (_: number, node: RuleFlatNode) => node.editMode; | |||||
isDeleted = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.deletedAt !== null; | isDeleted = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.deletedAt !== null; | ||||
hasNoContent = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.text === ''; | hasNoContent = (_: number, _nodeData: RuleFlatNode) => _nodeData.item.text === ''; | ||||
hasContent = (_nodeData: RuleFlatNode) => _nodeData.item.text !== ""; | hasContent = (_nodeData: RuleFlatNode) => _nodeData.item.text !== ""; | ||||
} | } | ||||
async addNode(newNode: RuleFlatNode, text: string) { | async addNode(newNode: RuleFlatNode, text: string) { | ||||
; | |||||
const newRule = this.flatNodeMap.get(newNode); | const newRule = this.flatNodeMap.get(newNode); | ||||
newRule.text = text; | newRule.text = text; | ||||
if (!!newRule.parentRule) { | if (!!newRule.parentRule) { | ||||
; | |||||
const parentRule = this._database.getParentById(newRule.parentRule.id) | const parentRule = this._database.getParentById(newRule.parentRule.id) | ||||
this._database.insertChild(parentRule, newRule) | this._database.insertChild(parentRule, newRule) | ||||
return; | return; | ||||
this._database.removeChild(parent, ruleToRemove); | this._database.removeChild(parent, ruleToRemove); | ||||
} | } | ||||
async openEdit(nodeToEdit: RuleFlatNode) { | |||||
nodeToEdit.editMode = true; | |||||
this._database.reload; | |||||
} | |||||
async restore(nodeToRestore: RuleFlatNode) { | async restore(nodeToRestore: RuleFlatNode) { | ||||
const ruleToRestore = this.flatNodeMap.get(nodeToRestore); | const ruleToRestore = this.flatNodeMap.get(nodeToRestore); | ||||
this._database.restore(ruleToRestore); | this._database.restore(ruleToRestore); | ||||
} | } | ||||
async updateNode(updatedNode: RuleFlatNode, updatedText: string) { | |||||
const parentNode = this.getParentNode(updatedNode); | |||||
if (parentNode) { | |||||
updatedNode.item.parentRule = { | |||||
id: parentNode.item.id, | |||||
text: parentNode.item.text, | |||||
paragraph: parentNode.item.text | |||||
} | |||||
} | |||||
const updatedRule = this.flatNodeMap.get(updatedNode); | |||||
updatedRule.text = updatedText; | |||||
this._database.update(updatedRule.id, updatedRule) | |||||
} | |||||
} | } |
<span> | |||||
<button *ngIf="config.node.root" mat-icon-button (click)="config.add.next(config.node)"> | |||||
<mat-icon>add</mat-icon> | |||||
</button> | |||||
<button mat-icon-button (click)="config.remove.next(config.node)"> | |||||
<mat-icon>remove</mat-icon> | |||||
</button> | |||||
<button mat-icon-button (click)="config.edit.next(config.node)"> | |||||
<mat-icon>edit</mat-icon> | |||||
</button> | |||||
</span> |
import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
import { OptionsShetComponent } from './options-shet.component'; | |||||
describe('OptionsShetComponent', () => { | |||||
let component: OptionsShetComponent; | |||||
let fixture: ComponentFixture<OptionsShetComponent>; | |||||
beforeEach(async () => { | |||||
await TestBed.configureTestingModule({ | |||||
declarations: [ OptionsShetComponent ] | |||||
}) | |||||
.compileComponents(); | |||||
}); | |||||
beforeEach(() => { | |||||
fixture = TestBed.createComponent(OptionsShetComponent); | |||||
component = fixture.componentInstance; | |||||
fixture.detectChanges(); | |||||
}); | |||||
it('should create', () => { | |||||
expect(component).toBeTruthy(); | |||||
}); | |||||
}); |
import { Component, Inject, OnInit } from '@angular/core'; | |||||
import { MAT_BOTTOM_SHEET_DATA } from '@angular/material/bottom-sheet'; | |||||
import { Subject } from 'rxjs'; | |||||
import { RuleFlatNode } from '../create-rule/class/rule-flat-node'; | |||||
@Component({ | |||||
selector: 'app-options-shet', | |||||
templateUrl: './options-shet.component.html', | |||||
styleUrls: ['./options-shet.component.scss'] | |||||
}) | |||||
export class OptionsShetComponent implements OnInit { | |||||
constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public config: { | |||||
add: Subject<RuleFlatNode>, | |||||
remove: Subject<RuleFlatNode>, | |||||
edit: Subject<RuleFlatNode>, | |||||
node: RuleFlatNode | |||||
}) { } | |||||
ngOnInit(): void { | |||||
} | |||||
} |
import { HTTP_INTERCEPTORS } from '@angular/common/http'; | import { HTTP_INTERCEPTORS } from '@angular/common/http'; | ||||
import { AuthInterceptor } from './auth/interceptor/auth.interceptor'; | import { AuthInterceptor } from './auth/interceptor/auth.interceptor'; | ||||
import { ErrorInterceptor } from './auth/interceptor/error.interceptor'; | import { ErrorInterceptor } from './auth/interceptor/error.interceptor'; | ||||
import { EventDialogComponent } from './event-dialog/event-dialog.component'; | |||||
@NgModule({ | @NgModule({ | ||||
declarations: [ | declarations: [ | ||||
DashboardComponent, | DashboardComponent, | ||||
GridComponent, | GridComponent, | ||||
MainComponent, | MainComponent, | ||||
EventDialogComponent, | |||||
], | ], | ||||
imports: [ | imports: [ | ||||
BrowserModule, | BrowserModule, |
// auto logout if 401 response returned from api | // auto logout if 401 response returned from api | ||||
this.authenticationService.logout(); | this.authenticationService.logout(); | ||||
} | } | ||||
const error = err.error.message || err.statusText; | |||||
; | |||||
const error = err.error?.message || err.statusText; | |||||
return throwError(error); | return throwError(error); | ||||
})) | })) | ||||
} | } |
<span [ngClass]="{primary: event.event !== 'Error', warn: event.event === 'Error'}"> | |||||
{{event.message}} | |||||
</span> |
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css'; | |||||
.primary {} | |||||
.warn { | |||||
background-color: red; | |||||
} |
import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
import { EventDialogComponent } from './event-dialog.component'; | |||||
describe('EventDialogComponent', () => { | |||||
let component: EventDialogComponent; | |||||
let fixture: ComponentFixture<EventDialogComponent>; | |||||
beforeEach(async () => { | |||||
await TestBed.configureTestingModule({ | |||||
declarations: [ EventDialogComponent ] | |||||
}) | |||||
.compileComponents(); | |||||
}); | |||||
beforeEach(() => { | |||||
fixture = TestBed.createComponent(EventDialogComponent); | |||||
component = fixture.componentInstance; | |||||
fixture.detectChanges(); | |||||
}); | |||||
it('should create', () => { | |||||
expect(component).toBeTruthy(); | |||||
}); | |||||
}); |
import { Component, Inject, Input, OnInit } from '@angular/core'; | |||||
import { MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar'; | |||||
import { EventMessage } from '../admin/create-rule/class/event-message'; | |||||
@Component({ | |||||
selector: 'app-event-dialog', | |||||
templateUrl: './event-dialog.component.html', | |||||
styleUrls: ['./event-dialog.component.scss'] | |||||
}) | |||||
export class EventDialogComponent implements OnInit { | |||||
constructor(@Inject(MAT_SNACK_BAR_DATA) public event: EventMessage) { } | |||||
ngOnInit(): void { | |||||
} | |||||
} |
<mat-toolbar> | <mat-toolbar> | ||||
<span > | |||||
<span> | |||||
<a href="https://ziermach.de/legal">legal</a> | <a href="https://ziermach.de/legal">legal</a> | ||||
</span | |||||
> | |||||
</mat-toolbar> | |||||
</span> | |||||
<span [routerLink]="['/admin/create']"> | |||||
<mat-icon>add</mat-icon> | |||||
</span> | |||||
</mat-toolbar> |
.mat-toolbar { | .mat-toolbar { | ||||
height: 3%; | |||||
height: 3%; | |||||
background: transparent; | |||||
color: white; | |||||
font-size: small; | |||||
} | } | ||||
a { | |||||
color: white | |||||
} |
import { CommonModule } from '@angular/common'; | import { CommonModule } from '@angular/common'; | ||||
import { FooterComponent } from './footer.component'; | import { FooterComponent } from './footer.component'; | ||||
import { MatToolbarModule } from '@angular/material/toolbar'; | import { MatToolbarModule } from '@angular/material/toolbar'; | ||||
import { MatIconModule } from '@angular/material/icon'; | |||||
import { RouterModule } from '@angular/router'; | |||||
], | ], | ||||
imports: [ | imports: [ | ||||
CommonModule, | CommonModule, | ||||
MatToolbarModule | |||||
MatToolbarModule, | |||||
MatIconModule, | |||||
RouterModule, | |||||
], | ], | ||||
exports: [ | exports: [ | ||||
FooterComponent | FooterComponent |
<mat-sidenav-content class="page-wrap"> | <mat-sidenav-content class="page-wrap"> | ||||
<mat-toolbar color="primary"> | <mat-toolbar color="primary"> | ||||
<span routerLink="" class="mat-title title"> | <span routerLink="" class="mat-title title"> | ||||
<mat-icon mat-list-icon class="logo">bedroom_baby</mat-icon> Hof Hoppe</span | |||||
<mat-icon mat-list-icon class="logo">bedroom_baby</mat-icon> Hof Hoppe</span | |||||
> | > | ||||
</mat-toolbar> | |||||
<main class="content"> | |||||
<router-outlet></router-outlet> | |||||
</main> | |||||
</mat-sidenav-content> | |||||
</mat-toolbar> | |||||
<main class="content"> | |||||
<router-outlet></router-outlet> | |||||
</main> | |||||
<app-footer></app-footer> | |||||
</mat-sidenav-content> | |||||
</mat-sidenav-container> | </mat-sidenav-container> |
<mat-card class="dashboard-card wood-card"> | <mat-card class="dashboard-card wood-card"> | ||||
<mat-card-header> | |||||
<mat-card-title class="title mat-title"> | |||||
<mat-icon mat-list-icon class="logo">gavel</mat-icon> Regeln | |||||
</mat-card-title> | |||||
</mat-card-header> | |||||
<mat-card-content class="dashboard-card-content"> | |||||
<app-rule-list [rules]="rules"></app-rule-list> | |||||
</mat-card-content> | |||||
</mat-card> | |||||
<mat-card-header> | |||||
<mat-card-title class="title mat-title"> | |||||
<mat-icon mat-list-icon class="logo">gavel</mat-icon> Regeln | |||||
</mat-card-title> | |||||
</mat-card-header> | |||||
<mat-card-content class="dashboard-card-content"> | |||||
<app-rule-list [rules]="rules"></app-rule-list> | |||||
</mat-card-content> | |||||
</mat-card> |
import { Rule } from '../../../../api/model/rule'; | import { Rule } from '../../../../api/model/rule'; | ||||
import { RulesService } from '../../../../api/api/rules.service'; | import { RulesService } from '../../../../api/api/rules.service'; | ||||
import { RuleDataService } from '../../service/rule-data.service'; | import { RuleDataService } from '../../service/rule-data.service'; | ||||
import { Router } from '@angular/router'; | |||||
@Component({ | @Component({ | ||||
selector: 'app-rules', | selector: 'app-rules', | ||||
templateUrl: './rules.component.html', | templateUrl: './rules.component.html', | ||||
}) | }) | ||||
export class RulesComponent implements OnInit { | export class RulesComponent implements OnInit { | ||||
rules: Rule[] = [] | rules: Rule[] = [] | ||||
constructor(protected rulesService: RulesService,protected ruleDataService: RuleDataService) { | |||||
constructor( | |||||
protected rulesService: RulesService, | |||||
protected ruleDataService: RuleDataService, | |||||
protected router: Router, | |||||
) { | |||||
rulesService.configuration.basePath = 'http://localhost:3000'; | rulesService.configuration.basePath = 'http://localhost:3000'; | ||||
} | } | ||||
}) | }) | ||||
} | } | ||||
openAdminArea() { | |||||
this.router.navigate(['/admin/create']); | |||||
} | |||||
} | } |