--- /dev/null
+<eg-staff-banner bannerText="Permission Group Configuration" i18n-bannerText>
+</eg-staff-banner>
+
+<ng-template #editStrTmpl i18n>Permission Group Update Succeeded</ng-template>
+<eg-string #editString [template]="editStrTmpl"></eg-string>
+
+<ng-template #createStrTmpl i18n>Permission Group Succeessfully Created</ng-template>
+<eg-string #createString [template]="createStrTmpl"></eg-string>
+
+<ng-template #errorStrTmpl i18n>Permission Group Update Failed</ng-template>
+<eg-string #errorString [template]="errorStrTmpl"></eg-string>
+
+<eg-confirm-dialog #delConfirm
+ i18n-dialogTitle i18n-dialogBody
+ dialogTitle="Confirm Delete"
+ dialogBody="Delete Permission Group {{selected ? selected.label : ''}}?">
+</eg-confirm-dialog>
+
+<eg-fm-record-editor #editDialog idlClass="pgt" readonlyFields="parent"
+ [fieldOptions]="fmEditorOptions()">
+</eg-fm-record-editor>
+
+<div class="row">
+ <div class="col-lg-4">
+ <h3 i18n>Permission Groups</h3>
+ <eg-tree [tree]="tree" (nodeClicked)="nodeClicked($event)"></eg-tree>
+ </div>
+ <div class="col-lg-8">
+ <h3 i18n class="mb-3">Selected Permission Group</h3>
+ <ng-container *ngIf="!selected">
+ <div class="alert alert-info font-italic" i18n>
+ Select an org unit type from the tree on the left.
+ </div>
+ </ng-container>
+ <div *ngIf="selected" class="common-form striped-even">
+ <ngb-tabset>
+ <ngb-tab title="Group Details" i18n-title id="detail">
+ <ng-template ngbTabContent>
+ <div class="row">
+ <div class="col-lg-3">
+ <label i18n>Actions for Selected: </label>
+ </div>
+ <div class="col-lg-9">
+ <button class="btn btn-info mr-2" (click)="edit()" i18n>Edit</button>
+ <button class="btn btn-info mr-2" (click)="addChild()" i18n>Add Child</button>
+ <button class="btn btn-warning mr-2" (click)="remove()" i18n>Delete</button>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-4">
+ <label i18n>Name: </label>
+ </div>
+ <div class="col-lg-8 font-weight-bold">
+ {{selected.callerData.name()}}
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="Group Permission" i18n-title id="perm">
+ <ng-template ngbTabContent>
+ </ng-template>
+ </ngb-tab>
+ </ngb-tabset>
+ </div>
+ </div>
+</div>
--- /dev/null
+import {Component, Input, ViewChild, OnInit} from '@angular/core';
+import {Tree, TreeNode} from '@eg/share/tree/tree';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {FmRecordEditorComponent, FmFieldOptions} from '@eg/share/fm-editor/fm-editor.component';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+ templateUrl: './perm-group-tree.component.html'
+})
+
+export class PermGroupTreeComponent implements OnInit {
+
+ tree: Tree;
+ selected: TreeNode;
+ permissions: ComboboxEntry[];
+
+ @ViewChild('editDialog') editDialog: FmRecordEditorComponent;
+ @ViewChild('editString') editString: StringComponent;
+ @ViewChild('createString') createString: StringComponent;
+ @ViewChild('errorString') errorString: StringComponent;
+ @ViewChild('delConfirm') delConfirm: ConfirmDialogComponent;
+
+ constructor(
+ private idl: IdlService,
+ private org: OrgService,
+ private auth: AuthService,
+ private pcrud: PcrudService,
+ private toast: ToastService
+ ) {
+ this.permissions = [];
+ }
+
+
+ ngOnInit() {
+ this.loadPgtTree();
+ }
+
+ loadPgtTree() {
+ this.pcrud.search('pgt', {parent: null},
+ {flesh: -1, flesh_fields: {pgt: ['children']}}
+ ).subscribe(pgtTree => this.ingestPgtTree(pgtTree));
+
+ // ComboboxEntry's for perms uses code() for id instead of
+ // the database ID, because the application_perm field on
+ // "pgt" is text instead of a link. So the value it expects
+ // is the code, not the ID.
+ this.pcrud.retrieveAll('ppl', {order_by: {ppl: ['name']}})
+ .subscribe(perm =>
+ this.permissions.push({id: perm.code(), label: perm.code()}));
+ }
+
+ fmEditorOptions(): {[fieldName: string]: FmFieldOptions} {
+ return {
+ application_perm: {
+ customValues: this.permissions
+ }
+ };
+ }
+
+ // Translate the org unt type tree into a structure EgTree can use.
+ ingestPgtTree(pgtTree) {
+
+ const handleNode = (pgtNode: IdlObject): TreeNode => {
+ if (!pgtNode) { return; }
+
+ const treeNode = new TreeNode({
+ id: pgtNode.id(),
+ label: pgtNode.name(),
+ callerData: pgtNode
+ });
+
+ pgtNode.children().forEach(childNode =>
+ treeNode.children.push(handleNode(childNode))
+ );
+
+ return treeNode;
+ };
+
+ const rootNode = handleNode(pgtTree);
+ this.tree = new Tree(rootNode);
+ }
+
+ nodeClicked($event: any) {
+ this.selected = $event;
+ }
+
+ postUpdate(message: StringComponent) {
+ // Modifying org unit types means refetching the org unit
+ // data normally fetched on page load, since it includes
+ // org unit type data.
+ this.org.fetchOrgs().then(
+ ok => {
+ message.current().then(str => this.toast.success(str));
+ }
+ );
+ }
+
+ edit() {
+ this.editDialog.mode = 'update';
+ this.editDialog.setRecord(this.selected.callerData);
+
+ this.editDialog.open({size: 'lg'}).then(
+ success => {
+ this.postUpdate(this.editString);
+ },
+ rejected => {
+ if (rejected && rejected.dismissed) {
+ return;
+ }
+ this.errorString.current()
+ .then(str => this.toast.danger(str));
+ }
+ );
+ }
+
+ remove() {
+ this.delConfirm.open().then(
+ ok => {
+ this.pcrud.remove(this.selected.callerData)
+ .subscribe(
+ ok2 => {},
+ err => {
+ this.errorString.current()
+ .then(str => this.toast.danger(str));
+ },
+ () => {
+ // Avoid updating until we know the entire
+ // pcrud action/transaction completed.
+ this.tree.removeNode(this.selected);
+ this.selected = null;
+ this.postUpdate(this.editString);
+ }
+ );
+ },
+ notConfirmed => {}
+ );
+ }
+
+ addChild() {
+ const parentTreeNode = this.selected;
+ const parentType = parentTreeNode.callerData;
+
+ const newType = this.idl.create('pgt');
+ newType.parent(parentType.id());
+
+ this.editDialog.setRecord(newType);
+ this.editDialog.mode = 'create';
+
+ this.editDialog.open({size: 'lg'}).then(
+ result => { // pgt object
+
+ // Add our new node to the tree
+ const newNode = new TreeNode({
+ id: result.id(),
+ label: result.name(),
+ callerData: result
+ });
+ parentTreeNode.children.push(newNode);
+ this.postUpdate(this.createString);
+ },
+
+ rejected => {
+ if (rejected && rejected.dismissed) {
+ return;
+ }
+ this.errorString.current()
+ .then(str => this.toast.danger(str));
+ }
+ );
+ }
+}
+