lp1852321 Angular Shelving Location Groups UI Port
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / admin / local / shelving_location_groups / shelving_location_groups.component.ts
1 import {Component, OnInit, Input, ViewChild} from '@angular/core';
2 import {IdlService, IdlObject} from '@eg/core/idl.service';
3 import {OrgService} from '@eg/core/org.service';
4 import {PcrudService} from '@eg/core/pcrud.service';
5 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
6 import {StringComponent} from '@eg/share/string/string.component';
7 import {ToastService} from '@eg/share/toast/toast.service';
8 import {PermService} from '@eg/core/perm.service';
9
10 @Component({
11     templateUrl: './shelving_location_groups.component.html'
12 })
13
14 export class ShelvingLocationGroupsComponent implements OnInit {
15
16     selectedOrg: IdlObject;
17     selectedOrgId = 1;
18     locationGroups: IdlObject[];
19     shelvingLocations: IdlObject[];
20     groupEntries: IdlObject[];
21     selectedLocationGroupId: number;
22     selectedLocationGroup: IdlObject;
23     permissions: number[];
24     hasPermission = false;
25     draggedElement: IdlObject;
26     dragTarget: IdlObject;
27     defaultNewRecord: IdlObject;
28
29     @ViewChild ('editDialog', { static: true }) private editDialog: FmRecordEditorComponent;
30     @ViewChild ('editLocGroupSuccess', {static: true}) editLocGroupSuccess: StringComponent;
31     @ViewChild ('editLocGroupFailure', {static: true}) editLocGroupFailure: StringComponent;
32     @ViewChild ('addedGroupEntriesSuccess', {static: true})
33         addedGroupEntriesSuccess: StringComponent;
34     @ViewChild ('addedGroupEntriesFailure', {static: true})
35         addedGroupEntriesFailure: StringComponent;
36     @ViewChild ('removedGroupEntriesSuccess', {static: true})
37         removedGroupEntriesSuccess: StringComponent;
38     @ViewChild ('removedGroupEntriesFailure', {static: true})
39         removedGroupEntriesFailure: StringComponent;
40     @ViewChild ('changeOrderSuccess', {static: true}) changeOrderSuccess: StringComponent;
41     @ViewChild ('changeOrderFailure', {static: true}) changeOrderFailure: StringComponent;
42
43     constructor(
44         private org: OrgService,
45         private pcrud: PcrudService,
46         private toast: ToastService,
47         private idl: IdlService,
48         private perm: PermService
49     ) {
50        this.permissions = [];
51     }
52
53     ngOnInit() {
54         this.loadLocationGroups();
55         this.perm.hasWorkPermAt(['ADMIN_COPY_LOCATION_GROUP'], true).then((perm) => {
56             this.permissions = perm['ADMIN_COPY_LOCATION_GROUP'];
57             this.checkCurrentPermissions();
58         });
59     }
60
61     checkCurrentPermissions = () => {
62         this.hasPermission =
63             (this.permissions.indexOf(this.selectedOrgId) !== -1);
64     }
65
66     createLocationGroup = () => {
67         this.editDialog.mode = 'create';
68         this.defaultNewRecord = this.idl.create('acplg');
69         this.defaultNewRecord.owner(this.selectedOrgId);
70         let highestPosition = 0;
71         if (this.locationGroups.length) {
72             highestPosition = this.locationGroups[0].posit;
73             this.locationGroups.forEach(grp => {
74                 if (grp.posit > highestPosition) {
75                     highestPosition = grp.posit;
76                 }
77             });
78         }
79         // make the new record the last one on the list
80         this.defaultNewRecord.pos(highestPosition + 1);
81         this.editDialog.record = this.defaultNewRecord;
82         this.editDialog.recordId = null;
83         this.editDialog.open({size: 'lg'}).subscribe(
84             newLocationGroup => {
85                 this.processLocationGroup(newLocationGroup);
86                 this.locationGroups.push(newLocationGroup);
87                 // select it by default if it's the only location group
88                 if (this.locationGroups.length === 1) {
89                     this.markAsSelected(newLocationGroup);
90                 } else {
91                     this.sortLocationGroups();
92                 }
93                 console.debug('Record editor performed action');
94             }, err => {
95                 console.debug(err);
96             }
97         );
98     }
99
100     processLocationGroup = (locationGroup) => {
101         locationGroup.isVisible = (locationGroup.opac_visible() === 't');
102         locationGroup.posit = locationGroup.pos();
103         locationGroup.name = locationGroup.name();
104     }
105
106     editLocationGroup = (group) => {
107         this.editDialog.mode = 'update';
108         this.editDialog.recordId = group.id();
109         this.editDialog.open({size: 'lg'}).subscribe(
110             id => {
111                 console.debug('Record editor performed action');
112                 this.loadLocationGroups();
113             },
114             err => {
115                 console.debug(err);
116             },
117             () => console.debug('Dialog closed')
118         );
119     }
120
121     deleteLocationGroup = (locationGroupToDelete) => {
122         const idToDelete = locationGroupToDelete.id();
123         this.pcrud.remove(locationGroupToDelete).subscribe(
124             ok => {
125                 this.locationGroups.forEach((locationGroup, index) => {
126                     if (locationGroup.id() === idToDelete) {
127                         this.locationGroups.splice(index, 1);
128                     }
129                 });
130             },
131             err => console.debug(err)
132         );
133     }
134
135     sortLocationGroups = () => {
136         this.locationGroups.sort((a, b) => (a.posit > b.posit) ? 1 : -1);
137     }
138
139     loadLocationGroups = () => {
140         this.locationGroups = [];
141         this.pcrud.search('acplg', {owner: this.selectedOrgId}, {
142             flesh: 1,
143             flesh_fields: {acplg: ['opac_visible', 'pos', 'name']},
144             order_by: {acplg: 'owner'}
145         }).subscribe(data => {
146             this.processLocationGroup(data);
147             this.locationGroups.push(data);
148         }, (error) => {
149             console.debug(error);
150         }, () => {
151             this.sortLocationGroups();
152             if (this.locationGroups.length) {
153                 this.markAsSelected(this.locationGroups[0]);
154             }
155             this.loadGroupEntries();
156         });
157     }
158
159     changeSelectedLocationGroup = (group) => {
160         this.selectedLocationGroup.selected = false;
161         this.markAsSelected(group);
162         this.loadGroupEntries();
163     }
164
165     markAsSelected = (locationGroup) => {
166         this.selectedLocationGroup = locationGroup;
167         this.selectedLocationGroup.selected = true;
168         this.selectedLocationGroupId = locationGroup.id();
169     }
170
171     loadGroupEntries = () => {
172         this.groupEntries = [];
173         this.pcrud.search('acplgm', {lgroup: this.selectedLocationGroupId}, {
174             flesh: 1,
175             flesh_fields: {acplgm: ['location']},
176             order_by: {acplgm: ['location']}
177         }).subscribe(data => {
178             data.name = data.location().name();
179             data.shortname = this.org.get(data.location().owning_lib()).shortname();
180             // remove all non-alphanumeric chars to make label a valid id
181             data.label = (data.shortname + data.name).replace(/\W/g, '');
182             data.checked = false;
183             this.groupEntries.push(data);
184         }, (error) => {
185             console.debug(error);
186         }, () => {
187             this.loadShelvingLocations();
188         });
189     }
190
191     loadShelvingLocations = () => {
192         let orgList = this.org.fullPath(this.selectedOrgId, false);
193         orgList.sort(function(a, b) {
194             return a.ou_type().depth() < b.ou_type().depth() ? -1 : 1;
195         });
196         orgList = orgList.map((member) => {
197             return member.id();
198         });
199         const groupEntryIds = this.groupEntries.map(
200             (group) => group.location().id());
201         this.shelvingLocations = [];
202         this.pcrud.search('acpl', {owning_lib : orgList, deleted: 'f'})
203         .subscribe(data => {
204             data.name = data.name();
205             data.shortname = this.org.get(data.owning_lib()).shortname();
206             // remove all non-alphanumeric chars to make label a valid id
207             data.label = (data.shortname + data.name).replace(/\W/g, '');
208             data.checked = false;
209             if (groupEntryIds.indexOf(data.id()) === -1) {
210                 data.hidden = false;
211             } else {
212                 data.hidden = true;
213             }
214             this.shelvingLocations.push(data);
215         }, (error) => {
216             console.debug(error);
217         }, () => {
218             this.shelvingLocations.sort(function(a, b) {
219                 return a.name < b.name ? -1 : 1;
220             });
221             const sortedShelvingLocations = [];
222             // order our array primarily by location
223             orgList.forEach(member => {
224                 const currentLocationArray = this.shelvingLocations.filter((loc) => {
225                     return (member === loc.owning_lib());
226                 });
227                 Array.prototype.push.apply(sortedShelvingLocations, currentLocationArray);
228             });
229             this.shelvingLocations = sortedShelvingLocations;
230         });
231     }
232
233     addEntries = () => {
234         const checkedEntries = this.shelvingLocations.filter((entry) => {
235             return entry.checked;
236         });
237         checkedEntries.forEach((entry) => {
238             const newGroupEntry = this.idl.create('acplgm');
239             newGroupEntry.location(entry);
240             newGroupEntry.lgroup(this.selectedLocationGroup.id());
241             this.pcrud.create(newGroupEntry).subscribe(
242                 newEntry => {
243                     // hide item so it won't show on on list of shelving locations
244                     entry.hidden = true;
245                     entry.checked = false;
246                     newEntry.checked = false;
247                     newEntry.location(entry);
248                     newEntry.name = entry.name;
249                     newEntry.shortname = entry.shortname;
250                     this.groupEntries.push(newEntry);
251                     this.addedGroupEntriesSuccess.current().then(msg =>
252                         this.toast.success(msg));
253                 },
254                 err => {
255                     console.debug(err);
256                     this.addedGroupEntriesFailure.current().then(msg => this.toast.warning(msg));
257                 }
258             );
259         });
260     }
261
262     removeEntries = () => {
263         const checkedEntries = this.groupEntries.filter((entry) => {
264             return entry.checked;
265         });
266         this.pcrud.remove(checkedEntries).subscribe(
267             idRemoved => {
268                 idRemoved = parseInt(idRemoved, 10);
269                 let deletedName;
270                 let deletedShortName;
271                 // on pcrud success, remove from local group entries array
272                 this.groupEntries = this.groupEntries.filter((entry) => {
273                     if (entry.id() === idRemoved) {
274                         deletedName = entry.name;
275                         deletedShortName = entry.shortname;
276                     }
277                     return (entry.id() !== idRemoved);
278                 });
279                 // show the entry on list of shelving locations
280                 this.shelvingLocations.forEach((location) => {
281                     if ((location.name === deletedName) && (location.shortname ===
282                         deletedShortName)) {
283                         location.hidden = false;
284                     }
285                 });
286                 this.removedGroupEntriesSuccess.current().then(msg =>
287                     this.toast.success(msg));
288             }, (error) => {
289                 console.debug(error);
290                 this.removedGroupEntriesFailure.current().then(msg =>
291                     this.toast.warning(msg));
292             }
293         );
294     }
295
296     orgOnChange = (org: IdlObject): void => {
297         this.selectedOrg = org;
298         this.selectedOrgId = org.id();
299         this.loadLocationGroups();
300         this.checkCurrentPermissions();
301     }
302
303     onDragStart = (event, locationGroup) => {
304         this.draggedElement = locationGroup;
305     }
306
307     onDragOver = (event) => {
308         event.preventDefault();
309     }
310
311     onDragEnter = (event, locationGroup) => {
312         this.dragTarget = locationGroup;
313         // remove border where we previously were dragging
314         if (event.relatedTarget) {
315             event.relatedTarget.parentElement.style.borderTop = 'none';
316         }
317         // add border above target location group
318         if (event.target.parentElement !== null) {
319             if (event.target.parentElement.classList.contains('locationGroup')) {
320                 event.target.parentElement.style.borderTop = '1px solid black';
321             }
322         }
323     }
324
325     onDragDrop = (event, index) => {
326         // do nothing if element is dragged onto itself
327         if (this.draggedElement !== this.dragTarget) {
328             this.assignNewPositions(index);
329         }
330         event.target.parentElement.style.borderTop = 'none';
331         this.draggedElement = null;
332         this.dragTarget = null;
333     }
334
335     assignNewPositions (index) {
336         const endingPos = this.dragTarget.posit;
337         const locationGroupsToUpdate = [];
338         this.draggedElement.pos(endingPos);
339         this.draggedElement.posit = endingPos;
340         locationGroupsToUpdate.push(this.draggedElement);
341         // add 1 to the position of all groups after the one we inserted
342         for (let i = index; i < this.locationGroups.length; i++) {
343             // we already processed the item being dragged; skip it
344             if (this.locationGroups[i] === this.draggedElement) { continue; }
345             const newPosition = this.locationGroups[i].posit + 1;
346             this.locationGroups[i].pos(newPosition);
347             this.locationGroups[i].posit = newPosition;
348             locationGroupsToUpdate.push(this.locationGroups[i]);
349         }
350         this.saveNewPositions(locationGroupsToUpdate);
351     }
352
353     saveNewPositions (locationGroupsToUpdate) {
354         let errorHappened = false;
355         this.pcrud.update(locationGroupsToUpdate).subscribe(
356             ok => {
357                 console.debug('Record editor performed action');
358             },
359             err => {
360                 console.debug(err);
361                 errorHappened = true;
362             },
363             () => {
364                 this.sortLocationGroups();
365                 if (errorHappened) {
366                     this.changeOrderFailure.current().then(msg =>
367                         this.toast.warning(msg));
368                 } else {
369                     this.changeOrderSuccess.current().then(msg =>
370                         this.toast.success(msg));
371                 }
372             }
373         );
374     }
375 }