tango-app-api-trax 3.8.13 → 3.8.14-nike

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.
@@ -0,0 +1,218 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6
+ <title>Weekly Wrap</title>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <style type="text/css">
9
+ body { font-family: "Inter", sans-serif !important; margin:0; padding:0; background:#dbe5ea; }
10
+ table { border-collapse: collapse; }
11
+ a { color:#1a82e2; }
12
+ .card {
13
+ background:#F4F8FB;
14
+ border-radius:8px;
15
+ padding:20px 22px;
16
+ }
17
+ .cardLabel { font-size:13px; color:#475569; margin-bottom:6px; }
18
+ .cardValue { font-size:22px; font-weight:700; color:#0F172A; }
19
+ .wow { font-size:12px; font-weight:600; margin-left:6px; }
20
+ .wow.up { color:#16A34A; }
21
+ .wow.down { color:#DC2626; }
22
+ .wowLabel { font-size:11px; color:#94A3B8; margin-left:4px; }
23
+ .sectionTitle { font-size:16px; font-weight:700; color:#0F172A; margin:18px 0 10px; }
24
+ .breakdownRow { font-size:14px; color:#1E293B; line-height:24px; }
25
+ .insightCard {
26
+ background:#EFF7FF;
27
+ border-radius:8px;
28
+ padding:20px 22px;
29
+ }
30
+ .attach {
31
+ background:#F1F5F9;
32
+ border-radius:8px;
33
+ padding:12px 14px;
34
+ display:flex;
35
+ align-items:center;
36
+ gap:10px;
37
+ font-size:13px;
38
+ color:#0F172A;
39
+ }
40
+ .ctaBtn {
41
+ display:inline-block;
42
+ background:#00A3FF;
43
+ color:#FFFFFF !important;
44
+ padding:14px 36px;
45
+ font-weight:600;
46
+ font-size:15px;
47
+ border-radius:8px;
48
+ text-decoration:none;
49
+ }
50
+ .footerText { font-size:12px; color:#475569; line-height:18px; }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <table width="100%" cellpadding="0" cellspacing="0" style="background:#dbe5ea;padding:24px 10px;">
55
+ <tr>
56
+ <td align="center" style="padding-top:10px;padding-bottom:10px">
57
+ <table width="100%" cellpadding="0" cellspacing="0" style="max-width:680px;background:#FFFFFF;border:1px dashed #93B6CC;border-radius:10px;overflow:hidden;">
58
+
59
+ {{!-- Header --}}
60
+ <tr>
61
+ <td style="padding:20px 24px 0 24px;">
62
+ <img src="https://devtangoretail-api.tangoeye.ai/logo.png" width="160" alt="TangoEye"/>
63
+ </td>
64
+ </tr>
65
+
66
+ {{!-- Banner --}}
67
+ <tr>
68
+ <td style="padding:14px 24px 0 24px;">
69
+ <div style="position:relative;background:#1F2A37;border-radius:10px;color:#FFFFFF;padding:36px 20px;text-align:center;">
70
+ <div style="font-size:22px;font-weight:700;">Weekly Wrap</div>
71
+ <div style="font-size:13px;color:#CBD5E1;margin-top:6px;">
72
+ Your Store's Flags Insights for {{data.startDate}} - {{data.endDate}}
73
+ </div>
74
+ </div>
75
+ </td>
76
+ </tr>
77
+
78
+ {{!-- Greeting --}}
79
+ <tr>
80
+ <td style="padding:18px 24px 0 24px;font-size:14px;color:#1E293B;line-height:22px;">
81
+ Hi Team,<br/>
82
+ Here is your weekly checklist insights report, highlighting key observations, recurring issues, and areas that may require attention to improve store performance and compliance.
83
+ </td>
84
+ </tr>
85
+
86
+ {{!-- Weekly Overview --}}
87
+ <tr>
88
+ <td style="padding:8px 24px 0 24px;">
89
+ <div class="sectionTitle">Weekly Overview</div>
90
+ <table width="100%" cellpadding="0" cellspacing="8">
91
+ <tr>
92
+ <td width="30%" style="padding-right:10px">
93
+ <div class="card">
94
+ <div class="cardLabel">Stores Flagged</div>
95
+ <div>
96
+ <span class="cardValue">{{data.storesFlagged}}</span>
97
+ {{#if data.storesFlaggedWow.value}}
98
+ <span class="wow {{data.storesFlaggedWow.direction}}">{{#eq data.storesFlaggedWow.direction "up"}}&#x2197;{{else}}&#x2198;{{/eq}} {{data.storesFlaggedWow.value}}</span>
99
+ <span class="wowLabel">WoW</span>
100
+ {{/if}}
101
+ </div>
102
+ </div>
103
+ </td>
104
+ <td width="30%" style="padding-right:10px">
105
+ <div class="card">
106
+ <div class="cardLabel">Total Flags</div>
107
+ <div>
108
+ <span class="cardValue">{{data.totalFlags}}</span>
109
+ {{#if data.totalFlagsWow.value}}
110
+ <span class="wow {{data.totalFlagsWow.direction}}">{{#eq data.totalFlagsWow.direction "up"}}&#x2197;{{else}}&#x2198;{{/eq}} {{data.totalFlagsWow.value}}</span>
111
+ <span class="wowLabel">WoW</span>
112
+ {{/if}}
113
+ </div>
114
+ </div>
115
+ </td>
116
+ <td width="30%" style="padding-right:10px">
117
+ <div class="card">
118
+ <div class="cardLabel">Checklist Flags</div>
119
+ <div>
120
+ <span class="cardValue">{{data.checklistFlags}}</span>
121
+ {{#if data.checklistFlagsWow.value}}
122
+ <span class="wow {{data.checklistFlagsWow.direction}}">{{#eq data.checklistFlagsWow.direction "up"}}&#x2197;{{else}}&#x2198;{{/eq}} {{data.checklistFlagsWow.value}}</span>
123
+ <span class="wowLabel">WoW</span>
124
+ {{/if}}
125
+ </div>
126
+ </div>
127
+ </td>
128
+ </tr>
129
+ </table>
130
+ </td>
131
+ </tr>
132
+
133
+ {{!-- Flags Breakdown --}}
134
+ <tr>
135
+ <td style="padding:8px 24px 0 24px;">
136
+ <div class="sectionTitle">Flags Breakdown</div>
137
+ <div class="breakdownRow">
138
+ Question Flags : {{data.questionFlags}} &nbsp;&nbsp;
139
+ Not Submitted Flags : {{data.notSubmittedFlags}} &nbsp;&nbsp;
140
+ Run AI Flags : {{data.runAIFlags}} &nbsp;&nbsp;
141
+ Recurring Flags : {{data.recurringFlags}}
142
+ </div>
143
+ </td>
144
+ </tr>
145
+
146
+ {{!-- Key Insights --}}
147
+ <tr>
148
+ <td style="padding:8px 24px 0 24px;">
149
+ <div class="sectionTitle">Key Insights</div>
150
+ <table width="100%" cellpadding="0" cellspacing="8">
151
+ <tr>
152
+ <td width="40%" style="padding-right:10px">
153
+ <div class="insightCard">
154
+ <div class="cardLabel">Highest Flagged Store</div>
155
+ <div>
156
+ <span class="cardValue">{{data.highestFlaggedStore}}</span>
157
+ {{#if data.highestFlaggedStoreWow.value}}
158
+ <span class="wow {{data.highestFlaggedStoreWow.direction}}">{{#eq data.highestFlaggedStoreWow.direction "up"}}&#x2197;{{else}}&#x2198;{{/eq}} {{data.highestFlaggedStoreWow.value}}</span>
159
+ <span class="wowLabel">WoW</span>
160
+ {{/if}}
161
+ </div>
162
+ </div>
163
+ </td>
164
+ <td width="40%">
165
+ <div class="insightCard">
166
+ <div class="cardLabel">Highest Recurring Flagged Store</div>
167
+ <div>
168
+ <span class="cardValue">{{data.highestRecurringStore}}</span>
169
+ {{#if data.highestRecurringStoreWow.value}}
170
+ <span class="wow {{data.highestRecurringStoreWow.direction}}">{{#eq data.highestRecurringStoreWow.direction "up"}}&#x2197;{{else}}&#x2198;{{/eq}} {{data.highestRecurringStoreWow.value}}</span>
171
+ <span class="wowLabel">WoW</span>
172
+ {{/if}}
173
+ </div>
174
+ </div>
175
+ </td>
176
+ </tr>
177
+ </table>
178
+ </td>
179
+ </tr>
180
+
181
+ {{!-- Attachment hint --}}
182
+ <tr>
183
+ <td style="padding:18px 24px 0 24px;">
184
+ <div style="color:#1D2939;font-family: Inter;font-weight: 400;font-style: Regular;font-size: 14px;leading-trim: NONE;line-height: 100%;letter-spacing: 0.2px;">
185
+ Refer to the attached report for the weekly summary of the stores assigned to you.
186
+ </div>
187
+ <div style="color:#384860;font-family: Inter;font-weight: 400;font-style: Regular;font-size: 10px;leading-trim: NONE;line-height: 150%;letter-spacing: 0.2px;padding-top:10px">
188
+ If you have any questions or need assistance, please reach out to us at support@tangotech.co.in.
189
+ </div>
190
+ {{!-- <div class="attach">
191
+ <span style="display:inline-block;width:22px;height:22px;background:#21A366;color:#FFFFFF;border-radius:4px;text-align:center;line-height:22px;font-size:12px;font-weight:700;">X</span>
192
+ <span><b>{{data.attachmentName}}</b>{{#if data.attachmentSize}} &nbsp; <span style="color:#94A3B8;">{{data.attachmentSize}}</span>{{/if}}</span>
193
+ </div> --}}
194
+ </td>
195
+ </tr>
196
+
197
+ {{!-- CTA --}}
198
+ <tr>
199
+ <td style="padding:18px 24px 8px 24px;text-align:center;">
200
+ <a href="{{data.domain}}" class="ctaBtn">View Detailed Insights</a>
201
+ </td>
202
+ </tr>
203
+
204
+ {{!-- Footer --}}
205
+ <tr>
206
+ <td style="padding:18px 24px 24px 24px;border-top:1px solid #E2E8F0;">
207
+ <div class="footerText">
208
+ &copy;2024 Tango IT Solutions India Pvt. Ltd. All rights reserved.<br/>
209
+ Old Number 12 and New, 39, Haddows Rd, Thousand Lights West, Nungambakkam, Chennai, Tamil Nadu 600006
210
+ </div>
211
+ </td>
212
+ </tr>
213
+ </table>
214
+ </td>
215
+ </tr>
216
+ </table>
217
+ </body>
218
+ </html>
@@ -37,12 +37,16 @@ internalTraxRouter
37
37
  .post( '/getSubmissionDetails', isAllowedInternalAPIHandler, internalController.checklistTaskSubmissionDetails )
38
38
  .post( '/posblock', isAllowedInternalAPIHandler, internalController.getStoreTaskDetails )
39
39
  .post( '/runAIFlag', isAllowedInternalAPIHandler, internalController.runAIFlag )
40
+ .post( '/incrementRunAIRecurring', isAllowedInternalAPIHandler, internalController.incrementRunAIRecurring )
41
+ .post( '/recurringFlag', isAllowedInternalAPIHandler, internalController.recurringFlagAlert )
42
+ .post( '/weeklyWrap', isAllowedInternalAPIHandler, internalController.weeklyWrapAlert )
40
43
  .post( '/downloadInsertPdf', isAllowedInternalAPIHandler, internalController.downloadInsertPdf )
41
44
  .get( '/checklistAutoMailList', isAllowedInternalAPIHandler, internalController.checklistAutoMailList )
42
45
  .post( '/sendAIEmailList', isAllowedInternalAPIHandler, internalController.sendAIEmailList )
43
46
  .post( '/liveAiPushNotificationAlert', isAllowedInternalAPIHandler, internalController.liveAiPushNotificationAlert )
44
47
  .get( '/aiChecklistDetails', isAllowedInternalAPIHandler, internalController.aiChecklistDetails )
45
48
  .get( '/getEyetestStream', isAllowedInternalAPIHandler, internalController.getEyetestStream )
49
+ .post( '/updateStoreLatLong', isAllowedInternalAPIHandler, internalController.updateStoreLatLong )
46
50
  ;
47
51
 
48
52
 
@@ -0,0 +1,33 @@
1
+ import model from 'tango-api-schema';
2
+
3
+ export const findOne = async ( query={}, field={} ) => {
4
+ return model.recurringFlagTrackerModel.findOne( query, field );
5
+ };
6
+
7
+ export const find = async ( query={}, field={} ) => {
8
+ return model.recurringFlagTrackerModel.find( query, field );
9
+ };
10
+
11
+ export const create = async ( document = {} ) => {
12
+ return model.recurringFlagTrackerModel.create( document );
13
+ };
14
+
15
+ export const updateOne = async ( query = {}, record={} ) => {
16
+ return model.recurringFlagTrackerModel.updateOne( query, { $set: record }, { upsert: true } );
17
+ };
18
+
19
+ export const updateMany = async ( query = {}, record={} ) => {
20
+ return model.recurringFlagTrackerModel.updateMany( query, { $set: record } );
21
+ };
22
+
23
+ export const deleteOne = async ( query = {} ) => {
24
+ return model.recurringFlagTrackerModel.deleteOne( query );
25
+ };
26
+
27
+ export const aggregate = async ( query = [] ) => {
28
+ return model.recurringFlagTrackerModel.aggregate( query );
29
+ };
30
+
31
+ export const bulkWrite = async ( ops = [] ) => {
32
+ return model.recurringFlagTrackerModel.bulkWrite( ops );
33
+ };
@@ -129,9 +129,23 @@ function getMediaDisplayType( questionAnswerType, userAnswer = {} ) {
129
129
  function getValidationDisplayType( validationType ) {
130
130
  if ( validationType === 'Capture Image' ) return 'image';
131
131
  if ( validationType === 'Capture Video' ) return 'video';
132
+ if ( validationType === 'Capture Multiple Image with description' ) return 'multiImage';
132
133
  return 'text';
133
134
  }
134
135
 
136
+ function flattenImageRefs( arr ) {
137
+ if ( !Array.isArray( arr ) ) return [];
138
+ return arr
139
+ .map( ( ele ) => {
140
+ if ( typeof ele === 'string' ) return ele;
141
+ if ( ele && typeof ele === 'object' ) {
142
+ return ele.imageURL || ele.imageUrl || ele.url || ele.image || ele.path || '';
143
+ }
144
+ return '';
145
+ } )
146
+ .filter( Boolean );
147
+ }
148
+
135
149
  function buildQuestionAnswerEntries( question ) {
136
150
  const rawUserAnswers = getSourceUserAnswers( question );
137
151
  const uniqueUserAnswers = [];
@@ -153,15 +167,31 @@ function buildQuestionAnswerEntries( question ) {
153
167
  const validationType = matchedAnswer?.validationType || userAnswer?.validationType || '';
154
168
  const validationAnswer = matchedAnswer?.validationAnswer || userAnswer?.validationAnswer || '';
155
169
 
170
+ const rawMultiRefSources = [
171
+ userAnswer?.multiReferenceImage,
172
+ // matchedAnswer?.multiReferenceImage,
173
+ // question?.answers?.[0]?.multiReferenceImage,
174
+ ];
175
+ let multiReferenceImage = [];
176
+ for ( const src of rawMultiRefSources ) {
177
+ const flat = flattenImageRefs( src );
178
+ if ( flat.length ) {
179
+ multiReferenceImage = flat; break;
180
+ }
181
+ }
182
+ const validationImage = flattenImageRefs( userAnswer?.validationImage );
183
+
156
184
  return {
157
185
  answer: userAnswer?.answer || '',
158
186
  answerType: getMediaDisplayType( question?.answerType, userAnswer ),
159
187
  referenceImage: userAnswer?.referenceImage || matchedAnswer?.referenceImage || '',
188
+ multiReferenceImage,
160
189
  remarks: userAnswer?.remarks || '',
161
190
  sopFlag: userAnswer?.sopFlag ?? matchedAnswer?.sopFlag ?? false,
162
191
  validation,
163
192
  validationType,
164
193
  validationAnswer,
194
+ validationImage,
165
195
  validationDisplayType: getValidationDisplayType( validationType ),
166
196
  };
167
197
  } );
@@ -276,6 +306,10 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
276
306
 
277
307
  compliance: Boolean( q.compliance ),
278
308
 
309
+ questionReferenceImage: q.questionReferenceImage || '',
310
+
311
+ multiQuestionReferenceImage: flattenImageRefs( q.multiQuestionReferenceImage ),
312
+
279
313
  isYes: isYes || ( !isNo && score >= 10 ),
280
314
 
281
315
  isNo,
@@ -410,7 +444,7 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
410
444
 
411
445
  const checklistName = doc.checkListName || 'Visit Checklist';
412
446
 
413
- const refFromId = doc._id && String( doc._id ).length > 8 ? String( doc._id ).slice( -8 ).toUpperCase() : ( doc._id ? String( doc._id ) : '--' );
447
+ // const refFromId = doc._id && String( doc._id ).length > 8 ? String( doc._id ).slice( -8 ).toUpperCase() : ( doc._id ? String( doc._id ) : '--' );
414
448
 
415
449
  const { titleLine1, titleLine2 } = splitCoverTitle( checklistName );
416
450
 
@@ -750,14 +784,27 @@ export function createImageCache() {
750
784
 
751
785
  const collectFromSection = ( section ) => {
752
786
  section.questions?.forEach( ( q ) => {
787
+ if ( q.questionReferenceImage && q.questionReferenceImage.startsWith( 'http' ) ) urls.add( q.questionReferenceImage );
788
+ q.multiQuestionReferenceImage?.forEach( ( u ) => {
789
+ if ( typeof u === 'string' && u.startsWith( 'http' ) ) urls.add( u );
790
+ } );
753
791
  q.userAnswer?.forEach( ( ua ) => {
754
792
  if ( ua.referenceImage && ua.referenceImage.startsWith( 'http' ) ) urls.add( ua.referenceImage );
755
793
 
794
+ ua.multiReferenceImage?.forEach( ( u ) => {
795
+ if ( typeof u === 'string' && u.startsWith( 'http' ) ) urls.add( u );
796
+ } );
756
797
 
757
798
  if ( ua.answer && ua.answerType === 'image' && ua.answer.startsWith( 'http' ) ) urls.add( ua.answer );
758
799
 
759
800
 
760
801
  if ( ua.validationAnswer && ua.validationDisplayType === 'image' && ua.validationAnswer.startsWith( 'http' ) ) urls.add( ua.validationAnswer );
802
+
803
+ if ( ua.validationDisplayType === 'multiImage' ) {
804
+ ua.validationImage?.forEach( ( u ) => {
805
+ if ( typeof u === 'string' && u.startsWith( 'http' ) ) urls.add( u );
806
+ } );
807
+ }
761
808
  } );
762
809
  } );
763
810
  };
@@ -797,14 +844,25 @@ export function createImageCache() {
797
844
 
798
845
  const replaceInSection = ( section ) => {
799
846
  section.questions?.forEach( ( q ) => {
847
+ if ( q.questionReferenceImage && cache.has( q.questionReferenceImage ) ) q.questionReferenceImage = cache.get( q.questionReferenceImage );
848
+ if ( q.multiQuestionReferenceImage?.length ) {
849
+ q.multiQuestionReferenceImage = q.multiQuestionReferenceImage.map( ( u ) => ( cache.has( u ) ? cache.get( u ) : u ) );
850
+ }
800
851
  q.userAnswer?.forEach( ( ua ) => {
801
852
  if ( ua.referenceImage && cache.has( ua.referenceImage ) ) ua.referenceImage = cache.get( ua.referenceImage );
802
853
 
854
+ if ( ua.multiReferenceImage?.length ) {
855
+ ua.multiReferenceImage = ua.multiReferenceImage.map( ( u ) => ( cache.has( u ) ? cache.get( u ) : u ) );
856
+ }
803
857
 
804
858
  if ( ua.answer && ua.answerType === 'image' && cache.has( ua.answer ) ) ua.answer = cache.get( ua.answer );
805
859
 
806
860
 
807
861
  if ( ua.validationAnswer && ua.validationDisplayType === 'image' && cache.has( ua.validationAnswer ) ) ua.validationAnswer = cache.get( ua.validationAnswer );
862
+
863
+ if ( ua.validationDisplayType === 'multiImage' && ua.validationImage?.length ) {
864
+ ua.validationImage = ua.validationImage.map( ( u ) => ( cache.has( u ) ? cache.get( u ) : u ) );
865
+ }
808
866
  } );
809
867
  } );
810
868
  };
@@ -859,7 +917,14 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
859
917
 
860
918
  const resolveQuestionMedia = ( section ) => {
861
919
  section.questions?.forEach( ( q ) => {
920
+ if ( q.questionReferenceImage ) q.questionReferenceImage = resolveUrl( q.questionReferenceImage );
921
+ if ( q.multiQuestionReferenceImage?.length ) {
922
+ q.multiQuestionReferenceImage = q.multiQuestionReferenceImage.map( ( ele ) => resolveUrl( ele ) );
923
+ }
862
924
  q.userAnswer?.forEach( ( ua ) => {
925
+ if ( ua.multiReferenceImage?.length ) {
926
+ ua.multiReferenceImage = ua.multiReferenceImage.map( ( ele ) => resolveUrl( ele ) );
927
+ }
863
928
  if ( ua.referenceImage ) ua.referenceImage = resolveUrl( ua.referenceImage );
864
929
 
865
930
 
@@ -867,6 +932,10 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
867
932
  ua.validationAnswer = resolveUrl( ua.validationAnswer );
868
933
  }
869
934
 
935
+ if ( ua?.validationImage?.length ) {
936
+ ua.validationImage = ua.validationImage.map( ( ele ) => resolveUrl( ele ) );
937
+ }
938
+
870
939
 
871
940
  if ( ua.answer && ua.answerType !== 'text' ) {
872
941
  ua.answer = resolveUrl( ua.answer );
@@ -982,13 +1051,25 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
982
1051
 
983
1052
  const resolveQuestionMedia = ( section ) => {
984
1053
  section.questions?.forEach( ( q ) => {
1054
+ if ( q.questionReferenceImage ) q.questionReferenceImage = resolveUrl( q.questionReferenceImage );
1055
+ if ( q.multiQuestionReferenceImage?.length ) {
1056
+ q.multiQuestionReferenceImage = q.multiQuestionReferenceImage.map( ( ele ) => resolveUrl( ele ) );
1057
+ }
985
1058
  q.userAnswer?.forEach( ( ua ) => {
986
1059
  if ( ua.referenceImage ) ua.referenceImage = resolveUrl( ua.referenceImage );
987
1060
 
1061
+ if ( ua.multiReferenceImage?.length ) {
1062
+ ua.multiReferenceImage = ua.multiReferenceImage.map( ( ele ) => resolveUrl( ele ) );
1063
+ }
1064
+
988
1065
  if ( ua.validationAnswer && ua.validationDisplayType !== 'text' ) {
989
1066
  ua.validationAnswer = resolveUrl( ua.validationAnswer );
990
1067
  }
991
1068
 
1069
+ if ( ua.validationImage?.length ) {
1070
+ ua.validationImage = ua.validationImage.map( ( ele ) => resolveUrl( ele ) );
1071
+ }
1072
+
992
1073
  if ( ua.answer && ua.answerType !== 'text' ) {
993
1074
  ua.answer = resolveUrl( ua.answer );
994
1075
  }