tango-app-ui-manage-tickets 3.7.0-beta.69 → 3.7.0-beta.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/.eslintrc.json +37 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +12 -25
  4. package/src/lib/components/add-csm-modal/add-csm-modal.component.html +32 -0
  5. package/src/lib/components/add-csm-modal/add-csm-modal.component.scss +14 -0
  6. package/src/lib/components/add-csm-modal/add-csm-modal.component.spec.ts +23 -0
  7. package/src/lib/components/add-csm-modal/add-csm-modal.component.ts +94 -0
  8. package/src/lib/components/audit-log/audit-log.component.html +1 -0
  9. package/src/lib/components/audit-log/audit-log.component.scss +0 -0
  10. package/src/lib/components/audit-log/audit-log.component.spec.ts +23 -0
  11. package/src/lib/components/audit-log/audit-log.component.ts +10 -0
  12. package/src/lib/components/audit-mapping-list/audit-mapping-list.component.html +234 -0
  13. package/src/lib/components/audit-mapping-list/audit-mapping-list.component.scss +186 -0
  14. package/src/lib/components/audit-mapping-list/audit-mapping-list.component.spec.ts +23 -0
  15. package/src/lib/components/audit-mapping-list/audit-mapping-list.component.ts +536 -0
  16. package/src/lib/components/audit-metrics/audit-metrics.component.html +345 -0
  17. package/src/lib/components/audit-metrics/audit-metrics.component.scss +34 -0
  18. package/src/lib/components/audit-metrics/audit-metrics.component.spec.ts +23 -0
  19. package/src/lib/components/audit-metrics/audit-metrics.component.ts +292 -0
  20. package/src/lib/components/audit-report-popup/audit-report-popup.component.html +111 -0
  21. package/src/lib/components/audit-report-popup/audit-report-popup.component.scss +101 -0
  22. package/src/lib/components/audit-report-popup/audit-report-popup.component.spec.ts +23 -0
  23. package/src/lib/components/audit-report-popup/audit-report-popup.component.ts +397 -0
  24. package/src/lib/components/audit-retag/audit-retag.component.html +129 -0
  25. package/src/lib/components/audit-retag/audit-retag.component.scss +146 -0
  26. package/src/lib/components/audit-retag/audit-retag.component.spec.ts +23 -0
  27. package/src/lib/components/audit-retag/audit-retag.component.ts +497 -0
  28. package/src/lib/components/comment-model/comment-model.component.html +24 -0
  29. package/src/lib/components/comment-model/comment-model.component.scss +20 -0
  30. package/src/lib/components/comment-model/comment-model.component.spec.ts +23 -0
  31. package/src/lib/components/comment-model/comment-model.component.ts +53 -0
  32. package/src/lib/components/count/count.component.html +54 -0
  33. package/src/lib/components/count/count.component.scss +14 -0
  34. package/src/lib/components/count/count.component.spec.ts +23 -0
  35. package/src/lib/components/count/count.component.ts +82 -0
  36. package/src/lib/components/custom-select/custom-select.component.html +134 -0
  37. package/src/lib/components/custom-select/custom-select.component.scss +204 -0
  38. package/src/lib/components/custom-select/custom-select.component.spec.ts +23 -0
  39. package/src/lib/components/custom-select/custom-select.component.ts +189 -0
  40. package/src/lib/components/filter-options/filter-options.component.html +51 -0
  41. package/src/lib/components/filter-options/filter-options.component.scss +102 -0
  42. package/src/lib/components/filter-options/filter-options.component.spec.ts +23 -0
  43. package/src/lib/components/filter-options/filter-options.component.ts +38 -0
  44. package/src/lib/components/footfall-dic/footfall-dic.component.html +1275 -0
  45. package/src/lib/components/footfall-dic/footfall-dic.component.scss +273 -0
  46. package/src/lib/components/footfall-dic/footfall-dic.component.spec.ts +23 -0
  47. package/src/lib/components/footfall-dic/footfall-dic.component.ts +1206 -0
  48. package/src/lib/components/footfall-dicview/footfall-dicview.component.html +1136 -0
  49. package/src/lib/components/footfall-dicview/footfall-dicview.component.scss +416 -0
  50. package/src/lib/components/footfall-dicview/footfall-dicview.component.spec.ts +23 -0
  51. package/src/lib/components/footfall-dicview/footfall-dicview.component.ts +1168 -0
  52. package/src/lib/components/footfall-popup/footfall-popup.component.html +61 -0
  53. package/src/lib/components/footfall-popup/footfall-popup.component.scss +20 -0
  54. package/src/lib/components/footfall-popup/footfall-popup.component.spec.ts +23 -0
  55. package/src/lib/components/footfall-popup/footfall-popup.component.ts +12 -0
  56. package/src/lib/components/group-select/group-select.component.html +44 -0
  57. package/src/lib/components/group-select/group-select.component.scss +144 -0
  58. package/src/lib/components/group-select/group-select.component.spec.ts +23 -0
  59. package/src/lib/components/group-select/group-select.component.ts +145 -0
  60. package/src/lib/components/re-trigger/re-trigger.component.html +53 -0
  61. package/src/lib/components/re-trigger/re-trigger.component.scss +16 -0
  62. package/src/lib/components/re-trigger/re-trigger.component.spec.ts +23 -0
  63. package/src/lib/components/re-trigger/re-trigger.component.ts +96 -0
  64. package/src/lib/components/reactive-select/reactive-select.component.html +18 -0
  65. package/src/lib/components/reactive-select/reactive-select.component.scss +52 -0
  66. package/src/lib/components/reactive-select/reactive-select.component.spec.ts +23 -0
  67. package/src/lib/components/reactive-select/reactive-select.component.ts +104 -0
  68. package/src/lib/components/remove-audit/remove-audit.component.html +38 -0
  69. package/src/lib/components/remove-audit/remove-audit.component.scss +27 -0
  70. package/src/lib/components/remove-audit/remove-audit.component.spec.ts +23 -0
  71. package/src/lib/components/remove-audit/remove-audit.component.ts +81 -0
  72. package/src/lib/components/start-audit/start-audit.component.html +174 -0
  73. package/src/lib/components/start-audit/start-audit.component.scss +185 -0
  74. package/src/lib/components/start-audit/start-audit.component.spec.ts +23 -0
  75. package/src/lib/components/start-audit/start-audit.component.ts +772 -0
  76. package/src/lib/components/tango-manage-tickets/tango-manage-tickets.component.html +43 -0
  77. package/src/lib/components/tango-manage-tickets/tango-manage-tickets.component.scss +35 -0
  78. package/src/lib/components/tango-manage-tickets/tango-manage-tickets.component.spec.ts +23 -0
  79. package/src/lib/components/tango-manage-tickets/tango-manage-tickets.component.ts +118 -0
  80. package/src/lib/components/ticket-filter-panel/ticket-filter-panel.component.html +386 -0
  81. package/src/lib/components/ticket-filter-panel/ticket-filter-panel.component.scss +87 -0
  82. package/src/lib/components/ticket-filter-panel/ticket-filter-panel.component.spec.ts +23 -0
  83. package/src/lib/components/ticket-filter-panel/ticket-filter-panel.component.ts +493 -0
  84. package/src/lib/components/ticket-footfall-new/ticket-footfall-new.component.html +3751 -0
  85. package/src/lib/components/ticket-footfall-new/ticket-footfall-new.component.scss +1240 -0
  86. package/src/lib/components/ticket-footfall-new/ticket-footfall-new.component.spec.ts +23 -0
  87. package/src/lib/components/ticket-footfall-new/ticket-footfall-new.component.ts +2863 -0
  88. package/src/lib/components/ticketclosepopup/ticketclosepopup.component.html +100 -0
  89. package/src/lib/components/ticketclosepopup/ticketclosepopup.component.scss +34 -0
  90. package/src/lib/components/ticketclosepopup/ticketclosepopup.component.spec.ts +23 -0
  91. package/src/lib/components/ticketclosepopup/ticketclosepopup.component.ts +48 -0
  92. package/src/lib/components/tickets/tickets.component.html +451 -0
  93. package/src/lib/components/tickets/tickets.component.scss +131 -0
  94. package/src/lib/components/tickets/tickets.component.spec.ts +23 -0
  95. package/src/lib/components/tickets/tickets.component.ts +809 -0
  96. package/src/lib/components/viewcategory/viewcategory.component.html +38 -0
  97. package/src/lib/components/viewcategory/viewcategory.component.scss +29 -0
  98. package/src/lib/components/viewcategory/viewcategory.component.spec.ts +23 -0
  99. package/src/lib/components/viewcategory/viewcategory.component.ts +79 -0
  100. package/src/lib/services/audit.service.spec.ts +16 -0
  101. package/src/lib/services/audit.service.ts +98 -0
  102. package/src/lib/services/excel.service.ts +48 -0
  103. package/src/lib/services/ticket.service.spec.ts +16 -0
  104. package/src/lib/services/ticket.service.ts +501 -0
  105. package/src/lib/services/timer.service.spec.ts +16 -0
  106. package/src/lib/services/timer.service.ts +92 -0
  107. package/src/lib/tango-manage-tickets-routing.module.ts +37 -0
  108. package/src/lib/tango-manage-tickets.module.ts +68 -0
  109. package/{public-api.d.ts → src/public-api.ts} +8 -2
  110. package/tsconfig.lib.json +14 -0
  111. package/tsconfig.lib.prod.json +10 -0
  112. package/tsconfig.spec.json +14 -0
  113. package/esm2022/lib/components/add-csm-modal/add-csm-modal.component.mjs +0 -98
  114. package/esm2022/lib/components/audit-log/audit-log.component.mjs +0 -11
  115. package/esm2022/lib/components/audit-mapping-list/audit-mapping-list.component.mjs +0 -498
  116. package/esm2022/lib/components/audit-metrics/audit-metrics.component.mjs +0 -298
  117. package/esm2022/lib/components/audit-report-popup/audit-report-popup.component.mjs +0 -389
  118. package/esm2022/lib/components/audit-retag/audit-retag.component.mjs +0 -480
  119. package/esm2022/lib/components/comment-model/comment-model.component.mjs +0 -58
  120. package/esm2022/lib/components/count/count.component.mjs +0 -89
  121. package/esm2022/lib/components/custom-select/custom-select.component.mjs +0 -187
  122. package/esm2022/lib/components/filter-options/filter-options.component.mjs +0 -41
  123. package/esm2022/lib/components/footfall-dic/footfall-dic.component.mjs +0 -1061
  124. package/esm2022/lib/components/footfall-dicview/footfall-dicview.component.mjs +0 -1014
  125. package/esm2022/lib/components/footfall-popup/footfall-popup.component.mjs +0 -15
  126. package/esm2022/lib/components/group-select/group-select.component.mjs +0 -155
  127. package/esm2022/lib/components/re-trigger/re-trigger.component.mjs +0 -96
  128. package/esm2022/lib/components/reactive-select/reactive-select.component.mjs +0 -108
  129. package/esm2022/lib/components/remove-audit/remove-audit.component.mjs +0 -81
  130. package/esm2022/lib/components/start-audit/start-audit.component.mjs +0 -758
  131. package/esm2022/lib/components/tango-manage-tickets/tango-manage-tickets.component.mjs +0 -131
  132. package/esm2022/lib/components/ticket-filter-panel/ticket-filter-panel.component.mjs +0 -435
  133. package/esm2022/lib/components/ticket-footfall-new/ticket-footfall-new.component.mjs +0 -2414
  134. package/esm2022/lib/components/ticketclosepopup/ticketclosepopup.component.mjs +0 -43
  135. package/esm2022/lib/components/tickets/tickets.component.mjs +0 -847
  136. package/esm2022/lib/components/viewcategory/viewcategory.component.mjs +0 -89
  137. package/esm2022/lib/services/audit.service.mjs +0 -88
  138. package/esm2022/lib/services/excel.service.mjs +0 -45
  139. package/esm2022/lib/services/ticket.service.mjs +0 -319
  140. package/esm2022/lib/services/timer.service.mjs +0 -84
  141. package/esm2022/lib/tango-manage-tickets-routing.module.mjs +0 -44
  142. package/esm2022/lib/tango-manage-tickets.module.mjs +0 -109
  143. package/esm2022/public-api.mjs +0 -6
  144. package/esm2022/tango-app-ui-manage-tickets.mjs +0 -5
  145. package/fesm2022/tango-app-ui-manage-tickets.mjs +0 -9848
  146. package/fesm2022/tango-app-ui-manage-tickets.mjs.map +0 -1
  147. package/index.d.ts +0 -5
  148. package/lib/components/add-csm-modal/add-csm-modal.component.d.ts +0 -30
  149. package/lib/components/audit-log/audit-log.component.d.ts +0 -5
  150. package/lib/components/audit-mapping-list/audit-mapping-list.component.d.ts +0 -73
  151. package/lib/components/audit-metrics/audit-metrics.component.d.ts +0 -59
  152. package/lib/components/audit-report-popup/audit-report-popup.component.d.ts +0 -52
  153. package/lib/components/audit-retag/audit-retag.component.d.ts +0 -59
  154. package/lib/components/comment-model/comment-model.component.d.ts +0 -17
  155. package/lib/components/count/count.component.d.ts +0 -23
  156. package/lib/components/custom-select/custom-select.component.d.ts +0 -35
  157. package/lib/components/filter-options/filter-options.component.d.ts +0 -15
  158. package/lib/components/footfall-dic/footfall-dic.component.d.ts +0 -143
  159. package/lib/components/footfall-dicview/footfall-dicview.component.d.ts +0 -132
  160. package/lib/components/footfall-popup/footfall-popup.component.d.ts +0 -8
  161. package/lib/components/group-select/group-select.component.d.ts +0 -33
  162. package/lib/components/re-trigger/re-trigger.component.d.ts +0 -32
  163. package/lib/components/reactive-select/reactive-select.component.d.ts +0 -32
  164. package/lib/components/remove-audit/remove-audit.component.d.ts +0 -16
  165. package/lib/components/start-audit/start-audit.component.d.ts +0 -86
  166. package/lib/components/tango-manage-tickets/tango-manage-tickets.component.d.ts +0 -28
  167. package/lib/components/ticket-filter-panel/ticket-filter-panel.component.d.ts +0 -79
  168. package/lib/components/ticket-footfall-new/ticket-footfall-new.component.d.ts +0 -291
  169. package/lib/components/ticketclosepopup/ticketclosepopup.component.d.ts +0 -15
  170. package/lib/components/tickets/tickets.component.d.ts +0 -88
  171. package/lib/components/viewcategory/viewcategory.component.d.ts +0 -16
  172. package/lib/services/audit.service.d.ts +0 -36
  173. package/lib/services/excel.service.d.ts +0 -10
  174. package/lib/services/ticket.service.d.ts +0 -87
  175. package/lib/services/timer.service.d.ts +0 -22
  176. package/lib/tango-manage-tickets-routing.module.d.ts +0 -7
  177. package/lib/tango-manage-tickets.module.d.ts +0 -38
@@ -0,0 +1,2863 @@
1
+ import {
2
+ Component,
3
+ AfterViewInit,
4
+ OnDestroy,
5
+ OnInit,
6
+ ChangeDetectorRef,
7
+ EventEmitter,
8
+ Output,
9
+ HostListener,
10
+ ElementRef,
11
+ ViewChild,
12
+ OnChanges,
13
+ } from "@angular/core";
14
+ import { FootfallPopupComponent } from "../footfall-popup/footfall-popup.component";
15
+ import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
16
+ import { GlobalStateService } from "tango-app-ui-global";
17
+ import { Subject, takeUntil } from "rxjs";
18
+ import { TicketService } from "../../services/ticket.service";
19
+ import { ExcelService } from "../../services/excel.service";
20
+ import { ToastService } from "tango-app-ui-shared";
21
+ import { FormBuilder, FormGroup } from "@angular/forms";
22
+ import { Router } from "@angular/router";
23
+ import 'dayjs/locale/en';
24
+ import utc from 'dayjs/plugin/utc';
25
+ import timezone from 'dayjs/plugin/timezone';
26
+ dayjs.extend(utc);
27
+ dayjs.extend(timezone)
28
+ import { MatTooltip } from '@angular/material/tooltip';
29
+ import dayjs from 'dayjs';
30
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
31
+ import { TicketclosepopupComponent } from "../ticketclosepopup/ticketclosepopup.component";
32
+ import { AuditService } from "../../services/audit.service";
33
+ dayjs.extend(customParseFormat)
34
+
35
+ @Component({
36
+ selector: "lib-ticket-footfall-new",
37
+ templateUrl: "./ticket-footfall-new.component.html",
38
+ styleUrl: "./ticket-footfall-new.component.scss",
39
+ })
40
+ export class TicketFootfallNewComponent implements OnInit, OnDestroy {
41
+ searchValue: any = "";
42
+ sortedColumn: string = "";
43
+ sortDirection: number = 1;
44
+ private readonly destroy$ = new Subject();
45
+ constructor(
46
+ private modalService: NgbModal,
47
+ public gs: GlobalStateService,
48
+ private service: TicketService,
49
+ private cd: ChangeDetectorRef,
50
+ private excelService: ExcelService,
51
+ private ts: ToastService,
52
+ private fb: FormBuilder,
53
+ private router: Router,
54
+ private auditservice: AuditService,
55
+ ) { }
56
+ headerFilters: any;
57
+ footfallList_req: any;
58
+ usersDetails: any;
59
+ selectedStore: any;
60
+ selectedReviewer: any;
61
+ storeList: any = []
62
+ dayjs = dayjs
63
+ selectedDateRange: any = {};
64
+ arrowshow = true;
65
+ accuracyList: any = []
66
+ selectedIssue: any = ''
67
+ selectedsubIssue: any = ''
68
+ isCustomDate = (m: dayjs.Dayjs) => {
69
+ const isValidDate = m > this.dayjs();
70
+ return isValidDate ? 'invalid-date' : false;
71
+ }
72
+ @ViewChild('tooltip') tooltip: MatTooltip;
73
+ footfallcount: any = 0
74
+ @ViewChild('internalticket') internalticket: any;
75
+ @ViewChild('closeacuuracyissue') closeacuuracyissue: any;
76
+ clientData: any;
77
+ ngOnInit(): void {
78
+ let tickettype = sessionStorage.getItem('ticketType')
79
+ this.tangoType = tickettype
80
+ sessionStorage.clear();
81
+ this.imageUrl = this.service?.footfallCDN;
82
+ let clientdata: any = localStorage.getItem("footfall-config");
83
+ this.clientData = JSON.parse(clientdata)
84
+ let userData: any = localStorage.getItem("user-info");
85
+ this.usersDetails = JSON.parse(userData);
86
+
87
+ this.gs.dataRangeValue?.pipe(takeUntil(this.destroy$))?.subscribe({
88
+ next: (data: any) => {
89
+ if (data) {
90
+ this.headerFilters = data;
91
+ this.footfallList_req = {
92
+ client: this.headerFilters.clients.toString(),
93
+ fromDate: this.headerFilters?.date?.startDate,
94
+ toDate: this.headerFilters?.date?.endDate,
95
+ };
96
+ if (!this.selectedRole) {
97
+ if (this.hasApproverAccess) {
98
+ this.selectedRole = 'approver';
99
+ } else if (this.hasReviewerAccess) {
100
+ this.selectedRole = 'reviewer';
101
+ }
102
+ }
103
+ this.viewTicket('store');
104
+ let payload = {
105
+ clientId: this.headerFilters.clients
106
+ }
107
+ const yesterday = this.dayjs().subtract(1, "days");
108
+ const formattedDate = {
109
+ startDate: yesterday.format("YYYY-MM-DD"),
110
+ endDate: yesterday.format("YYYY-MM-DD"),
111
+ };
112
+ this.selectedDateRange = {
113
+ startDate: yesterday.format("DD-MM-YYYY"),
114
+ endDate: yesterday.format("DD-MM-YYYY"),
115
+ };
116
+ this.service.getstoreList(payload).subscribe({
117
+ next: (e: any) => {
118
+ if (e) {
119
+ this.storeList = e.data.result;
120
+ }
121
+ }
122
+ })
123
+ }
124
+ },
125
+ });
126
+ }
127
+ viewTicket(type: any) {
128
+
129
+ this.offset = 1;
130
+ this.limit = 10;
131
+ this.tangoType = type;
132
+ this.getTicketSummary(type);
133
+ this.select = "ticketList";
134
+ if (this.usersDetails?.userType !== 'tango') {
135
+ if (this.hasApproverAccess && this.selectedRole === 'approver') {
136
+ this.SelectedRole('approver');
137
+ return;
138
+ }
139
+
140
+ if (this.hasReviewerAccess && this.selectedRole === 'reviewer') {
141
+ this.SelectedRole('reviewer');
142
+ return;
143
+ }
144
+ }
145
+ this.getTicketList(type);
146
+
147
+
148
+ }
149
+
150
+ private areAllItemsReviewed(mapping: any): boolean {
151
+ const revised = mapping?.revisedDetail || [];
152
+ if (!revised.length) return false;
153
+
154
+ const itemsToCheck: any[] = [];
155
+
156
+ revised.forEach((detail: any) => {
157
+ // If duplicate parent → check its duplicateImage children
158
+ if (
159
+ detail?.revopsType === 'duplicate' &&
160
+ detail?.isParent &&
161
+ Array.isArray(detail?.duplicateImage) &&
162
+ detail.duplicateImage.length
163
+ ) {
164
+ itemsToCheck.push(...detail.duplicateImage);
165
+ } else {
166
+ // Normal item or non-parent duplicate
167
+ itemsToCheck.push(detail);
168
+ }
169
+ });
170
+
171
+ // All must have review approved/rejected
172
+ return itemsToCheck.length > 0 && itemsToCheck.every(item => this.hasReviewDecision(item));
173
+ }
174
+ private hasReviewDecision(item: any): boolean {
175
+ const actions = item?.actions || [];
176
+ return actions.some(
177
+ (a: any) =>
178
+ a.actionType === 'review' &&
179
+ (a.action === 'approved' || a.action === 'rejected')
180
+ );
181
+ }
182
+
183
+ selectedRole: 'approver' | 'reviewer' | '' = '';
184
+ statusVal: any;
185
+ dueDate: any;
186
+ reviewStatus =false;
187
+ getHeaderStatus(): any {
188
+ // Default: ticket-level status
189
+ let headerStatus = this.ticketData?.status || '--';
190
+ // if(headerStatus === 'Open'){
191
+ // this.isCheckboxEnable =false;
192
+ // } else if(headerStatus === 'In-Progress'){
193
+ // this.isCheckboxEnable =true;
194
+ // }
195
+ if (!this.footfallTicketsData || !this.footfallTicketsData.length) {
196
+ return headerStatus;
197
+ }
198
+
199
+ // 🔹 1) Collect latest mapping by type (only those with a status)
200
+ let tangoMapping: any = null;
201
+ let approverMapping: any = null;
202
+ let reviewerMapping: any = null;
203
+
204
+ for (const ticket of this.footfallTicketsData) {
205
+ const source = ticket?._source;
206
+ const mappingList = source?.mappingInfo || [];
207
+
208
+ for (const m of mappingList) {
209
+ this.dueDate = m.dueDate ? m.dueDate : ''
210
+ if (!m?.status) continue; // need status to show in header
211
+
212
+ if (m.type === 'tangoreview') {
213
+ tangoMapping = m; // last tango wins
214
+ }
215
+
216
+ if (m.type === 'approve') {
217
+ approverMapping = m; // last approve wins (Open / In-Progress / Closed)
218
+ }
219
+
220
+ if (m.type === 'review') {
221
+ reviewerMapping = m; // last review wins
222
+
223
+ }
224
+ }
225
+ }
226
+
227
+ // 🔹 2) Compute permissionType same as your API
228
+ let permissionType: 'approve' | 'review' | null = null;
229
+
230
+ if (this.usersDetails?.userType !== 'tango') {
231
+ if (this.hasApproverAccess && this.selectedRole === 'approver') {
232
+ permissionType = 'approve';
233
+ } else if (this.hasReviewerAccess && this.selectedRole === 'reviewer') {
234
+ permissionType = 'review';
235
+ }
236
+ }
237
+
238
+ let roleMapping: any = null;
239
+
240
+ // 🔹 3) Decide which mapping to show in header
241
+ if (!permissionType) {
242
+ // tango or no special role → prefer tango > approve > review
243
+ roleMapping = tangoMapping || approverMapping || reviewerMapping;
244
+ } else if (permissionType === 'approve') {
245
+ // approver view → try approve first, else fallbacks
246
+ roleMapping = approverMapping || tangoMapping || reviewerMapping;
247
+ } else if (permissionType === 'review') {
248
+
249
+ // reviewer view → try review first, else fallbacks
250
+ roleMapping = reviewerMapping
251
+ }
252
+
253
+ // Save for other places if needed
254
+ this.statusVal = roleMapping;
255
+ if (roleMapping?.status) {
256
+ this.areAllItemsReviewed(roleMapping); // keep your side-effect
257
+ headerStatus = roleMapping.status; // "Open", "In-Progress", "Closed", etc.
258
+ }
259
+ return headerStatus;
260
+ }
261
+
262
+
263
+
264
+ approverClosed: any = false;
265
+ getCurrentRoleMapping(): any {
266
+ if (!this.footfallTicketsData || !this.footfallTicketsData.length) {
267
+ return null;
268
+ }
269
+
270
+ let approverMapping: any = null;
271
+ let reviewerMapping: any = null;
272
+ let tangoMapping: any = null;
273
+ let hasClosedApprove = false; // 👈 track overall condition
274
+
275
+ for (const ticket of this.footfallTicketsData) {
276
+ const source = ticket?._source;
277
+ const mappingList = source?.mappingInfo || [];
278
+
279
+ for (const m of mappingList) {
280
+
281
+ // 👇 overall check for arrowshow (approve + Closed)
282
+ if (
283
+ m?.type === 'approve' &&
284
+ m?.status === 'Closed'
285
+ ) {
286
+ this.approverClosed = hasClosedApprove = true;
287
+ this.arrowshow = !hasClosedApprove;
288
+ // console.log(this.approverClosed)
289
+ }
290
+
291
+ if (m?.type === 'tangoreview' && m?.revisedDetail?.length) {
292
+ tangoMapping = m; // last tango wins
293
+ }
294
+
295
+ if (m?.type === 'approve' && m?.revisedDetail?.length) {
296
+ approverMapping = m;
297
+ // last approve wins
298
+ }
299
+
300
+ if (m?.type === 'review' && m?.revisedDetail?.length) {
301
+ reviewerMapping = m; // last review wins
302
+
303
+ }
304
+ }
305
+ }
306
+ if (tangoMapping) {
307
+ return tangoMapping;
308
+ }
309
+
310
+ // 🔹 2) Then approver / reviewer based on access
311
+ if (this.hasApproverAccess && approverMapping) {
312
+ return approverMapping;
313
+ }
314
+
315
+ if (this.hasReviewerAccess && reviewerMapping) {
316
+ return reviewerMapping;
317
+ }
318
+
319
+ // 🔹 3) Fallback if nothing found
320
+ return null;
321
+ }
322
+
323
+
324
+ disableToday = (date: any): boolean => {
325
+ const today = dayjs().format("YYYY-MM-DD");
326
+ return dayjs(date).format("YYYY-MM-DD") === today;
327
+ };
328
+
329
+
330
+ datechange(event: any) {
331
+ if (event && event.startDate && event.endDate) {
332
+ if (
333
+ this.dayjs(event.startDate).isValid() &&
334
+ this.dayjs(event.endDate).isValid()
335
+ ) {
336
+ this.selectedDateRange.startDate = event.startDate
337
+ this.selectedDateRange.endDate = event.endDate
338
+ var datetime = {
339
+ startDate: this.dayjs(event.startDate, "DD-MM-YYYY").format("YYYY-MM-DD"),
340
+ endDate: this.dayjs(event.endDate, "DD-MM-YYYY").format("YYYY-MM-DD"),
341
+ };
342
+ if (this.selectedStore?.storeId) {
343
+
344
+ this.service
345
+ .getfootfallcount({
346
+ "storeId": this.selectedStore?.storeId,
347
+ "Date": this.dayjs(this.selectedDateRange.startDate, "DD-MM-YYYY").format("YYYY-MM-DD"),
348
+ })
349
+ .pipe(takeUntil(this.destroy$))
350
+ .subscribe({
351
+ next: (res: any) => {
352
+ if (res && res.code === 200) {
353
+ // console.log(res.data)
354
+ this.footfallcount = res?.data[0]?.footfallCount
355
+
356
+
357
+ }
358
+ }
359
+ })
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ getStatusBadgeClass(status: string): string {
366
+ const map: any = {
367
+ 'Open': 'badge-light-warning',
368
+ 'open': 'badge-light-warning',
369
+ 'In-Progress': 'badge-light-primary',
370
+ 'tangoreview': 'badge-light-primary',
371
+ 'Closed': 'badge-secondary',
372
+ 'Pending': 'badge-light-warning',
373
+ 'Reviewer-Closed': 'badge-light-warning',
374
+ 'Rejected': 'badge-light-danger',
375
+ 'Open - Accuracy Issue': 'badge-light-danger',
376
+ 'Raised': 'badge-light-info'
377
+ };
378
+
379
+ return map[status] || 'badge-light-dark'; // fallback
380
+ }
381
+
382
+ getFootfallSummaryData: any;
383
+ getTicketSummary(type: any) {
384
+ this.setPermissionType();
385
+ this.service
386
+ .getTicketSummaryApi(
387
+ this.footfallList_req.client,
388
+ this.footfallList_req.fromDate,
389
+ this.footfallList_req.toDate,
390
+ type,
391
+ this.permissionType,
392
+ )
393
+ .pipe(takeUntil(this.destroy$))
394
+ .subscribe({
395
+ next: (res: any) => {
396
+ if (res && res?.data && res?.data?.result) {
397
+ this.getFootfallSummaryData = res?.data?.result;
398
+ } else {
399
+ this.getFootfallSummaryData = [];
400
+ }
401
+ },
402
+ error: (err: any) => {
403
+ this.getFootfallSummaryData = [];
404
+ },
405
+ complete: () => {
406
+ this.getFootfallSummaryData.length === 0;
407
+ },
408
+ });
409
+ this.cd.detectChanges();
410
+ }
411
+ offset = 1;
412
+ limit = 10;
413
+ isExport: any = false;
414
+ footfallListData: any;
415
+ totalItems: any;
416
+ paginationSizes = [10, 20, 30];
417
+ loading = true;
418
+ noData = false;
419
+ tangoType: any = "store";
420
+ filterPayload: any = null;
421
+ permissionType: string | null = null;
422
+ setPermissionType() {
423
+ if (this.usersDetails?.userType !== 'tango') {
424
+ this.permissionType =
425
+ this.hasApproverAccess && this.selectedRole === 'approver'
426
+ ? 'approve'
427
+ : this.hasReviewerAccess && this.selectedRole === 'reviewer'
428
+ ? 'review'
429
+ : null;
430
+ } else {
431
+ // tango -> can see all flows
432
+ this.permissionType = '';
433
+ }
434
+ }
435
+ private buildFiltersForApi(): any {
436
+ const p = this.filterPayload || {};
437
+
438
+ // Always send all keys
439
+ const filters: any = {
440
+ filterByStatus: [],
441
+ filterByTango: null,
442
+ filterByReviewer: null,
443
+ filterByApprover: null,
444
+ filterByReviewedBy: [],
445
+ fileterByApprovedBy: []
446
+ };
447
+
448
+ // Status
449
+ if (p.filterByStatus?.length) {
450
+ filters.filterByStatus = p.filterByStatus;
451
+ }
452
+
453
+ // Role-based Accuracy Blocks
454
+ if (this.usersDetails?.userType === 'tango') {
455
+ filters.filterByTango = p.filterByTango?.length ? p.filterByTango : null;
456
+ filters.filterByReviewer = p.filterByReviewer?.length ? p.filterByReviewer : null;
457
+ filters.filterByApprover = p.filterByApprover?.length ? p.filterByApprover : null;
458
+
459
+ } else if (this.permissionType === 'review') {
460
+ filters.filterByReviewer = p.filterByReviewer?.length ? p.filterByReviewer : null;
461
+
462
+ } else if (this.permissionType === 'approve') {
463
+ filters.filterByApprover = p.filterByApprover?.length ? p.filterByApprover : null;
464
+ }
465
+
466
+ // ReviewedBy
467
+ if (p.filterByReviewedBy?.length) {
468
+ filters.filterByReviewedBy = p.filterByReviewedBy;
469
+ }
470
+
471
+ // ApprovedBy
472
+ if (p.fileterByApprovedBy?.length) {
473
+ filters.fileterByApprovedBy = p.fileterByApprovedBy; // keep spelling
474
+ }
475
+
476
+ return filters;
477
+ }
478
+
479
+
480
+
481
+ getTicketList(type: any) {
482
+ this.currentPage = 1;
483
+ this.offset = 1;
484
+ this.limit = 10
485
+ this.loading = true;
486
+ this.noData = false
487
+ this.searchValue = this.searchValue?.trim() || "";
488
+ this.isExport = false;
489
+ this.footfallListData =[];
490
+ this.setPermissionType(); // make sure it’s up-to-date
491
+
492
+ const filters = this.buildFiltersForApi();
493
+
494
+ this.service
495
+ .getTicketListApi(
496
+ this.footfallList_req?.client,
497
+ this.footfallList_req?.fromDate,
498
+ this.footfallList_req?.toDate,
499
+ this.searchValue,
500
+ 10,
501
+ 1,
502
+ this.isExport,
503
+ this.sortedColumn,
504
+ this.sortDirection,
505
+ type,
506
+ this.permissionType,
507
+ filters
508
+ ).pipe(takeUntil(this.destroy$))
509
+ .subscribe({
510
+ next: (res: any) => {
511
+ if (res && res.code === 200) {
512
+ this.noData = false;
513
+ this.loading = false;
514
+ this.footfallListData = res?.data?.result;
515
+
516
+ if (this.footfallListData.length > 0) {
517
+ this.generateColumns(this.footfallListData[0]);
518
+ }
519
+ this.totalItems = res?.data?.count;
520
+ if (this.totalItems < 10) {
521
+ this.paginationSizes = [this.totalItems];
522
+ } else {
523
+ this.paginationSizes = [10, 20, 30];
524
+ }
525
+ } else {
526
+ this.totalItems = 0
527
+ this.noData = true;
528
+ this.loading = false;
529
+ this.footfallListData = [];
530
+ }
531
+ this.cd.detectChanges();
532
+ },
533
+ error: (err: any) => {
534
+ this.noData = true;
535
+ this.loading = false;
536
+ },
537
+ complete: () => {
538
+ this.loading = false;
539
+ },
540
+ });
541
+ }
542
+ tableColumns: any[] = [];
543
+ labelOverrides: Record<string, string> = {
544
+ ticketId: 'Ticket ID',
545
+ storeName: 'Store Name',
546
+ storeId: 'Store ID', // or just 'Store'
547
+ storeRevisedAccuracy: 'Store (Accuracy%)',
548
+ reviewerRevisedAccuracy: 'Reviewer (Accuracy%)',
549
+ approverRevisedAccuracy: 'Approver (Accuracy%)',
550
+ tangoRevisedAccuracy: 'Tango (Accuracy%)',
551
+ ticketRaised: 'Ticket Raised On',
552
+ issueDate: 'Issue Date',
553
+ footfall: 'Actual FF',
554
+ };
555
+ hiddenColumns: string[] = [
556
+ 'storeName', // uncomment if you want to REMOVE the extra "Store Name" column
557
+ 'type',
558
+ ];
559
+ sortableColumns: string[] = ['issueDate', 'storeId', 'footfall'];
560
+
561
+ generateColumns(firstRow: any) {
562
+ const keys = Object.keys(firstRow)
563
+ .filter(key => !this.hiddenColumns.includes(key)); // remove unwanted columns
564
+
565
+ this.tableColumns = keys.map(key => ({
566
+ key,
567
+ label: this.labelOverrides[key] ?? this.formatColumnName(key),
568
+ sortable: this.sortableColumns.includes(key), // ✅ only 3 columns sortable
569
+ type: this.detectColumnType ? this.detectColumnType(key) : null
570
+ }));
571
+ }
572
+
573
+ formatColumnName(key: string): string {
574
+ return key
575
+ .replace(/([A-Z])/g, ' $1')
576
+ .replace(/_/g, ' ')
577
+ .replace(/\b\w/g, c => c.toUpperCase());
578
+ }
579
+
580
+ detectColumnType(key: string) {
581
+ if (key === 'storeName' || key === 'ticketId') return 'store';
582
+ if (key.toLowerCase().includes('date') || key === 'ticketRaised') return 'date';
583
+ if (key.toLowerCase().includes('status')) return 'status';
584
+ return 'text';
585
+ }
586
+ currentPage: any = 1;
587
+ pageSize: any = 10;
588
+ onPageChange(pageOffset: number) {
589
+ this.currentPage = Number(pageOffset);
590
+ this.offset = Number(pageOffset);
591
+ // this.limit = 10;
592
+ this.loading = true;
593
+ this.noData = false
594
+ this.searchValue = this.searchValue?.trim() || "";
595
+ this.isExport = false;
596
+ this.setPermissionType(); // make sure it’s up-to-date
597
+
598
+ const filters = this.buildFiltersForApi();
599
+
600
+ this.service
601
+ .getTicketListApi(
602
+ this.footfallList_req?.client,
603
+ this.footfallList_req?.fromDate,
604
+ this.footfallList_req?.toDate,
605
+ this.searchValue,
606
+ this.limit,
607
+ this.offset,
608
+ this.isExport,
609
+ this.sortedColumn,
610
+ this.sortDirection,
611
+ this.tangoType,
612
+ this.permissionType,
613
+ filters
614
+ ).pipe(takeUntil(this.destroy$))
615
+ .subscribe({
616
+ next: (res: any) => {
617
+ if (res && res.code === 200) {
618
+ this.noData = false;
619
+ this.loading = false;
620
+ this.footfallListData = res?.data?.result;
621
+ if (this.footfallListData.length > 0) {
622
+ this.generateColumns(this.footfallListData[0]);
623
+ }
624
+ this.totalItems = res?.data?.count;
625
+ if (this.totalItems < 10) {
626
+ this.paginationSizes = [this.totalItems];
627
+ } else {
628
+ this.paginationSizes = [10, 20, 30];
629
+ }
630
+ } else {
631
+ this.totalItems = 0
632
+ this.noData = true;
633
+ this.loading = false;
634
+ this.footfallListData = [];
635
+ }
636
+ this.cd.detectChanges();
637
+ },
638
+ error: (err: any) => {
639
+ this.noData = true;
640
+ this.loading = false;
641
+ },
642
+ complete: () => {
643
+ this.loading = false;
644
+ },
645
+ });
646
+ }
647
+
648
+ onPageSizeChange(pageSize: number) {
649
+ this.pageSize = Number(pageSize);
650
+ this.limit = Number(pageSize);
651
+ this.currentPage = 1;
652
+ this.offset = 1;
653
+ this.loading = true;
654
+ this.noData = false
655
+ this.searchValue = this.searchValue?.trim() || "";
656
+ this.isExport = false;
657
+ this.setPermissionType(); // make sure it’s up-to-date
658
+
659
+ const filters = this.buildFiltersForApi();
660
+
661
+ this.service
662
+ .getTicketListApi(
663
+ this.footfallList_req?.client,
664
+ this.footfallList_req?.fromDate,
665
+ this.footfallList_req?.toDate,
666
+ this.searchValue,
667
+ this.limit,
668
+ this.offset,
669
+ this.isExport,
670
+ this.sortedColumn,
671
+ this.sortDirection,
672
+ this.tangoType,
673
+ this.permissionType,
674
+ filters
675
+ ).pipe(takeUntil(this.destroy$))
676
+ .subscribe({
677
+ next: (res: any) => {
678
+ if (res && res.code === 200) {
679
+ this.noData = false;
680
+ this.loading = false;
681
+ this.footfallListData = res?.data?.result;
682
+ if (this.footfallListData.length > 0) {
683
+ this.generateColumns(this.footfallListData[0]);
684
+ }
685
+ this.totalItems = res?.data?.count;
686
+ if (this.totalItems < 10) {
687
+ this.paginationSizes = [this.totalItems];
688
+ } else {
689
+ this.paginationSizes = [10, 20, 30];
690
+ }
691
+ } else {
692
+ this.totalItems = 0
693
+ this.noData = true;
694
+ this.loading = false;
695
+ this.footfallListData = [];
696
+ }
697
+ this.cd.detectChanges();
698
+ },
699
+ error: (err: any) => {
700
+ this.noData = true;
701
+ this.loading = false;
702
+ },
703
+ complete: () => {
704
+ this.loading = false;
705
+ },
706
+ });
707
+ }
708
+
709
+ setpageSize() {
710
+ if (this.totalItems < 10) {
711
+ return this.totalItems;
712
+ } else {
713
+ return this.pageSize;
714
+ }
715
+ }
716
+ searchData() {
717
+ this.currentPage = 1;
718
+ this.offset = 1;
719
+ this.limit = 10;
720
+ this.getTicketList(this.tangoType);
721
+ }
722
+ storeChange(event: any) {
723
+ this.selectedStore = { storeId: event.storeId, storeName: event.storeName }
724
+ this.service
725
+ .getfootfallcount({
726
+ "storeId": this.selectedStore?.storeId,
727
+ "Date": this.dayjs(this.selectedDateRange.startDate, "DD-MM-YYYY").format("YYYY-MM-DD"),
728
+ })
729
+ .pipe(takeUntil(this.destroy$))
730
+ .subscribe({
731
+ next: (res: any) => {
732
+ if (res && res.code === 200) {
733
+ // console.log(res.data)
734
+ this.footfallcount = res?.data[0]?.footfallCount
735
+
736
+
737
+ }
738
+ }
739
+ })
740
+
741
+ }
742
+ onStartDateChange(event: any) {
743
+ if (this.dayjs(event.startDate).isValid()) {
744
+ this.isCustomDate = (m: dayjs.Dayjs) => {
745
+ const isValidDate = m > this.dayjs() || m > this.dayjs(event.startDate.add(90, 'days'));
746
+ return isValidDate ? 'invalid-date' : false;
747
+ }
748
+ }
749
+ }
750
+
751
+
752
+
753
+ createInternalticket() {
754
+ this.selectedStore = {}
755
+ this.footfallcount = 0
756
+ const modalRef = this.modalService.open(this.internalticket)
757
+ modalRef.result.then((result) => {
758
+ if (result === 'isConfirmed') {
759
+ let obj = {
760
+ "storeId": this.selectedStore?.storeId,
761
+ "dateString": dayjs(this.selectedDateRange.startDate, "DD-MM-YYYY").format("YYYY-MM-DD"),
762
+ "storeName": this.selectedStore?.storeName,
763
+ "ticketName": "footfall-directory",
764
+ "footfallCount": this.footfallcount,
765
+ "clientId": this.headerFilters?.client,
766
+ "status": "Raised",
767
+ "comments": ""
768
+ }
769
+ this.service
770
+ .CreateinternalTicketApi(obj)
771
+ .pipe(takeUntil(this.destroy$))
772
+ .subscribe({
773
+ next: (res: any) => {
774
+ if (res && res?.code === 200) {
775
+ this.ts.getSuccessToast("Ticket created successfully");
776
+ this.modalService.dismissAll();
777
+ const data = {
778
+ totalfiles: this.footfallcount,
779
+ filedetails: {
780
+ clientId: this.headerFilters?.client,
781
+ storeId: this.selectedStore?.storeId,
782
+ Date: dayjs(this.selectedDateRange.startDate, "DD-MM-YYYY").format("YYYY-MM-DD"),
783
+ auditId: "",
784
+ userId: "",
785
+ nextToken: "",
786
+ },
787
+ auditId: "",
788
+ storedetails: {
789
+ storeName: this.selectedStore?.storeName
790
+ }
791
+ };
792
+ sessionStorage.setItem("totalfiles", JSON.stringify(data));
793
+ sessionStorage.setItem("ticketType", "internal");
794
+ this.select = 'ticketMethod';
795
+ this.router.navigateByUrl('/manage/tickets/audit')
796
+
797
+ } else {
798
+ this.ts.getErrorToast("Error closing ticket");
799
+ }
800
+ this.cd.detectChanges();
801
+ },
802
+ error: (err: any) => {
803
+ const errorMsg =
804
+ err?.error?.message ||
805
+ err?.error?.error ||
806
+ err?.error ||
807
+ err?.message ||
808
+ "Error closing ticket";
809
+ this.ts.getErrorToast(errorMsg);
810
+ this.isCheckboxEnable = true;
811
+ this.closeBtn = true;
812
+ this.closeDisabled = false;
813
+ }
814
+ });
815
+
816
+
817
+
818
+ }
819
+ })
820
+
821
+
822
+
823
+
824
+ }
825
+ exportXLSX() {
826
+ this.searchValue = this.searchValue?.trim() || "";
827
+ this.offset = 1;
828
+ this.limit = 10000;
829
+ this.isExport = true;
830
+ this.service
831
+ .getTicketListExportApi(
832
+ this.footfallList_req.client,
833
+ this.footfallList_req.fromDate,
834
+ this.footfallList_req.toDate,
835
+ this.searchValue,
836
+ this.limit,
837
+ this.offset,
838
+ this.isExport,
839
+ this.sortedColumn,
840
+ this.sortDirection,
841
+ this.tangoType
842
+ )
843
+ .pipe(takeUntil(this.destroy$))
844
+ .subscribe({
845
+ next: (res: any) => {
846
+ this.excelService.saveAsExcelFile(res, "footfall directory ticket ");
847
+ },
848
+ error: (err: any) => {
849
+ this.ts.getErrorToast(
850
+ "Error exporting data:" + err.error ? err.error : err.message
851
+ );
852
+ },
853
+ });
854
+ }
855
+
856
+ onSort(column: string) {
857
+ if (this.sortedColumn === column) {
858
+ this.sortDirection = this.sortDirection === 1 ? -1 : 1;
859
+ } else {
860
+ this.sortedColumn = column;
861
+ this.sortDirection = 1;
862
+ }
863
+ this.getTicketList(this.tangoType);
864
+ }
865
+ select = "ticketList";
866
+ ticketData: any;
867
+
868
+ startAudit() {
869
+ let input = {
870
+ storeId: this.ticketData?.storeId,
871
+ dateString: this.ticketData?.issueDate,
872
+ }
873
+ this.service
874
+ .checkTicketExists(input).pipe(takeUntil(this.destroy$))
875
+ .subscribe({
876
+ next: (res: any) => {
877
+ if (res && res?.code === 200) {
878
+ // console.log(res.data)
879
+ let ticketList = res?.data
880
+ let findTangoticket = ticketList.filter((data: any) => data?._source?.type === 'internal' && data?._source?.status === "Closed")
881
+ let findstoreticket = ticketList.filter((data: any) => data?._source?.type === 'store' && data?._source?.status != "Closed")
882
+
883
+ if (findTangoticket.length > 0) {
884
+ const modalRef = this.modalService.open(TicketclosepopupComponent, {
885
+ centered: true,
886
+ size: "md",
887
+ scrollable: true,
888
+ backdrop: "static",
889
+ });
890
+ modalRef.componentInstance.tangoTicket = findTangoticket[0]._source;
891
+ modalRef.componentInstance.storeTicket = findstoreticket[0]._source;
892
+ modalRef.result.then((result) => {
893
+
894
+ if (result.type === 'merge') {
895
+
896
+ let obj = findTangoticket[0]?._source
897
+ if (obj && obj?.mappingInfo.length > 0) {
898
+ let mapData = obj.mappingInfo.filter((data: any) => data.type === "tangoreview")[0]
899
+
900
+ let payload = {
901
+ storeId: this.ticketData?.storeId,
902
+ "dateString": this.ticketData?.issueDate,
903
+ ticketType: 'store',
904
+ mappingInfo: mapData
905
+ }
906
+ this.auditservice
907
+ .saveaudit(payload).pipe(takeUntil(this.destroy$))
908
+ .subscribe({
909
+ next: (res: any) => {
910
+ if (res && res?.code === 200) {
911
+ this.router.navigate(['/manage/tickets'], { queryParams: { type: 'footfall' } })
912
+ }
913
+ }
914
+ })
915
+ }
916
+ } else if (result.type == '') {
917
+ const data = {
918
+ totalfiles: this.ticketData?.footfall,
919
+ filedetails: {
920
+ clientId: this.headerFilters?.client,
921
+ storeId: this.ticketData?.storeId,
922
+ Date: this.ticketData?.issueDate,
923
+ auditId: "",
924
+ userId: "",
925
+ nextToken: "",
926
+ },
927
+ auditId: "",
928
+ storedetails: {
929
+ storeName: this.ticketData?.storeName
930
+ }
931
+ };
932
+ sessionStorage.setItem("totalfiles", JSON.stringify(data));
933
+ sessionStorage.setItem("ticketType", this.tangoType);
934
+
935
+ this.select = 'ticketMethod';
936
+ this.router.navigateByUrl('/manage/tickets/audit')
937
+ }
938
+ });
939
+ } else {
940
+ const data = {
941
+ totalfiles: this.ticketData?.footfall,
942
+ filedetails: {
943
+ clientId: this.headerFilters?.client,
944
+ storeId: this.ticketData?.storeId,
945
+ Date: this.ticketData?.issueDate,
946
+ auditId: "",
947
+ userId: "",
948
+ nextToken: "",
949
+ },
950
+ auditId: "",
951
+ storedetails: {
952
+ storeName: this.ticketData?.storeName
953
+ }
954
+ };
955
+ sessionStorage.setItem("totalfiles", JSON.stringify(data));
956
+ sessionStorage.setItem("ticketType", this.tangoType);
957
+
958
+ this.select = 'ticketMethod';
959
+ this.router.navigateByUrl('/manage/tickets/audit')
960
+ }
961
+
962
+
963
+
964
+
965
+ }
966
+ }
967
+ })
968
+
969
+
970
+ return
971
+
972
+
973
+ }
974
+
975
+ backToNavigation() {
976
+ this.select = "ticketList";
977
+ this.footfallTicketsData = [];
978
+ this.getTicketSummary(this.tangoType);
979
+ this.getTicketList(this.tangoType);
980
+ this.closeBtn = false;
981
+ this.isCheckboxEnable = false;
982
+ this.arrowshow = !this.arrowshow;
983
+ this.commentsAccordionOpen = false;
984
+ this.selectedCommentCategory = null;
985
+ this.resetSelections();
986
+ this.allSelected = false;
987
+ }
988
+ isCollapsed = false;
989
+ toggleSidebar() {
990
+ this.isCollapsed = !this.isCollapsed;
991
+ }
992
+
993
+ toggleFilter() {
994
+ this.isFilterOpen = !this.isFilterOpen;
995
+ }
996
+ isFilterOpen = false; // controls show/hide panel
997
+ filterForm: FormGroup;
998
+ @Output() filterChange = new EventEmitter<any>();
999
+ newForm() {
1000
+ this.filterForm = this.fb.group({
1001
+ status: [""],
1002
+ reviewerCondition: [""],
1003
+ reviewerValue: [""],
1004
+ approverCondition: [""],
1005
+ approverValue: [""],
1006
+ tangoCondition: [""],
1007
+ tangoValue: [""],
1008
+ });
1009
+ }
1010
+
1011
+ applyFilter() {
1012
+ this.filterChange.emit(this.filterForm.value);
1013
+ this.isFilterOpen = false; // close after apply
1014
+ }
1015
+
1016
+ resetFilter() {
1017
+ this.filterForm.reset();
1018
+ this.filterChange.emit(this.filterForm.value);
1019
+ }
1020
+
1021
+ close() {
1022
+ this.isFilterOpen = false;
1023
+ }
1024
+ StoresSearchValue: any = "";
1025
+ sortedColumn1: string = "";
1026
+ sortDirection1: number = 1;
1027
+
1028
+ openTicketsList: any[] = [];
1029
+ selectedStores: any[] = []; // holds selected rows
1030
+ allSelected = false; // top "Select All" checkbox
1031
+
1032
+ get storeCount(): number {
1033
+ return this.openTicketsList?.length || 0;
1034
+ }
1035
+
1036
+ // check if a row is selected (used in template)
1037
+ isSelected(store: any): boolean {
1038
+ return this.selectedStores.includes(store);
1039
+ }
1040
+
1041
+
1042
+
1043
+ toggleStoreSelection(store: any): void {
1044
+
1045
+ if (this.isSelected(store)) {
1046
+
1047
+ this.selectedStores = this.selectedStores.filter((s) => s !== store);
1048
+ } else {
1049
+
1050
+ this.selectedStores = [...this.selectedStores, store];
1051
+ }
1052
+ this.closeMultiple = (this.selectedStores.length > 1) ? false : true;
1053
+
1054
+
1055
+ this.allSelected =
1056
+ this.selectedStores.length === this.openTicketsList.length &&
1057
+ this.openTicketsList.length > 0;
1058
+ }
1059
+
1060
+ newallSelected = false;
1061
+ toggleSelectAll(): void {
1062
+ if (this.newallSelected) {
1063
+ this.selectedStores = [];
1064
+ this.newallSelected = false;
1065
+ } else {
1066
+ this.selectedStores = [...this.openTicketsList];
1067
+ this.newallSelected = true;
1068
+ }
1069
+ }
1070
+ ticketViewChanges(store: any) {
1071
+ this.selectedStores = [store];
1072
+ this.dataStoreView(store);
1073
+ this.newallSelected =
1074
+ this.selectedStores.length === this.openTicketsList.length &&
1075
+ this.openTicketsList.length > 0;
1076
+ }
1077
+
1078
+ originalImage: any = {
1079
+ id: 4,
1080
+ entryTime: "10:00 AM",
1081
+ };
1082
+ ticket: any = [];
1083
+ imageUrl: any;
1084
+ getFormattedEntryTime(entryTime: any) {
1085
+ if (!entryTime) return "-";
1086
+ const [hours, minutes, seconds] = entryTime.split(":").map(Number);
1087
+ const date = new Date();
1088
+ date.setHours(hours, minutes, seconds);
1089
+ return date.toLocaleTimeString("en-US", {
1090
+ hour: "2-digit",
1091
+ minute: "2-digit",
1092
+ // second: '2-digit',
1093
+ hour12: true,
1094
+ });
1095
+ }
1096
+ duplicates: any;
1097
+
1098
+ // key = parent original.tempId, value = array of selected duplicate tempIds
1099
+ selectedDuplicatesByParent: { [parentId: number]: number[] } = {};
1100
+
1101
+ // is one duplicate selected?
1102
+ isDuplicateSelected(parentId: number, duplicateId: number): boolean {
1103
+ return (
1104
+ this.selectedDuplicatesByParent[parentId]?.includes(duplicateId) || false
1105
+ );
1106
+ }
1107
+
1108
+ // select / unselect single duplicate
1109
+ onDuplicateCheckboxChange(
1110
+ parentId: number,
1111
+ duplicateId: number,
1112
+ event: Event
1113
+ ): void {
1114
+ const checked = (event.target as HTMLInputElement).checked;
1115
+
1116
+ if (!this.selectedDuplicatesByParent[parentId]) {
1117
+ this.selectedDuplicatesByParent[parentId] = [];
1118
+ }
1119
+
1120
+ const list = this.selectedDuplicatesByParent[parentId];
1121
+
1122
+ if (checked) {
1123
+ if (!list.includes(duplicateId)) {
1124
+ list.push(duplicateId);
1125
+ }
1126
+ } else {
1127
+ this.selectedDuplicatesByParent[parentId] = list.filter(
1128
+ (id) => id !== duplicateId
1129
+ );
1130
+ }
1131
+ this.updateOverallSelectedIds();
1132
+ }
1133
+
1134
+ // are *all* duplicates under this parent selected?
1135
+ areAllDuplicatesSelected(original: any): boolean {
1136
+ const parentId = original.tempId;
1137
+ const duplicates = original?.duplicateImage || [];
1138
+ const selected = this.selectedDuplicatesByParent[parentId] || [];
1139
+
1140
+ return duplicates.length > 0 && selected.length === duplicates.length;
1141
+ }
1142
+
1143
+ // handle "Select All" toggle for this original
1144
+ onToggleSelectAllDuplicates(original: any, event: Event): void {
1145
+ const checked = (event.target as HTMLInputElement).checked;
1146
+ const parentId = original.tempId;
1147
+
1148
+ const duplicates = (original?.duplicateImage || []).filter(
1149
+ (d: any) => !this.isFinalStatus(d) // ⛔ skip approved/rejected
1150
+ );
1151
+
1152
+ if (checked) {
1153
+ this.selectedDuplicatesByParent[parentId] = duplicates.map(
1154
+ (d: any) => d.tempId
1155
+ );
1156
+ } else {
1157
+ this.selectedDuplicatesByParent[parentId] = [];
1158
+ }
1159
+
1160
+ this.updateOverallSelectedIds();
1161
+ }
1162
+
1163
+
1164
+ // comments: any;
1165
+
1166
+ commentModal(obj: any) {
1167
+ const modalRef = this.modalService.open(FootfallPopupComponent, {
1168
+ centered: true,
1169
+ size: "md",
1170
+ scrollable: true,
1171
+ backdrop: "static",
1172
+ });
1173
+ modalRef.componentInstance.ticketId = obj.ticketId;
1174
+ modalRef.result.then((result) => { });
1175
+ }
1176
+
1177
+ commentsAccordionOpen = false;
1178
+ selectedCommentCategory: string | null = null;
1179
+ getCategoryCommentCountForSource(
1180
+ source: any,
1181
+ category: string,
1182
+
1183
+ ): number {
1184
+ if (!source?.commentsDetails || !category) return 0;
1185
+
1186
+ let count = 0;
1187
+
1188
+ for (const block of source.commentsDetails) {
1189
+ // 🔹 DUPLICATE: no block.comments, use block itself
1190
+ // if (category === 'duplicate') {
1191
+ // if (
1192
+ // block.category === 'duplicate' &&
1193
+ // (parentId == null || String(block.parent) === String(parentId))
1194
+ // ) {
1195
+ // count += 1; // each block = one comment thread
1196
+ // }
1197
+ // continue;
1198
+ // }
1199
+
1200
+ // 🔹 NON-DUPLICATE: old shapes
1201
+ const comments = block?.comments || [];
1202
+ if (!comments.length) continue;
1203
+
1204
+ if (block.category) {
1205
+ // New shape: category at block level
1206
+ if (block.category === category) {
1207
+ count += comments.length;
1208
+ }
1209
+ } else {
1210
+ // Old shape: category on each comment
1211
+ count += comments.filter((c: any) => c.category === category).length;
1212
+ }
1213
+ }
1214
+
1215
+ return count;
1216
+ }
1217
+
1218
+
1219
+
1220
+
1221
+
1222
+ toggleCommentsAccordion(type: string) {
1223
+ // console.log(id)
1224
+ // If you click the same type again → close accordion
1225
+ if (this.commentsAccordionOpen && this.selectedCommentCategory === type) {
1226
+ this.commentsAccordionOpen = false;
1227
+ this.selectedCommentCategory = null;
1228
+ } else {
1229
+ // Clicked a new type → open accordion for that type
1230
+ this.commentsAccordionOpen = true;
1231
+ this.selectedCommentCategory = type;
1232
+ }
1233
+ // console.log(this.selectedCommentCategory)
1234
+ }
1235
+
1236
+ ngOnDestroy(): void {
1237
+ this.destroy$.next(true);
1238
+ this.destroy$.complete();
1239
+ }
1240
+
1241
+ comments: any = [];
1242
+
1243
+ showRevisedDetails = false;
1244
+ footfallNoData: boolean = false;
1245
+ footfallLoading: boolean = false;
1246
+ footfalloffset = 1;
1247
+ footfalllimit = 1;
1248
+ totalItemsFootfall: any = 0;
1249
+ dataIndexId: string = "";
1250
+ dateString: string = "";
1251
+ storeIdValue: any = [];
1252
+ lastSelectedTicket: any;
1253
+ selecteValues: any = "";
1254
+ hasInitialStoreSynced: boolean = false;
1255
+
1256
+ pageSizeFootfall: number = 10;
1257
+ paginationSizesFootfall: number[] = [10, 20, 30];
1258
+ addStoreIfNotExists(store: any) {
1259
+ if (this.hasInitialStoreSynced) return;
1260
+
1261
+ const storeId = store?.storeId;
1262
+ if (storeId && !this.selectedStores.includes(storeId)) {
1263
+ this.selectedStores.push(storeId);
1264
+ }
1265
+
1266
+ this.hasInitialStoreSynced = true;
1267
+ // this.allSelected =
1268
+ // this.selectedStores.length === this.openTicketsList.length;
1269
+ }
1270
+ revopsTypes: string[] = [];
1271
+ countData: any;
1272
+ filterList: any;
1273
+ buildRevopsTypes(tickets: any[]) {
1274
+
1275
+ const types = new Set<string>();
1276
+
1277
+ (tickets || []).forEach((ticket: any) => {
1278
+ const mappingInfo = ticket?._source?.mappingInfo || [];
1279
+ mappingInfo.forEach((mapping: any) => {
1280
+ if (mapping?.status === 'In-Progress' && ['review', 'approve'].includes(mapping.type)) {
1281
+ this.ticketStatus = mapping?.createdByEmail
1282
+ }
1283
+ if (mapping.status === 'Closed' && ['review'].includes(mapping.type)) {
1284
+ this.isCheckboxEnable = false;
1285
+ return
1286
+ }
1287
+ // 🔹 ONE function handles review + approve
1288
+ if (mapping.status === 'Closed' && ['approve', 'tangoreview'].includes(mapping.type)) {
1289
+ this.openArrow();
1290
+ this.approverClosed = true;
1291
+
1292
+ }
1293
+ if ((mapping?.status === 'Closed - Accuracy Issue' || mapping?.status === 'Open - Accuracy Issue') && ['tangoreview'].includes(mapping.type)) {
1294
+ this.openArrow();
1295
+ this.approverClosed = true;
1296
+ }
1297
+
1298
+ this.updateCloseStateFromMapping(mapping);
1299
+ // 🔹 Existing revopsTypes logic
1300
+ (mapping?.revisedDetail || []).forEach((item: any) => {
1301
+ // inside your component (e.g., ngOnChanges or wherever you process mapping)
1302
+
1303
+ if (
1304
+ (mapping?.status === 'Closed' ||
1305
+ mapping?.status === 'Closed - Accuracy Issue' ||
1306
+ mapping?.status === 'Open - Accuracy Issue') &&
1307
+ mapping?.type === 'tangoreview'
1308
+ ) {
1309
+ this.filterList = mapping?.revisedDetail || [];
1310
+ // initialize counts
1311
+
1312
+
1313
+ let Data = this.filterList.filter((data:any)=>data.isParent===false)
1314
+ this.countData = Object.entries(
1315
+ Data.reduce((acc:any, it:any) => {
1316
+ const k = it.revopsType || 'unknown';
1317
+ acc[k] = (acc[k] || 0) + 1;
1318
+ return acc;
1319
+ }, {})
1320
+ ).map(([type, count]) => ({ type, count }));
1321
+
1322
+
1323
+ this.applyFilter1(this.filterList);
1324
+
1325
+ }
1326
+
1327
+ if (item?.revopsType) {
1328
+ types.add(item.revopsType);
1329
+ } else if (item?.type && item?.name) {
1330
+ types.add(item.type); // e.g. 'vendor', 'technician', 'houseKeeping'
1331
+ }
1332
+ });
1333
+ });
1334
+ });
1335
+
1336
+ this.revopsTypes = Array.from(types);
1337
+ this.cd.detectChanges();
1338
+ }
1339
+ ticketStatus: any
1340
+ // Handles both: review & approve in ONE place
1341
+ private updateCloseStateFromMapping(mapping: any): void {
1342
+ if (!mapping || mapping.status !== 'In-Progress') {
1343
+ return;
1344
+ }
1345
+ // Decide which stage this mapping belongs to
1346
+ const stage: 'review' | 'approve' | null =
1347
+ mapping.type === 'review'
1348
+ ? 'review'
1349
+ : mapping.type === 'approve'
1350
+ ? 'approve'
1351
+ : null;
1352
+ if (!stage) return; // ignore other types
1353
+
1354
+ const { total, done, fullyDone } = this.getActionCompletion(mapping, stage);
1355
+
1356
+ // 🔹 Case 1: nothing to review → hide/disable button
1357
+ if (done === 0) {
1358
+ this.isCheckboxEnable = true;
1359
+ this.closeBtn = true;
1360
+ this.closeDisabled = true;
1361
+ } else {
1362
+
1363
+ this.isCheckboxEnable = true;
1364
+ this.closeBtn = true;
1365
+ this.closeDisabled = !fullyDone;
1366
+ }
1367
+ this.cd.detectChanges();
1368
+
1369
+ const hasCorrectRole =
1370
+ (this.hasReviewerAccess && this.selectedRole === "reviewer") ||
1371
+ (this.hasApproverAccess && this.selectedRole === "approver");
1372
+
1373
+ const isOwner =
1374
+ this.usersDetails?.email === this.ticketStatus ||
1375
+ this.usersDetails?.email === this.ticketStatus;
1376
+ if (hasCorrectRole === isOwner) {
1377
+ this.isCheckboxEnable = true;
1378
+ } else {
1379
+ this.isCheckboxEnable = false;
1380
+ }
1381
+ }
1382
+
1383
+ private getActionCompletion(mapping: any, actionType: 'review' | 'approve') {
1384
+ const details = mapping?.revisedDetail || [];
1385
+ const allItems: any[] = [];
1386
+
1387
+ for (const d of details) {
1388
+ if (!d.isParent && d.revopsType !== 'duplicate') {
1389
+ allItems.push(d);
1390
+ }
1391
+ if (d.isParent && Array.isArray(d.duplicateImage)) {
1392
+ allItems.push(...d.duplicateImage);
1393
+ }
1394
+ }
1395
+
1396
+ const isDone = (item: any) => {
1397
+ const actions = item.actions || [];
1398
+ const act = actions.find((a: any) => a.actionType === actionType);
1399
+ return act && (act.action === 'approved' || act.action === 'rejected');
1400
+ };
1401
+
1402
+ const done = allItems.filter(isDone).length;
1403
+ const total = allItems.length;
1404
+ if (done > 1) {
1405
+
1406
+ }
1407
+ return { total, done, fullyDone: total > 0 && done === total };
1408
+
1409
+ }
1410
+
1411
+
1412
+ hasRevopsType(type: any) {
1413
+ return this.revopsTypes.includes(type);
1414
+ }
1415
+
1416
+ private isFinalStatus(item: any): boolean {
1417
+ if (this.permissionType === 'review' && this.selectedRole === 'reviewer') {
1418
+ return this.isApproved(item) || this.isRejected(item);
1419
+ }
1420
+
1421
+ if (this.permissionType === 'approve' && this.selectedRole === 'approver') {
1422
+ return this.isApproved1(item) || this.isRejected1(item);
1423
+ }
1424
+
1425
+ return false;
1426
+ }
1427
+
1428
+
1429
+
1430
+ overallSelectedIds: any;
1431
+ overallSelect(event: any) {
1432
+ const checked = (event.target as HTMLInputElement).checked;
1433
+
1434
+ this.allSelected = checked;
1435
+
1436
+ const parents = this.getAllParentItems() || [];
1437
+
1438
+ if (checked) {
1439
+ // non-duplicate types
1440
+ (this.revopsTypes || []).forEach((type: string) => {
1441
+ if (type === 'duplicate') return;
1442
+
1443
+ const list = (this.getListByType(type) || []).filter(
1444
+ (i: any) => !this.isFinalStatus(i)
1445
+ );
1446
+
1447
+ this.selectedByType[type] = list.map((i: any) => i.tempId);
1448
+ this.selectAllByType[type] = list.length > 0;
1449
+ });
1450
+
1451
+ // duplicate type
1452
+ this.selectedDuplicatesByParent = {};
1453
+
1454
+ parents
1455
+ .filter((p: any) => p.revopsType === 'duplicate')
1456
+ .forEach((p: any) => {
1457
+ const parentId = p.tempId;
1458
+
1459
+ const duplicates = (p.duplicateImage || []).filter(
1460
+ (d: any) => !this.isFinalStatus(d)
1461
+ );
1462
+
1463
+ this.selectedDuplicatesByParent[parentId] = duplicates.map(
1464
+ (d: any) => d.tempId
1465
+ );
1466
+ });
1467
+ } else {
1468
+ // clear everything
1469
+ (this.revopsTypes || []).forEach((type: string) => {
1470
+ if (type === 'duplicate') return;
1471
+ this.selectedByType[type] = [];
1472
+ this.selectAllByType[type] = false;
1473
+ });
1474
+
1475
+ this.selectedDuplicatesByParent = {};
1476
+ this.selectAllByType.duplicate = false;
1477
+ this.overallSelectedIds = [];
1478
+ }
1479
+
1480
+ // 🔥 always recalc after change
1481
+ this.updateOverallSelectedIds();
1482
+ }
1483
+
1484
+
1485
+
1486
+ getAllParentItems() {
1487
+ if (!this.footfallTicketsData) {
1488
+ return [];
1489
+ }
1490
+
1491
+ const allMappings: any[] = [];
1492
+ const allRevised: any[] = [];
1493
+
1494
+ // first level: collect all mappingInfo arrays
1495
+ this.footfallTicketsData.forEach((ticket: any) => {
1496
+ const mappings = ticket?._source?.mappingInfo || [];
1497
+ allMappings.push.apply(allMappings, mappings); // concat
1498
+ });
1499
+
1500
+ // second level: collect all revisedDetail arrays
1501
+ allMappings.forEach((mapping: any) => {
1502
+ const details = mapping?.revisedDetail || [];
1503
+ allRevised.push.apply(allRevised, details); // concat
1504
+ });
1505
+
1506
+ return allRevised;
1507
+ }
1508
+
1509
+ isCheckboxEnable = false;
1510
+ closeBtn = false;
1511
+ closeDisabled = false;
1512
+ startReview() {
1513
+ let obj = {
1514
+ "storeId": this.ticketData?.storeId,
1515
+ "dateString": this.ticketData?.issueDate,
1516
+ "mode": "web"
1517
+ }
1518
+ // return
1519
+ this.service.getUpdateTicketStatusApi(obj).pipe(takeUntil(this.destroy$)).subscribe({
1520
+ next: (res: any) => {
1521
+ if (res && res.code === 200) {
1522
+ this.isCheckboxEnable = true; // call this on your "Review" button click
1523
+ this.closeBtn = true;
1524
+ this.closeDisabled = true;
1525
+ this.getTicketList(this.tangoType)
1526
+
1527
+ this.dataStoreView(this.ticketData);
1528
+ }
1529
+ },
1530
+ error: (err: any) => {
1531
+ },
1532
+ complete: () => {
1533
+ this.cd.detectChanges();
1534
+ }
1535
+ })
1536
+ }
1537
+ @ViewChild("closePopup") closePopup: ElementRef;
1538
+ stopReview() {
1539
+ const modalRef = this.modalService.open(this.closePopup, {
1540
+ centered: true,
1541
+ size: "md",
1542
+ backdrop: "static",
1543
+ keyboard: false,
1544
+ });
1545
+ this.isCheckboxEnable = false;
1546
+ this.closeBtn = true;
1547
+ }
1548
+
1549
+ ticketView(data?: any) {
1550
+ this.ticketData = data;
1551
+ this.allSelected = false;
1552
+ if (!data) {
1553
+ this.getOpenTicketList(data);
1554
+ this.select = 'ticketMethod';
1555
+ return;
1556
+ }
1557
+
1558
+ const status = (data.status || '').trim();
1559
+ if (this.usersDetails.userType === 'tango') {
1560
+ this.dataStoreView(data);
1561
+ } else {
1562
+ if (status === 'Open') {
1563
+ // when ticket status is Open → use open-ticket API
1564
+ this.getOpenTicketList(data);
1565
+ } else {
1566
+ // otherwise → use store view API
1567
+ this.dataStoreView(data);
1568
+ }
1569
+ }
1570
+ if (data.status === 'Open - Accuracy Issue') {
1571
+ this.accuracyReasons()
1572
+ }
1573
+ this.select = 'ticketMethod'; // keep your existing tab; change if you use a different view key
1574
+ }
1575
+ accuracyReasons() {
1576
+ this.service
1577
+ .accuracyReasons(this.headerFilters?.client)
1578
+ .pipe(takeUntil(this.destroy$))
1579
+ .subscribe({
1580
+ next: (res: any) => {
1581
+ if (res && res.code === 200) {
1582
+ this.accuracyList = res.data.result
1583
+ }
1584
+ }
1585
+ })
1586
+
1587
+ }
1588
+ oncloseSubmit() {
1589
+ const modalRef = this.modalService.open(this.closeacuuracyissue)
1590
+ modalRef.result.then((result) => {
1591
+
1592
+ if (result === 'isConfirmed') {
1593
+
1594
+ let payload = {
1595
+ storeId: this.footfallTicketsData[0]?._source?.storeId,
1596
+ dateString: this.footfallTicketsData[0]?._source?.dateString,
1597
+ comments: this.selectedIssue,
1598
+ subComment: this.selectedsubIssue,
1599
+ }
1600
+
1601
+ this.service
1602
+ .accuracyReasonsupdate(payload)
1603
+ .pipe(takeUntil(this.destroy$))
1604
+ .subscribe({
1605
+ next: (res: any) => {
1606
+ if (res && res.code === 200) {
1607
+ this.ts.getSuccessToast('updated Sucessfully'),
1608
+ this.router.navigate(['/manage/tickets'], { queryParams: { type: 'footfall' } })
1609
+ }
1610
+ }
1611
+ })
1612
+
1613
+
1614
+ }
1615
+ })
1616
+ }
1617
+ dataStoreView(data?: any) {
1618
+ this.footfallTicketsData = [];
1619
+ this.footfallNoData = false;
1620
+ this.footfallLoading = true;
1621
+ this.allSelected = false;
1622
+ const ticket = data?.ticketId;
1623
+
1624
+ this.lastSelectedTicket = ticket;
1625
+ this.addStoreIfNotExists(ticket);
1626
+ this.imageUrl = this.service?.footfallCDN;
1627
+ this.storeIdValue = this.selectedStores;
1628
+
1629
+ this.service
1630
+ .getTicketsNewApi(ticket)
1631
+ .pipe(takeUntil(this.destroy$))
1632
+ .subscribe({
1633
+ next: (res: any) => {
1634
+ if (res && res.code === 200) {
1635
+ this.getIconType();
1636
+ if (res?.data?.result?.length === 0) {
1637
+ this.footfallTicketsData = [];
1638
+ this.footfallNoData = true;
1639
+ this.footfallLoading = false;
1640
+ this.ts.getErrorToast("No data found for the selected filters");
1641
+ } else {
1642
+ this.footfallTicketsData = res?.data?.result ?? [];
1643
+
1644
+ this.buildRevopsTypes(this.footfallTicketsData);
1645
+ this.totalItemsFootfall = res?.data?.count;
1646
+ this.cd.detectChanges();
1647
+ if (this.footfalllimit === 1) {
1648
+ this.paginationSizes = [1];
1649
+ this.pageSizeFootfall = 1;
1650
+ } else {
1651
+ const limit =
1652
+ this.totalItemsFootfall < 10 ? this.totalItemsFootfall : 10;
1653
+ this.paginationSizes = [limit];
1654
+ this.pageSizeFootfall = limit;
1655
+ }
1656
+ this.footfallNoData = false;
1657
+ this.footfallLoading = false;
1658
+ this.cd.detectChanges();
1659
+ // this.getCurrentReviewMapping();
1660
+ }
1661
+ } else {
1662
+ this.footfallTicketsData = [];
1663
+ this.footfallNoData = true;
1664
+ this.footfallLoading = false;
1665
+ }
1666
+ },
1667
+ error: (err: any) => {
1668
+ if (err?.error?.code === 400)
1669
+ this.ts.getErrorToast(
1670
+ err?.error?.message ? err?.error?.message : err?.error?.error
1671
+ );
1672
+ this.footfallTicketsData = [];
1673
+ this.footfallNoData = true;
1674
+ this.footfallLoading = false;
1675
+ },
1676
+ complete: () => {
1677
+ this.cd.detectChanges();
1678
+ },
1679
+ });
1680
+ }
1681
+ footfallTicketsData: any[] = [];
1682
+ // ---- SELECTION STATE ----
1683
+
1684
+ selectedByType: any = {};
1685
+ selectAllByType: any = {};
1686
+
1687
+ // ---- HELPERS ----
1688
+
1689
+ // Get all revisedDetail items of a given type from footfallTicketsData
1690
+ // Get all revisedDetail items of a given type from footfallTicketsData
1691
+ getListByType(type: any) {
1692
+ if (!this.footfallTicketsData) {
1693
+ return [];
1694
+ }
1695
+
1696
+ const allRevised: any[] = [];
1697
+
1698
+ // collect revisedDetail ONLY from mappingInfo with type === 'review'
1699
+ this.footfallTicketsData.forEach((ticket: any) => {
1700
+ const mappings = ticket?._source?.mappingInfo || [];
1701
+
1702
+ mappings
1703
+ .filter((m: any) => m?.type === 'review') // 🔹 IMPORTANT
1704
+ .forEach((mapping: any) => {
1705
+ const details = mapping?.revisedDetail || [];
1706
+ allRevised.push(...details);
1707
+ });
1708
+ });
1709
+
1710
+ // filter by type (junk / employee / houseKeeping)
1711
+ const filtered = allRevised.filter(
1712
+ (original: any) => original?.revopsType === type
1713
+ );
1714
+
1715
+ // 🔹 DEDUPE by tempId (or id) so we don't count same image twice
1716
+ const uniqueByTempId = Array.from(
1717
+ new Map(filtered.map((item: any) => [item.tempId, item])).values()
1718
+ );
1719
+
1720
+ return uniqueByTempId;
1721
+ }
1722
+
1723
+
1724
+
1725
+ private updateOverallSelectedIds(): void {
1726
+ const ids: string[] = [];
1727
+ const parents = this.getAllParentItems() || [];
1728
+ const selectedTypesSet = new Set<string>();
1729
+
1730
+ // Non-duplicate
1731
+ (this.revopsTypes || [])
1732
+ .filter((type) => type !== 'duplicate')
1733
+ .forEach((type: string) => {
1734
+ const list = this.getListByType(type) || [];
1735
+ const selectedTemps = this.selectedByType[type] || [];
1736
+
1737
+ if (selectedTemps.length > 0) selectedTypesSet.add(type);
1738
+
1739
+ list.forEach((item: any) => {
1740
+ if (
1741
+ selectedTemps.includes(item.tempId) &&
1742
+ item.id &&
1743
+ !this.isFinalStatus(item) // ⛔ safeguard
1744
+ ) {
1745
+ ids.push(item.id);
1746
+ } else if (selectedTemps.includes(item.tempId) &&
1747
+ item.id &&
1748
+ this.isFinalStatus(item)) {
1749
+ ids.push(item.id);
1750
+ }
1751
+ });
1752
+ });
1753
+
1754
+ // Duplicate
1755
+ parents
1756
+ .filter((p: any) => p.revopsType === 'duplicate')
1757
+ .forEach((parent: any) => {
1758
+ const parentId = parent.tempId;
1759
+ const selectedChildTemps =
1760
+ this.selectedDuplicatesByParent[parentId] || [];
1761
+
1762
+ if (selectedChildTemps.length > 0) {
1763
+ selectedTypesSet.add('duplicate');
1764
+ }
1765
+
1766
+ (parent.duplicateImage || []).forEach((child: any) => {
1767
+ if (
1768
+ selectedChildTemps.includes(child.tempId) &&
1769
+ !this.isFinalStatus(child) // ⛔ safeguard
1770
+ ) {
1771
+ if (child.id) {
1772
+ ids.push(child.id);
1773
+ } else if (parent.id) {
1774
+ ids.push(parent.id);
1775
+ }
1776
+ }
1777
+ });
1778
+ });
1779
+
1780
+ this.overallSelectedIds = Array.from(new Set(ids));
1781
+ this.selectedCategories = Array.from(selectedTypesSet);
1782
+ }
1783
+
1784
+
1785
+
1786
+ selectedCategories: any = [];
1787
+ labelMap: Record<string, string> = {};
1788
+ toTitleCase(str: string): string {
1789
+ return str.charAt(0).toUpperCase() + str.slice(1);
1790
+ }
1791
+
1792
+ buildLabelMap(tickets: any[]) {
1793
+ tickets.forEach((ticket: any) => {
1794
+ const mappingInfo = ticket?._source?.mappingInfo || [];
1795
+
1796
+ mappingInfo.forEach((mapping: any) => {
1797
+ (mapping?.revisedDetail || []).forEach((item: any) => {
1798
+
1799
+ // CASE 1: normal entries (employee, vendor, technician, etc.)
1800
+ if (item?.revopsType) {
1801
+ const key = item.revopsType;
1802
+ if (!this.labelMap[key]) {
1803
+ this.labelMap[key] = this.toTitleCase(key);
1804
+ }
1805
+ }
1806
+
1807
+ // CASE 2: summary rows with { name, type }
1808
+ if (item?.type && item?.name) {
1809
+ this.labelMap[item.type] = item.name;
1810
+ }
1811
+
1812
+ });
1813
+ });
1814
+ });
1815
+ }
1816
+ get selectedCategoriesLabel(): string {
1817
+ if (!this.selectedCategories?.length) return "";
1818
+
1819
+ if (this.selectedCategories.length === 1) {
1820
+ const type = this.selectedCategories[0];
1821
+ return this.labelMap[type] || this.toTitleCase(type);
1822
+ }
1823
+
1824
+ return this.selectedCategories
1825
+ .map((t: any) => this.labelMap[t] || this.toTitleCase(t))
1826
+ .join(", ");
1827
+ }
1828
+ type: any = 'footfall';
1829
+ // "Select all" / "Unselect all" for a type
1830
+ popupvalue: any;
1831
+ // or just: popupvalue: string = '';
1832
+
1833
+ onSelectAll(type: any, event: any) {
1834
+ const checked = event?.target?.checked;
1835
+
1836
+ this.selectAllByType[type] = checked;
1837
+ this.type = type;
1838
+ this.popupvalue = type;
1839
+
1840
+ const list = this.getListByType(type) || [];
1841
+
1842
+ // ⛔ skip items that are already approved / rejected
1843
+ const selectable = list.filter((x: any) => !this.isFinalStatus(x));
1844
+
1845
+ this.selectedByType[type] = checked
1846
+ ? selectable.map((x: any) => x.tempId)
1847
+ : [];
1848
+
1849
+ this.updateOverallSelectedIds();
1850
+ }
1851
+
1852
+
1853
+ // Single checkbox change for one image
1854
+ onImageCheckboxChange(type: string, id: any, event: any) {
1855
+ const checked = event?.target?.checked;
1856
+ if (!this.selectedByType[type]) {
1857
+ this.selectedByType[type] = [];
1858
+ }
1859
+ if (this.selectAllByType[type] === undefined) {
1860
+ this.selectAllByType[type] = false;
1861
+ }
1862
+
1863
+ const arr = this.selectedByType[type];
1864
+
1865
+ if (checked) {
1866
+ // add if not present
1867
+ if (!arr.includes(id)) {
1868
+ arr.push(id);
1869
+ }
1870
+ } else {
1871
+ // remove if present
1872
+ const idx = arr.indexOf(id);
1873
+ if (idx > -1) {
1874
+ arr.splice(idx, 1);
1875
+ }
1876
+ }
1877
+
1878
+ // total items for this type
1879
+ const total = this.getListByType(type).length;
1880
+
1881
+ // auto-update "Select All" for that type
1882
+ this.selectAllByType[type] = total > 0 && arr.length === total;
1883
+
1884
+ this.updateOverallSelectedIds();
1885
+ this.popupvalue = type;
1886
+ }
1887
+
1888
+
1889
+
1890
+ remarks: any;
1891
+ @ViewChild("zoomPopup") zoomPopup: ElementRef;
1892
+ popupType: any;
1893
+ popupIds: any[] = [];
1894
+ popupOpen(
1895
+ type: any,
1896
+ value?: any
1897
+ ) {
1898
+ // store type
1899
+ this.popupType = type;
1900
+ this.popupIds = this.overallSelectedIds || [];
1901
+ const modalRef = this.modalService.open(this.zoomPopup, {
1902
+ centered: true,
1903
+ size: "md",
1904
+ backdrop: "static",
1905
+ keyboard: false,
1906
+ });
1907
+ }
1908
+
1909
+ houseKeepingACCount: any;
1910
+ employeeACCount: any;
1911
+ duplicateACCount: any;
1912
+ junkACCount: any;
1913
+
1914
+ selectedDuplicateImagesList: any[] = [];
1915
+ selectedHousekeepingImagesList: any[] = [];
1916
+ selectedEmployeeImagesList: any[] = [];
1917
+ selectedJunkImagesList: any[] = [];
1918
+ submitValue(status: string = "approved", category: string = "duplicate") {
1919
+ let permissionType =
1920
+ this.hasApproverAccess && this.selectedRole === 'approver'
1921
+ ? 'approve'
1922
+ : this.hasReviewerAccess && this.selectedRole === 'reviewer'
1923
+ ? 'review'
1924
+ : null;
1925
+ // Step 1: Validate based on current category
1926
+ let obj = {
1927
+ id: this.popupIds,
1928
+ status: status,
1929
+ type: this.hasApproverAccess && this.selectedRole == 'approver' ? "approve" : "review",
1930
+ ...(this.remarks?.trim() && { comments: this.remarks.trim() })
1931
+ };
1932
+ this.service
1933
+ .getUpdateTempStatusApi(obj)
1934
+ .pipe(takeUntil(this.destroy$))
1935
+ .subscribe({
1936
+ next: (res: any) => {
1937
+ if (res && res?.code === 200) {
1938
+ this.ts.getSuccessToast(`${this.overallSelectedIds?.length ? this.overallSelectedIds?.length : this.popupValue ?
1939
+ this.popupValue : '--'} ${this.selectedCategoriesLabel.charAt(0).toUpperCase() + this.selectedCategoriesLabel.slice(1)} ${status}`);
1940
+
1941
+ this.cancel();
1942
+
1943
+ this.dataStoreView(this.ticketData);
1944
+
1945
+ this.remarks = "";
1946
+ } else {
1947
+ this.ts.getErrorToast("Error updating status");
1948
+ }
1949
+ },
1950
+ error: (err: any) => {
1951
+ const errorMsg =
1952
+ err?.error?.message ||
1953
+ err?.error?.error ||
1954
+ err?.error ||
1955
+ err?.message ||
1956
+ "Error updating status";
1957
+ this.ts.getErrorToast(errorMsg);
1958
+ },
1959
+ complete: () => {
1960
+ this.cd.detectChanges();
1961
+ },
1962
+ });
1963
+ }
1964
+ resetSelections() {
1965
+ this.selectedByType = {};
1966
+ this.selectAllByType = {};
1967
+ this.selectedDuplicatesByParent = {};
1968
+ this.overallSelectedIds = [];
1969
+ this.popupIds = [];
1970
+ }
1971
+
1972
+ cancel() {
1973
+ this.modalService.dismissAll();
1974
+ this.resetSelections();
1975
+ this.allSelected = false;
1976
+ this.selectedCategories = [];
1977
+ }
1978
+ openArrow() {
1979
+ this.arrowshow = !this.arrowshow;
1980
+ }
1981
+
1982
+ sortOpen: 1 | -1 = 1; // default ascending
1983
+
1984
+ onSortClick() {
1985
+ this.sortOpen = this.sortOpen === 1 ? -1 : 1; // 👈 toggle and SAVE
1986
+ this.getOpenTicketList();
1987
+ }
1988
+ searchStoresData() {
1989
+ this.StoresSearchValue = this.StoresSearchValue
1990
+ this.getOpenTicketList();
1991
+ }
1992
+ getOpenTicketList(data?: any) {
1993
+ this.footfallLoading = true;
1994
+ const selectedTicketId = data?.ticketId ?? data;
1995
+
1996
+ const obj = {
1997
+ clientId: [this.headerFilters?.client],
1998
+ fromDate: this.headerFilters?.date?.startDate,
1999
+ toDate: this.headerFilters?.date?.endDate,
2000
+ type: this.hasApproverAccess && this.selectedRole == 'approver' ? "approve" : "review",
2001
+ sortOrder: this.sortOpen,
2002
+ searchValue: this.StoresSearchValue
2003
+ };
2004
+
2005
+ this.service
2006
+ .getOpenTicketListApi(obj)
2007
+ .pipe(takeUntil(this.destroy$))
2008
+ .subscribe({
2009
+ next: (res: any) => {
2010
+ if (res && res?.code === 200) {
2011
+ this.openTicketsList = res?.data ?? [];
2012
+ this.selectedStores = [];
2013
+ this.allSelected = false;
2014
+
2015
+ if (!this.openTicketsList.length) {
2016
+ this.footfallNoData = true;
2017
+ this.footfallLoading = false
2018
+ this.allSelected = false;
2019
+ return;
2020
+ }
2021
+
2022
+ let storeToSelect: any | undefined;
2023
+ if (selectedTicketId) {
2024
+ storeToSelect = this.openTicketsList.find(
2025
+ (s: any) => s.ticketId === selectedTicketId
2026
+ );
2027
+ }
2028
+
2029
+ if (!storeToSelect) {
2030
+ storeToSelect = this.openTicketsList[0];
2031
+ }
2032
+
2033
+ this.selectedStores = [storeToSelect];
2034
+ this.dataStoreView(storeToSelect);
2035
+ } else {
2036
+ this.openTicketsList = [];
2037
+ this.selectedStores = [];
2038
+ this.footfallNoData = true;
2039
+ this.allSelected = false;
2040
+ }
2041
+ },
2042
+ error: () => {
2043
+ this.openTicketsList = [];
2044
+ this.selectedStores = [];
2045
+ this.allSelected = false;
2046
+ },
2047
+ complete: () => {
2048
+ this.cd.detectChanges();
2049
+ },
2050
+ });
2051
+ }
2052
+ isLockedByReviewer(original: any): boolean {
2053
+ if (this.hasApproverAccess && this.selectedRole === 'approver') {
2054
+ this.getCurrentRoleMapping();
2055
+ return this.isApproved1(original) || this.isRejected1(original);
2056
+ }
2057
+ this.getCurrentReviewMapping();
2058
+
2059
+ return this.isApproved(original) || this.isRejected(original);
2060
+ }
2061
+
2062
+ isApproved1(original: any): boolean {
2063
+ const actions = original?.actions || [];
2064
+ return actions.some(
2065
+ (a: any) => a.actionType === "approve" && a.action === "approved"
2066
+ );
2067
+ }
2068
+
2069
+ isRejected1(original: any): boolean {
2070
+ const actions = original?.actions || [];
2071
+ return actions.some(
2072
+ (a: any) => a.actionType === "approve" && a.action === "rejected"
2073
+ );
2074
+ }
2075
+
2076
+ isApproved(original: any): boolean {
2077
+ const actions = original?.actions || [];
2078
+ return actions.some(
2079
+ (a: any) => a.actionType === "review" && a.action === "approved"
2080
+ );
2081
+ }
2082
+
2083
+ isRejected(original: any): boolean {
2084
+ const actions = original?.actions || [];
2085
+ return actions.some(
2086
+ (a: any) => a.actionType === "review" && a.action === "rejected"
2087
+ );
2088
+ }
2089
+
2090
+
2091
+ resetCheckbox(
2092
+ type: any,
2093
+ original: any
2094
+ ) {
2095
+ if (!original?.actions || !Array.isArray(original.actions)) {
2096
+ return;
2097
+ }
2098
+ const actions = original.actions;
2099
+
2100
+ // 1) Check if there is a decision (review/approve → approved/rejected)
2101
+ const hasDecision = actions.some((a: any) =>
2102
+ (
2103
+ (a.actionType === "review" || a.actionType === "approve") &&
2104
+ (a.action === "approved" || a.action === "rejected")
2105
+ )
2106
+ );
2107
+
2108
+ // If NO approved/rejected → do nothing
2109
+ if (!hasDecision) {
2110
+ return;
2111
+ }
2112
+
2113
+ // 2) Remove only decision actions
2114
+ original.actions = actions.filter(
2115
+ (a: any) =>
2116
+ !(
2117
+ (a.actionType === "review" || a.actionType === "approve") &&
2118
+ (a.action === "approved" || a.action === "rejected")
2119
+ )
2120
+ );
2121
+
2122
+ // 3) Remove from selected array
2123
+ const tempId = original.tempId;
2124
+ const arr = this.selectedByType[type] || [];
2125
+ const i = arr.indexOf(tempId);
2126
+ if (i > -1) arr.splice(i, 1);
2127
+
2128
+ // 4) Uncheck select all for that type
2129
+ this.selectAllByType[type] = false;
2130
+ }
2131
+
2132
+
2133
+ viewMode: "tangoreview" | "approve" | "review" | "tagging" = "tagging";
2134
+ getAction(original: any, type: "tagging" | "review" | "tangoreview" | "approve") {
2135
+ return original?.actions?.find((a: any) => a.actionType === type) || null;
2136
+ }
2137
+
2138
+
2139
+ getCurrentReviewMapping(): any {
2140
+ if (!this.footfallTicketsData || !this.footfallTicketsData.length) {
2141
+ return null;
2142
+ }
2143
+
2144
+ // Go through all tickets and mappingInfo
2145
+ for (const ticket of this.footfallTicketsData) {
2146
+ const source = ticket?._source;
2147
+ const mappingList = source?.mappingInfo || [];
2148
+
2149
+ // pick the first mapping with type 'review' that has revisedDetail
2150
+ const found = mappingList.find(
2151
+ (m: any) => m?.type === 'review' && m?.revisedDetail?.length
2152
+ );
2153
+
2154
+ if (found) {
2155
+ return found;
2156
+ }
2157
+ }
2158
+
2159
+ return null;
2160
+ }
2161
+
2162
+
2163
+ // Decide if ticket is fully reviewed based on ALL mappingInfo[type='review']
2164
+ isTicketFullyReviewed(mapping: any): boolean {
2165
+ if (!mapping?.revisedDetail) return false;
2166
+
2167
+ const details = mapping.revisedDetail as any[];
2168
+
2169
+ const nonDupTypes: any = [];
2170
+
2171
+ const nonDupItems = details.filter(d =>
2172
+ nonDupTypes.includes(d.revopsType)
2173
+ );
2174
+
2175
+ const nonDupReviewedCount = nonDupItems.filter(d => {
2176
+ const review = this.getAction(d, "review");
2177
+ return review && (review.action === "approved" || review.action === "rejected");
2178
+ }).length;
2179
+
2180
+ const parentItems = details.filter(d => d.isParent);
2181
+
2182
+ const dupChildren = parentItems.reduce((acc: any[], p: any) => {
2183
+ return acc.concat(p.duplicateImage || []);
2184
+ }, []);
2185
+
2186
+ const dupReviewedCount = dupChildren.filter(d => {
2187
+ const review = this.getAction(d, "review");
2188
+ return review && (review.action === "approved" || review.action === "rejected");
2189
+ }).length;
2190
+
2191
+ const totalToReview = nonDupItems.length + dupChildren.length;
2192
+ const totalReviewed = nonDupReviewedCount + dupReviewedCount;
2193
+
2194
+ return totalToReview > 0 && totalReviewed === totalToReview;
2195
+ }
2196
+
2197
+
2198
+ getInitialsFromEmail(email?: string): string {
2199
+ if (!email) return "";
2200
+
2201
+ // take text before @
2202
+ const namePart = email.split("@")[0];
2203
+
2204
+ // split by . or space (e.g. "sandeep.pal" -> ["sandeep", "pal"])
2205
+ const parts = namePart.split(/[.\s_-]+/).filter(Boolean);
2206
+
2207
+ if (parts.length >= 2) {
2208
+ return (parts[0][0] + parts[1][0]).toUpperCase(); // S + P = SP
2209
+ }
2210
+
2211
+ // fallback: first two chars of whole namePart
2212
+ return namePart.substring(0, 2).toUpperCase();
2213
+ }
2214
+
2215
+ confirmCloseTicket() {
2216
+ let permissionType = this.hasApproverAccess && this.selectedRole === 'approver'
2217
+ ? 'approve'
2218
+ : this.hasReviewerAccess && this.selectedRole === 'reviewer'
2219
+ ? 'review'
2220
+ : null;
2221
+ let obj = {
2222
+ ticketName: "footfall-directory",
2223
+ storeId: this.footfallTicketsData[0]?._source?.storeId,
2224
+ dateString: this.footfallTicketsData[0]?._source?.dateString,
2225
+ type: this.hasApproverAccess && this.selectedRole == 'approver' ? "approve" : "review",
2226
+ mode: "web",
2227
+ };
2228
+ this.service
2229
+ .getCreateTicketListApi(obj)
2230
+ .pipe(takeUntil(this.destroy$))
2231
+ .subscribe({
2232
+ next: (res: any) => {
2233
+ if (res && res?.code === 200) {
2234
+ this.ts.getSuccessToast("Ticket closed successfully");
2235
+ this.modalService.dismissAll();
2236
+ this.closeBtn = false;
2237
+ this.closeDisabled = true;
2238
+ this.dataStoreView(this.ticketData);
2239
+ } else {
2240
+ this.closeBtn = true;
2241
+ this.closeDisabled = false;
2242
+ this.ts.getErrorToast(res.error ? res.error : '');
2243
+ }
2244
+ this.cd.detectChanges();
2245
+ },
2246
+ error: (err: any) => {
2247
+ const errorMsg =
2248
+ err?.error?.message ||
2249
+ err?.error?.error ||
2250
+ err?.error ||
2251
+ err?.message ||
2252
+ "Error closing ticket";
2253
+ this.ts.getErrorToast(errorMsg);
2254
+ // this.isCheckboxEnable = true;
2255
+ this.closeBtn = true;
2256
+ this.closeDisabled = false;
2257
+ }
2258
+ });
2259
+ }
2260
+
2261
+ confirmCloseCancel() {
2262
+ this.modalService.dismissAll();
2263
+ this.closeBtn = false;
2264
+ this.closeDisabled = true;
2265
+ this.dataStoreView(this.ticketData);
2266
+ }
2267
+ @ViewChild("imagePreviewPopup") imagePreviewPopup: ElementRef;
2268
+ previewList: any[] = [];
2269
+ currentPreviewIndex = 0;
2270
+ previewTitle = 'Tagged Duplicates';
2271
+ get currentPreviewItem() {
2272
+ if (!this.previewList || !this.previewList.length) {
2273
+ return null;
2274
+ }
2275
+
2276
+ if (
2277
+ this.currentPreviewIndex == null ||
2278
+ this.currentPreviewIndex < 0 ||
2279
+ this.currentPreviewIndex >= this.previewList.length
2280
+ ) {
2281
+ return null;
2282
+ }
2283
+
2284
+ return this.previewList[this.currentPreviewIndex];
2285
+ }
2286
+
2287
+
2288
+
2289
+ getPreviewImageUrl() {
2290
+ const item = this.currentPreviewItem;
2291
+ return item ? this.imageUrl + item.filePath : '';
2292
+ }
2293
+ overallMapping: any;
2294
+ openImagePreview(list: any, target: any, startIndex: number, title: string) {
2295
+
2296
+ this.overallMapping = list;
2297
+ this.previewTitle = title;
2298
+
2299
+ // Decide preview list
2300
+ if (Array.isArray(target) && target.length) {
2301
+ this.previewList = target;
2302
+ } else if (Array.isArray(list) && list.length) {
2303
+ this.previewList = list; // Case 2
2304
+ } else if (Array.isArray(list?.revisedDetail)) {
2305
+ this.previewList = list.revisedDetail; // Case 3
2306
+ } else {
2307
+ this.previewList = []; // fallback
2308
+ }
2309
+
2310
+ // Set correct preview index
2311
+ this.setPreviewIndex(startIndex, target);
2312
+
2313
+ // Open popup
2314
+ this.modalService.open(this.imagePreviewPopup, {
2315
+ centered: true,
2316
+ size: 'lg',
2317
+ backdrop: 'static',
2318
+ });
2319
+ }
2320
+
2321
+ private setPreviewIndex(startIndex: number | null | undefined, target: any) {
2322
+
2323
+ let index = -1;
2324
+
2325
+ // 1️⃣ Always try to find the clicked item by ID/tempId FIRST
2326
+ if (target) {
2327
+ index = this.previewList.findIndex((item: any) =>
2328
+ (target.tempId && item.tempId === target.tempId) ||
2329
+ (target.id && item.id === target.id)
2330
+ );
2331
+ }
2332
+
2333
+ // 2️⃣ If not found by ID, fallback to startIndex (ONLY if valid)
2334
+ if (
2335
+ index < 0 &&
2336
+ startIndex != null &&
2337
+ startIndex >= 0 &&
2338
+ startIndex < this.previewList.length
2339
+ ) {
2340
+ index = startIndex;
2341
+ }
2342
+
2343
+ // 3️⃣ Final fallback: use first image
2344
+ if (index < 0) {
2345
+ index = 0;
2346
+ }
2347
+
2348
+ // Save final index
2349
+ this.currentPreviewIndex = index;
2350
+ }
2351
+
2352
+
2353
+
2354
+ // 1) Check if there is an APPROVE mapping with status Closed in overall mapping
2355
+ overallApproveClosed(): boolean {
2356
+ const list = this.overallMapping || [];
2357
+
2358
+ return Array.isArray(list) && list.some((m: any) =>
2359
+ m?.type === 'approve' &&
2360
+ m?.status?.toLowerCase() === 'closed'
2361
+ );
2362
+ }
2363
+
2364
+ // 2) Get approve action for a single image
2365
+ getApproveActionForItem(item: any): any | null {
2366
+ const actions = item?.actions || [];
2367
+ return actions.find(
2368
+ (a: any) =>
2369
+ a.actionType === 'approve' &&
2370
+ (a.action === 'approved' || a.action === 'rejected')
2371
+ ) || null;
2372
+ }
2373
+
2374
+ // 3) Final function: can we show Accept / Reject for this image?
2375
+ canShowPreviewActions(item: any): boolean {
2376
+ // if overall approve is closed -> no buttons
2377
+ if (this.overallApproveClosed()) {
2378
+ return false;
2379
+ }
2380
+
2381
+ // if this image already has approve approved/rejected -> no buttons
2382
+ const approveAction = this.getApproveActionForItem(item);
2383
+ if (approveAction) {
2384
+ return false;
2385
+ }
2386
+
2387
+ // otherwise -> show buttons
2388
+ return true;
2389
+ }
2390
+
2391
+ prevPreview() {
2392
+ if (this.previewList.length <= 1) return;
2393
+ this.currentPreviewIndex =
2394
+ this.currentPreviewIndex === 0
2395
+ ? this.previewList.length - 1
2396
+ : this.currentPreviewIndex - 1;
2397
+ }
2398
+
2399
+ nextPreview() {
2400
+ if (this.previewList.length <= 1) return;
2401
+ this.currentPreviewIndex =
2402
+ this.currentPreviewIndex === this.previewList.length - 1
2403
+ ? 0
2404
+ : this.currentPreviewIndex + 1;
2405
+ }
2406
+ popupValue: any;
2407
+ rejectedPopup(type: any, value: any) {
2408
+ this.overallSelectedIds = [value?.id];
2409
+ this.popupValue = 1;
2410
+ this.popupOpen(type)
2411
+ }
2412
+ approvedPopup(type: any, value: any) {
2413
+ this.overallSelectedIds = [value?.id]
2414
+ this.popupValue = 1;
2415
+ this.popupOpen(type)
2416
+ }
2417
+ get hasApproverAccess(): boolean {
2418
+ const perm = this.usersDetails?.permission;
2419
+ return !!(
2420
+ perm?.FootfallDirectory_approver_isAdd ||
2421
+ perm?.FootfallDirectory_approver_isEdit
2422
+ );
2423
+ }
2424
+
2425
+ get hasReviewerAccess(): boolean {
2426
+ const perm = this.usersDetails?.permission;
2427
+ return !!(
2428
+ perm?.FootfallDirectory_reviewer_isAdd ||
2429
+ perm?.FootfallDirectory_reviewer_isEdit
2430
+ );
2431
+ }
2432
+
2433
+ // call this in ngOnInit or after usersDetails is loaded
2434
+ setDefaultRole() {
2435
+ if (this.hasApproverAccess) {
2436
+ this.selectedRole = 'approver';
2437
+ } else if (this.hasReviewerAccess) {
2438
+ this.selectedRole = 'reviewer';
2439
+ } else {
2440
+ this.selectedRole = '';
2441
+ }
2442
+ }
2443
+
2444
+ SelectedRole(role: 'approver' | 'reviewer') {
2445
+ if (role === 'approver' && !this.hasApproverAccess) return;
2446
+ if (role === 'reviewer' && !this.hasReviewerAccess) return;
2447
+
2448
+ this.selectedRole = role;
2449
+ this.getTicketList(this.tangoType);
2450
+ this.getTicketSummary(this.tangoType);
2451
+ // 🔹 put your role-based logic here:
2452
+ // e.g. filter mappingInfo, change API call, etc.
2453
+ // this.loadRoleData(role);
2454
+ }
2455
+
2456
+
2457
+ showApproveDetails = false;
2458
+ showTangoDetails = false;
2459
+
2460
+ toggleRevisedDetails(mapping: any) {
2461
+ if (mapping?.type === 'approve') {
2462
+ this.showApproveDetails = !this.showApproveDetails;
2463
+ } else if (mapping?.type === 'tangoreview') {
2464
+ this.showTangoDetails = !this.showTangoDetails;
2465
+ }
2466
+ }
2467
+
2468
+ reviwerList: any;
2469
+ reviewrReassignApi() {
2470
+ this.service.getReviewerApi(this.headerFilters?.client, 'approve',this.usersDetails?.userType).pipe(takeUntil(this.destroy$))
2471
+ .subscribe({
2472
+ next: (res: any) => {
2473
+ if (res && res?.code === 200) {
2474
+ this.reviwerList = res?.data;
2475
+
2476
+ } else {
2477
+ this.reviwerList = [];
2478
+ }
2479
+ }, error: (err) => {
2480
+
2481
+ },
2482
+ })
2483
+
2484
+ }
2485
+ reviewrApi() {
2486
+ this.service.getReviewerApi(this.headerFilters?.client, 'approve', this.tangoType).pipe(takeUntil(this.destroy$))
2487
+ .subscribe({
2488
+ next: (res: any) => {
2489
+ if (res && res?.code === 200) {
2490
+ this.reviwerList = res?.data;
2491
+ this.viewTicket('store');
2492
+ } else {
2493
+ this.reviwerList = [];
2494
+ }
2495
+ }, error: (err) => {
2496
+
2497
+ },
2498
+ })
2499
+
2500
+ }
2501
+ reviewerChange(event: any) {
2502
+ this.selectedReviewer = event;
2503
+ }
2504
+ assignTicketView() {
2505
+
2506
+ if (this.usersDetails?.userType==='tango') {
2507
+ let obj = {
2508
+ email: this.selectedReviewer?.email,
2509
+ userName: this.selectedReviewer?.userName,
2510
+ storeId: this.ticketData?.storeId,
2511
+ dateString: this.ticketData?.issueDate,
2512
+ tickettype:this.tangoType
2513
+ }
2514
+ this.service.reAssignAudit(obj).pipe(takeUntil(this.destroy$))
2515
+ .subscribe({
2516
+ next: (res: any) => {
2517
+ if (res && res?.code === 200) {
2518
+ this.ts.getSuccessToast(res?.data?.message ? res?.data?.message : 'Ticket assigned successfully');
2519
+ this.backToNavigation();
2520
+ this.cancelassign();
2521
+ }
2522
+ }
2523
+ })
2524
+ return
2525
+ }
2526
+
2527
+
2528
+ let obj = {
2529
+ email: this.selectedReviewer?.email,
2530
+ userName: this.selectedReviewer?.userName,
2531
+ actionType: this.hasApproverAccess && this.selectedRole === 'approver' ? "approve" : "review",
2532
+ storeId: this.ticketData?.storeId,
2533
+ dateString: this.ticketData?.issueDate
2534
+ }
2535
+ this.service.getTicketAssignApi(obj).pipe(takeUntil(this.destroy$))
2536
+ .subscribe({
2537
+ next: (res: any) => {
2538
+ if (res && res?.code === 200) {
2539
+ this.ts.getSuccessToast(res?.data?.message ? res?.data?.message : 'Ticket assigned successfully');
2540
+ this.backToNavigation();
2541
+ this.cancelassign();
2542
+ }
2543
+ else {
2544
+ this.ts.getErrorToast("Error updating status");
2545
+ }
2546
+ }, error: (err) => {
2547
+ const errorMsg =
2548
+ err?.error?.message ||
2549
+ err?.error?.error ||
2550
+ err?.error ||
2551
+ err?.message ||
2552
+ "Error closing ticket";
2553
+ this.ts.getErrorToast(errorMsg);
2554
+ },
2555
+ })
2556
+ }
2557
+ cancelassign() {
2558
+ this.modalService.dismissAll();
2559
+ this.reviwerList = [];
2560
+ this.selectedReviewer = [];
2561
+ }
2562
+ isTicketMenuOpen = false;
2563
+
2564
+ toggleTicketMenu(event: MouseEvent) {
2565
+ event.stopPropagation();
2566
+ this.isTicketMenuOpen = !this.isTicketMenuOpen;
2567
+ }
2568
+
2569
+ // close when clicking outside
2570
+ @HostListener('document:click')
2571
+ onDocumentClick() {
2572
+ this.isTicketMenuOpen = false;
2573
+ }
2574
+
2575
+ onReassignClick() {
2576
+ this.isTicketMenuOpen = false;
2577
+ // 🔹 open your popup here
2578
+ this.openReassignPopup(); // implement this
2579
+ }
2580
+ @ViewChild("ReassignTicketPopup") ReassignTicketPopup: ElementRef;
2581
+ openReassignPopup() {
2582
+ const modalRef = this.modalService.open(this.ReassignTicketPopup, {
2583
+ centered: true,
2584
+ size: "md",
2585
+ scrollable: true,
2586
+ backdrop: "static",
2587
+ });
2588
+ this.reviewrReassignApi();
2589
+ }
2590
+ onExportClick() {
2591
+ this.isTicketMenuOpen = false;
2592
+ // 🔹 call your export API / logic here
2593
+ // this.exportTicket();
2594
+ }
2595
+
2596
+ getMultipleTicketClose() {
2597
+ // if (!this.selectedStores || !this.selectedStores.length) {
2598
+ // this.ts.getErrorToast('Please select at least one ticket');
2599
+ // return;
2600
+ // }
2601
+ let ticketList: any;
2602
+
2603
+ if (this.selectedStores?.length) {
2604
+ // When multiple stores are selected
2605
+ ticketList = this.selectedStores.map((store: any) => ({
2606
+ storeId: store.storeId,
2607
+ dateString: store.dateString || store.issueDate,
2608
+ }));
2609
+ } else {
2610
+ // When ticket rows are selected instead
2611
+ ticketList = this.selectedTicketRows.map((store: any) => ({
2612
+ storeId: store.storeId,
2613
+ dateString: store.dateString || store.issueDate,
2614
+ }));
2615
+ }
2616
+
2617
+ const obj = {
2618
+ ticketList,
2619
+ mode: 'web',
2620
+ };
2621
+
2622
+ this.service.getMultiCloseTicketApi(obj).subscribe({
2623
+ next: (res: any) => {
2624
+ if (res && res.code === 200) {
2625
+ // success
2626
+ this.ts.getSuccessToast('Tickets closed successfully');
2627
+ this.viewTicket(this.tangoType);
2628
+ this.selectedStores = [];
2629
+ this.allSelected = false;
2630
+ } else {
2631
+ // this.toastService.getErrorToast('Failed to close tickets');
2632
+ }
2633
+ },
2634
+ error: (err) => {
2635
+ console.error('Failed to close tickets', err);
2636
+ this.ts.getErrorToast('Failed to close tickets');
2637
+ },
2638
+ });
2639
+ }
2640
+ isRowSelectable(row: any): boolean {
2641
+ return row?.status?.toLowerCase() === 'open' || row?.status === 'Reviewed-Closed';
2642
+ }
2643
+ selectedTicketRows: any = [];
2644
+ // check if a row is currently selected
2645
+ isRowSelected(row: any): boolean {
2646
+ return this.selectedTicketRows.includes(row);
2647
+ }
2648
+
2649
+ // rows that CAN be selected (status open)
2650
+ getSelectableRows(): any[] {
2651
+ return (this.footfallListData || []).filter((r: any) => this.isRowSelectable(r));
2652
+ }
2653
+
2654
+ // header "select all" checkbox state
2655
+ areAllSelectableRowsSelected(): boolean {
2656
+ const selectable = this.getSelectableRows();
2657
+ return (
2658
+ selectable.length > 0 &&
2659
+ selectable.every(r => this.selectedTicketRows.includes(r))
2660
+ );
2661
+ }
2662
+ closeMultiple = true;
2663
+
2664
+
2665
+
2666
+ private updateCloseDisabled() {
2667
+ this.closeMultiple = this.selectedTicketRows.length === 0;
2668
+ }
2669
+ // single row checkbox toggle
2670
+ toggleRowSelection(row: any, event: Event): void {
2671
+ if (!this.isRowSelectable(row)) return;
2672
+
2673
+ const target = event.target as HTMLInputElement;
2674
+ const checked = target.checked;
2675
+
2676
+ if (checked) {
2677
+ if (!this.isRowSelected(row)) {
2678
+ this.selectedTicketRows = [...this.selectedTicketRows, row];
2679
+ }
2680
+ } else {
2681
+ this.selectedTicketRows = this.selectedTicketRows.filter((r: any) => r !== row);
2682
+ }
2683
+ this.updateCloseDisabled();
2684
+ }
2685
+
2686
+ // header "Select All" toggle
2687
+ toggleSelectAllRows(event: Event): void {
2688
+ const target = event.target as HTMLInputElement;
2689
+ const checked = target.checked;
2690
+
2691
+ if (checked) {
2692
+ this.selectedTicketRows = this.getSelectableRows().slice();
2693
+ } else {
2694
+ this.selectedTicketRows = [];
2695
+ }
2696
+ this.updateCloseDisabled();
2697
+ }
2698
+ showImagesModal = false;
2699
+ selectedImagesForPopup: any[] = [];
2700
+ selectedComment: any | null = null;
2701
+
2702
+ openImagesPopup(comment: any): void {
2703
+ // return
2704
+ this.selectedComment = comment;
2705
+ this.selectedImagesForPopup = comment?.taggedImages || [];
2706
+ this.showImagesModal = true;
2707
+ }
2708
+
2709
+ closeImagesPopup(): void {
2710
+ this.showImagesModal = false;
2711
+ this.selectedImagesForPopup = [];
2712
+ this.selectedComment = null;
2713
+ }
2714
+
2715
+ getIconTypeList: any;
2716
+ tagIconMap: Record<string, string> = {}; // type -> iconName
2717
+
2718
+ getIconType() {
2719
+ this.service
2720
+ .getTypeFunction(this.headerFilters?.client)
2721
+ .pipe(takeUntil(this.destroy$))
2722
+ .subscribe({
2723
+ next: (res: any) => {
2724
+ if (res && res?.code === 200) {
2725
+ this.getIconTypeList = res?.data?.footfallDirectoryConfigs || {};
2726
+
2727
+ const taggingLimitation = this.getIconTypeList?.taggingLimitation || [];
2728
+
2729
+ this.tagIconMap = {};
2730
+ taggingLimitation.forEach((item: any) => {
2731
+ if (item?.type && item?.iconName) {
2732
+ this.tagIconMap[item.type] = item.iconName;
2733
+ }
2734
+ });
2735
+ } else {
2736
+ this.getIconTypeList = {};
2737
+ this.tagIconMap = {};
2738
+ }
2739
+ },
2740
+ error: (err) => {
2741
+ this.getIconTypeList = {};
2742
+ this.tagIconMap = {};
2743
+ },
2744
+ });
2745
+ }
2746
+ onFilterPanelClosed() {
2747
+ // nothing required, but you can do logging here
2748
+ }
2749
+
2750
+ onFilterApply(filters: any) {
2751
+ this.filterPayload = filters;
2752
+ this.offset = 0;
2753
+ this.getTicketList(this.tangoType);
2754
+ }
2755
+ revFilter: 'all' | 'accept' | 'reject' | 'pending' = 'all';
2756
+ // private getActionStatus(item: any): 'accept' | 'reject' | 'pending' | null {
2757
+ // const actions = item?.actions || [];
2758
+
2759
+ // const approveAction = actions.find(
2760
+ // (a: any) => a?.actionType === 'approve' || a?.actionType === 'review'
2761
+ // );
2762
+
2763
+ // if (!approveAction) return 'pending';
2764
+
2765
+ // const act = (approveAction.action || '').toLowerCase();
2766
+
2767
+ // if (act === 'approved' || act === 'accept' || act === 'accepted') {
2768
+ // return 'accept';
2769
+ // }
2770
+
2771
+ // if (act === 'rejected' || act === 'reject') {
2772
+ // return 'reject';
2773
+ // }
2774
+
2775
+ // return 'pending';
2776
+ // }
2777
+ onFilterChange(): any {
2778
+ if (this.revFilter === 'all') return true;
2779
+
2780
+ this.footfallTicketsData.map((mapdata: any) => {
2781
+ mapdata._source.mappingInfo.map((data: any) => {
2782
+ if (data.status === this.footfallTicketsData[0]._source.status) {
2783
+ if (data.revisedDetail.revopsType === 'duplicate') {
2784
+
2785
+ }
2786
+ }
2787
+
2788
+
2789
+
2790
+ })
2791
+
2792
+ })
2793
+
2794
+
2795
+ }
2796
+ private getItemStatus(item: any): 'accept' | 'reject' | 'pending' {
2797
+ const actions = item?.actions || [];
2798
+
2799
+ const act = actions.find(
2800
+ (a: any) => a.actionType === 'approve' || a.actionType === 'review'
2801
+ );
2802
+
2803
+ if (!act) return 'pending';
2804
+
2805
+ const s = (act.action || '').toLowerCase();
2806
+
2807
+ if (s === 'approved' || s === 'accept' || s === 'accepted') return 'accept';
2808
+ if (s === 'rejected' || s === 'reject') return 'reject';
2809
+
2810
+ return 'pending';
2811
+ }
2812
+ matchesStatusFilter(item: any): boolean {
2813
+ if (!item) return false;
2814
+ const status = this.getItemStatus(item);
2815
+
2816
+ if (this.revFilter === 'all') return true;
2817
+ if (this.revFilter === 'accept') return status === 'accept';
2818
+ if (this.revFilter === 'reject') return status === 'reject';
2819
+ if (this.revFilter === 'pending') return status === 'pending';
2820
+
2821
+ return true;
2822
+ }
2823
+
2824
+ // onFilterChange() {
2825
+ // console.log("Filter changed:", this.revFilter);
2826
+ // }
2827
+
2828
+ selectPlanTrends(type: string) {
2829
+ this.type = type;
2830
+ this.applyFilter1(this.filterList);
2831
+ }
2832
+
2833
+ filteredList: any[] = [];
2834
+
2835
+
2836
+ applyFilter1(mapping: any) {
2837
+ const list = Array.isArray(mapping) ? mapping : [];
2838
+
2839
+ if (!this.type || this.type === 'footfall') {
2840
+ this.filteredList = list.slice(); // copy
2841
+ return;
2842
+ }
2843
+
2844
+ // simple filter by revopsType (change to 'processType' or other field if you need)
2845
+ this.filteredList = list.filter((item: any) => {
2846
+ // handle undefined safely
2847
+ const t = (item?.revopsType || '').toString().toLowerCase();
2848
+
2849
+ // if you pass special types like 'non-tag' vs 'non-tagging', normalize:
2850
+ const sel = this.type.toString().toLowerCase();
2851
+
2852
+ // Example rules:
2853
+ if (sel === 'non-tags') return t === 'non-tag' || t === 'non-tagging';
2854
+ if (sel === 'footfall') return (item?.processType || '').toString().toLowerCase() === 'footfall';
2855
+ if (sel === 'duplicated') return t === 'duplicate' || (item?.duplicateImage?.length > 0);
2856
+ if (sel === 'footfall') return true;
2857
+
2858
+ // default: exact match on revopsType
2859
+ return t === sel;
2860
+ });
2861
+ }
2862
+
2863
+ }