tango-app-api-trax 3.7.93 → 3.7.94

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-trax",
3
- "version": "3.7.93",
3
+ "version": "3.7.94",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -33,6 +33,13 @@ import puppeteer from 'puppeteer';
33
33
  import { getBrowser as getBrowserInstance } from '../utils/browserPool.utils.js';
34
34
  import fs from 'fs';
35
35
  import path from 'path';
36
+ import { fileURLToPath as toPath } from 'url';
37
+
38
+ const __ctrlDir = path.dirname( toPath( import.meta.url ) );
39
+ const tangoyeLogoSvg = fs.readFileSync(
40
+ path.join( __ctrlDir, '../hbs/partials/tangoye-footer-logo.hbs' ),
41
+ 'utf8',
42
+ ).replaceAll( '{{gid}}', 'ft' );
36
43
 
37
44
 
38
45
  const ObjectId = mongoose.Types.ObjectId;
@@ -3546,7 +3553,23 @@ export const downloadInsertPdf = async ( req, res ) => {
3546
3553
  pdfBuffer = await page.pdf( {
3547
3554
  format: 'A4',
3548
3555
  printBackground: true,
3549
- margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
3556
+ preferCSSPageSize: true,
3557
+ displayHeaderFooter: true,
3558
+ headerTemplate: '<span></span>',
3559
+ footerTemplate: [
3560
+ '<div style="width:100%;padding:0 10mm;font-size:10px;',
3561
+ 'font-family:Arial,sans-serif;display:flex;',
3562
+ 'justify-content:space-between;align-items:center;',
3563
+ 'border-top:1px solid #d9d9d9;padding-top:4px;">',
3564
+ '<span style="color:#999;">Page ',
3565
+ '<span class="pageNumber"></span>',
3566
+ ' of <span class="totalPages"></span></span>',
3567
+ '<span style="display:flex;align-items:center;gap:6px;">',
3568
+ '<span style="color:#666;font-weight:400;font-size:10px;">',
3569
+ 'Generated by</span>',
3570
+ tangoyeLogoSvg,
3571
+ '</span></div>',
3572
+ ].join( '' ),
3550
3573
  } );
3551
3574
  } catch ( err ) {
3552
3575
  logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed', error: err } );
@@ -7,15 +7,15 @@
7
7
  <style>
8
8
  *{box-sizing:border-box;margin:0;padding:0}
9
9
  body{font-family:Arial,Helvetica,sans-serif;background:#fff;padding:0}
10
- .page{width:794px;min-height:1123px;background:#fff;padding:0;overflow:hidden;position:relative;page-break-after:always}
11
- .cover-wrap{position:relative;width:794px;height:1123px;background:#fff;overflow:hidden;font-family:Arial,Helvetica,sans-serif}
10
+ .page{width:794px;background:#fff;padding:0;overflow:hidden;position:relative;page-break-after:always}
11
+ .cover-wrap{position:relative;width:794px;height:284mm;background:#fff;overflow:hidden;font-family:Arial,Helvetica,sans-serif}
12
12
  /* Cover — match brand PDF (cyan title, right geometry, grey footer rule) */
13
13
  /* Right-edge artwork: flush right; height 1043px leaves ~80px for footer band on 1123px cover */
14
- .cover-deco{position:absolute;top:0;right:0;left:auto;width:493px;height:1043px;pointer-events:none;z-index:0}
14
+ .cover-deco{position:absolute;top:0;right:0;bottom:0;left:auto;width:450px;pointer-events:none;z-index:0;}
15
15
  .cover-brand{position:absolute;top:48px;left:48px;display:flex;align-items:center;gap:10px;z-index:1}
16
16
  .cover-brand-name{font-size:22px;font-weight:600;color:#1a1a1a;letter-spacing:.02em;text-transform:lowercase}
17
17
  .cover-title-block{position:absolute;top:188px;left:48px;max-width:440px;z-index:1}
18
- .cover-title-line1,.cover-title-line2{font-size:54px;font-weight:600;color:#00AEEF;line-height:1.06;letter-spacing:-.5px}
18
+ .cover-title-line1,.cover-title-line2{font-size:40px;font-weight:600;color:#00AEEF;line-height:1.15;letter-spacing:-.5px;word-wrap:break-word;overflow-wrap:break-word}
19
19
  .cover-meta-block{position:absolute;top:418px;left:48px;z-index:1}
20
20
  .cover-ref{font-size:22px;font-weight:700;color:#1a1a1a;letter-spacing:.02em}
21
21
  .cover-datetime{font-size:15px;color:#1a1a1a;font-weight:400;margin-top:10px}
@@ -28,12 +28,12 @@
28
28
  .cover-footer-page{font-size:11px;color:#999}
29
29
  .cover-footer-gen{display:flex;align-items:center;gap:8px;font-size:12px;color:#00AEEF;font-weight:600}
30
30
  /* Score page */
31
- .score-page{padding:40px}
31
+ .score-page{margin-bottom:40px;padding-bottom:20px;border-bottom:1px solid #e0e0e0}
32
32
  .score-hero{text-align:center;margin-bottom:36px}
33
33
  .score-pct{font-size:72px;font-weight:700;color:#00AEEF;line-height:1}
34
34
  .score-sub{font-size:18px;color:#444;margin-top:8px}
35
35
  .score-date{font-size:14px;color:#888;margin-top:4px}
36
- .section-title{font-size:16px;font-weight:700;color:#1a1a2e;margin-bottom:16px;padding-bottom:8px;border-bottom:2px solid #00AEEF}
36
+ .section-title{font-size:16px;font-weight:700;color:#1a1a2e;margin-bottom:30px;padding-bottom:10px;border-bottom:2px solid #00AEEF}
37
37
  .history-bars{display:flex;align-items:flex-end;gap:8px;height:160px;margin-bottom:36px}
38
38
  .bar-wrap{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px}
39
39
  .bar{width:100%;background:#00AEEF;border-radius:4px 4px 0 0;display:flex;align-items:flex-start;justify-content:center}
@@ -50,9 +50,9 @@
50
50
  .pct-mid{background:#faeeda;color:#854f0b}
51
51
  .pct-lo{background:#fcebeb;color:#a32d2d}
52
52
  /* Detail pages */
53
- .detail-page{padding:32px 40px;break-inside:auto}
54
- .q-row{break-inside:avoid}
55
- .q-answer-item{break-inside:avoid}
53
+ .detail-page{padding:32px 40px}
54
+ .q-row{break-inside:auto}
55
+ .q-answer-item{break-inside:auto}
56
56
  .dp-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:12px;border-bottom:2px solid #00AEEF}
57
57
  .dp-header h2{font-size:18px;font-weight:700;color:#1a1a2e}
58
58
  .dp-score{font-size:15px;font-weight:700;color:#000000}
@@ -79,19 +79,20 @@
79
79
  .footer-gen-by{color:#666;font-weight:400}
80
80
  .cover-footer-gen .footer-gen-by{color:#1a1a1a}
81
81
  .footer-tangoye-logo{display:block;flex-shrink:0}
82
- @page{size:A4;margin:0}
82
+ @page{size:A4;margin:5mm 0mm 15mm 0mm}
83
+ @page:first{margin:0}
83
84
  </style>
84
85
  </head>
85
86
  <body>
86
87
 
87
88
  {{!-- PAGE 1: COVER --}}
88
89
  <div class="page cover-wrap">
89
- <svg class="cover-deco" width="493" height="1043" viewBox="0 0 398 842" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMaxYMin meet" aria-hidden="true">
90
- <path opacity="0.8" d="M397.199 842V586L334.199 292L222.199 842H397.199Z" fill="#99DAFF"/>
91
- <path opacity="0.7" d="M125.199 0H142.544L397.199 204.045V380L125.199 0Z" fill="#51C1FF"/>
92
- <path opacity="0.7" d="M84.7003 -0.00271425L125.197 -0.00675424L334.347 292.188L298.195 469L84.7003 -0.00271425Z" fill="#99DAFF"/>
93
- <path opacity="0.7" d="M397.199 360.707L334.199 292L397.199 586V360.707Z" fill="#6BCAFF"/>
94
- <path opacity="0.6" d="M397.199 220L125.199 0H397.199V220Z" fill="#009BF3"/>
90
+ <svg xmlns="http://www.w3.org/2000/svg" class="cover-deco" viewBox="0 0 650 2000" fill="none">
91
+ <path opacity="0.8" d="M711.5 2000V1391.92L568.063 693.586L313.062 2000H711.5Z" fill="#99DAFF" />
92
+ <path opacity="0.7" d="M92.2148 0.0078125H131.706L711.501 484.677V902.622L92.2148 0.0078125Z" fill="#51C1FF" />
93
+ <path opacity="0.7" d="M0 0.00959645L92.203 0L568.392 694.051L486.082 1114.03L0 0.00959645Z" fill="#99DAFF" />
94
+ <path opacity="0.7" d="M711.5 856.786L568.062 693.586L711.5 1391.92V856.786Z" fill="#6BCAFF" />
95
+ <path opacity="0.6" d="M711.501 522.574L92.2148 0.0078125H711.501V522.574Z" fill="#009BF3" />
95
96
  </svg>
96
97
 
97
98
  <div class="cover-brand">
@@ -115,25 +116,25 @@
115
116
  <div class="cover-sum-row"><span class="cover-sum-label">Country</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{country}}</span></div>
116
117
  </div>
117
118
 
118
- <div class="cover-footer">
119
+ {{!-- <div class="cover-footer">
119
120
  <span class="cover-footer-page"></span>
120
121
  <div class="cover-footer-gen">
121
122
  <span class="footer-gen-by">Generated by</span>
122
123
  {{> tangoyeFooterLogo gid="cover"}}
123
124
  </div>
124
- </div>
125
+ </div> --}}
125
126
  </div>
126
127
 
127
- {{!-- PAGE 2: SCORE SUMMARY --}}
128
+ {{!-- SCORE SUMMARY + DETAIL (continuous flow) --}}
129
+ <div class="detail-page">
128
130
  {{#if hasCompliancePage}}
129
- <div class="page">
130
131
  <div class="score-page">
131
132
  <div class="score-hero">
132
133
  <div class="score-pct">{{totalPercentage}}%</div>
133
134
  <div class="score-sub">Total: <strong>{{totalScore}}</strong> out of <strong>{{maxScore}}</strong></div>
134
135
  <div class="score-date">{{reportDate}}</div>
135
136
  </div>
136
-
137
+
137
138
  {{#if historyData}}
138
139
  <div class="section-title">History — Last 7 Days</div>
139
140
  <div style="display:flex;flex-direction:column;margin-bottom:36px">
@@ -148,7 +149,7 @@
148
149
  <div class="bar-axis"></div>
149
150
  </div>
150
151
  {{/if}}
151
-
152
+
152
153
  <div class="section-title">Section Wise Insights</div>
153
154
  <table class="sw-table">
154
155
  <thead><tr><th>Sections</th><th>Target Score</th><th>Actual Scrore</th><th>%</th></tr></thead>
@@ -164,18 +165,8 @@
164
165
  </tbody>
165
166
  </table>
166
167
  </div>
167
- {{!-- <div class="page-footer">
168
- <span>Page 2 of {{totalPages}}</span>
169
- <span class="footer-brand"><span class="footer-gen-by">Generated by</span>{{> tangoyeFooterLogo gid="p2"}}</span>
170
- </div>
171
- </div> --}}
172
168
  {{/if}}
173
-
174
- {{!-- PAGES 3+: DETAIL - Question sections --}}
175
- {{#each detailPageGroups}}
176
- <div class="page">
177
- <div class="detail-page">
178
- {{#each this.sections}}
169
+ {{#each sections}}
179
170
  <div class="dp-header" {{#unless @first}}style="margin-top:20px"{{/unless}}><h2>{{this.sectionName}}</h2>{{#if this.maxScore}}<span class="dp-score">{{this.currentScore}}/{{this.maxScore}}</span>{{/if}}</div>
180
171
  <div class="sec-questions">
181
172
  {{#each this.questions}}
@@ -247,22 +238,7 @@
247
238
  {{/each}}
248
239
  </div>
249
240
  {{/each}}
250
-
251
- {{!-- {{#if this.isLastGroup}}
252
- {{#each ../flags}}
253
- <div style="margin-top:12px;padding:12px;background:#faeeda;border-radius:8px;border-left:4px solid #ef9f27">
254
- <strong style="font-size:13px;color:#854f0b">⚠ Flag: {{this.sectionName}} — {{this.qname}}</strong>
255
- <p style="font-size:12px;color:#854f0b;margin-top:4px">Q{{this.qno}} ({{this.sectionName}}): "{{this.qname}}" — Answered: <strong>{{this.answer}}</strong>. Action required.</p>
256
- </div>
257
- {{/each}}
258
- {{/if}} --}}
259
- </div>
260
- {{!-- <div class="page-footer">
261
- <span>Page {{this.pageNumber}} of {{../totalPages}}</span>
262
- <span class="footer-brand"><span class="footer-gen-by">Generated by</span>{{> tangoyeFooterLogo gid=(strConcat 'd' @index)}}</span>
263
- </div> --}}
264
241
  </div>
265
- {{/each}}
266
242
 
267
243
  </body>
268
244
  </html>
@@ -214,7 +214,19 @@ function mapSectionsFromQuestionAnswer( questionAnswer ) {
214
214
  const max = q.compliance ? Math.max( ...q?.answers.map( ( o ) => o?.complianceScore ?? Math.max( o?.matchedCount ?? 0, o?.notMatchedCount ?? 0 ) ) ) : 0;
215
215
 
216
216
 
217
- const score = q.compliance ? Math.max( ...q?.userAnswer?.map( ( o ) => o?.complianceScore ?? 0 ) ) : 0;
217
+ let score = q.compliance ? Math.max( ...q?.userAnswer?.map( ( o ) => o?.complianceScore ?? 0 ) ) : 0;
218
+
219
+
220
+ if ( q.answerType == 'image' && q.compliance && q.userAnswer?.[0]?.runAIData ) {
221
+ let find = q.userAnswer?.[0]?.runAIData?.find( ( run ) => run?.featureName == 'Matched/Not Matched' );
222
+ if ( find ) {
223
+ if ( find?.value == 'True' ) {
224
+ score = q?.answers?.[0]?.matchedCount;
225
+ } else {
226
+ score = q?.answers?.[0]?.notMatchedCount;
227
+ }
228
+ }
229
+ }
218
230
 
219
231
 
220
232
  sectionScore += score;
@@ -491,6 +503,8 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
491
503
 
492
504
  detailPageGroups,
493
505
 
506
+ sections: detailPageGroups.flatMap( ( g ) => g.sections ),
507
+
494
508
  flags,
495
509
 
496
510
  };
@@ -629,6 +643,8 @@ function buildFromViewChecklistApi( getchecklistData, viewchecklistData, brandIn
629
643
 
630
644
  detailPageGroups,
631
645
 
646
+ sections: detailPageGroups.flatMap( ( g ) => g.sections ),
647
+
632
648
  flags,
633
649
 
634
650
  complianceCount: hasCompliancePage,
@@ -753,6 +769,8 @@ export function createImageCache() {
753
769
  group.sections?.forEach( collectFromSection );
754
770
  } );
755
771
 
772
+ resolvedData.sections?.forEach( collectFromSection );
773
+
756
774
 
757
775
  // Fetch all unique URLs in parallel (max 20 concurrent)
758
776
 
@@ -798,6 +816,8 @@ export function createImageCache() {
798
816
  group.sections?.forEach( replaceInSection );
799
817
  } );
800
818
 
819
+ resolvedData.sections?.forEach( replaceInSection );
820
+
801
821
 
802
822
  return resolvedData;
803
823
  }
@@ -862,6 +882,8 @@ export function resolveTemplateUrls( templateData, baseUrl = 'https://d1r0hc2ssk
862
882
  group.sections?.forEach( resolveQuestionMedia );
863
883
  } );
864
884
 
885
+ resolvedData.sections?.forEach( resolveQuestionMedia );
886
+
865
887
 
866
888
  if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
867
889
  resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );
@@ -977,6 +999,7 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
977
999
  resolvedData.detailPageGroups?.forEach( ( group ) => {
978
1000
  group.sections?.forEach( resolveQuestionMedia );
979
1001
  } );
1002
+ resolvedData.sections?.forEach( resolveQuestionMedia );
980
1003
 
981
1004
  if ( resolvedData.brandLogo && !resolvedData.brandLogo.startsWith( 'http' ) ) {
982
1005
  resolvedData.brandLogo = resolveUrl( resolvedData.brandLogo );