tango-app-api-trax 3.7.98 → 3.7.99

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.98",
3
+ "version": "3.7.99",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "nodemon": "^3.1.4",
30
30
  "path": "^0.12.7",
31
31
  "puppeteer": "^24.39.1",
32
- "tango-api-schema": "^2.5.70",
32
+ "tango-api-schema": "^2.5.68",
33
33
  "tango-app-api-middleware": "^3.5.2",
34
34
  "url": "^0.11.4",
35
35
  "winston": "^3.13.1",
@@ -2147,36 +2147,6 @@ export async function AiPushNotificationAlert( req, res ) {
2147
2147
  }
2148
2148
  }
2149
2149
 
2150
- export async function liveAiPushNotificationAlert( req, res ) {
2151
- try {
2152
- // console.log( req.body );
2153
- let findAichecklist = await PCLconfig.findOne( { checkListType: req.body.checkListType, client_id: req.body.clientId, date_string: req.body.Date }, { aiConfig: 1 } );
2154
- if ( findAichecklist?.aiConfig?.alertConfig?.mobile.length ) {
2155
- // console.log( findAichecklist.aiConfig.alerts.users );
2156
- for ( let user of findAichecklist?.aiConfig?.alertConfig?.mobile ) {
2157
- let findOneUser = await userService.findOne( { email: user.value }, { fcmToken: 1 } );
2158
- // console.log( findOneUser );
2159
- if ( findOneUser&&findOneUser.fcmToken&&findOneUser.fcmToken!='' ) {
2160
- // console.log( findOneUser.fcmToken );
2161
- try {
2162
- await sendPushNotification( req.body.title, req.body.description, findOneUser.fcmToken );
2163
- } catch ( e ) {
2164
- logger.error( {
2165
- message: 'push notification',
2166
- error: e,
2167
- details: data,
2168
- } );
2169
- }
2170
- } ;
2171
- }
2172
- }
2173
- return res.sendSuccess( 'Push notification send successfully' );
2174
- } catch ( e ) {
2175
- logger.error( { function: 'AiPushNotificationAlert', error: e } );
2176
- return res.sendError( e, 500 );
2177
- }
2178
- }
2179
-
2180
2150
 
2181
2151
  export async function taskPushNotification( req, res ) {
2182
2152
  try {
@@ -3615,16 +3585,9 @@ export const downloadInsertPdf = async ( req, res ) => {
3615
3585
  pdfBuffer :
3616
3586
  Buffer.from( pdfBuffer );
3617
3587
 
3618
- let pdfName;
3619
- if ( doc.store_id ) {
3620
- pdfName = `${safeName(
3621
- doc.store_id + '_' + ( doc.storeName || 'store' ),
3622
- )}.pdf`;
3623
- } else {
3624
- pdfName = `${safeName(
3625
- doc.userName + '_' + ( doc.sourceCheckList_id || 'store' ),
3626
- )}.pdf`;
3627
- }
3588
+ const pdfName = `${safeName(
3589
+ doc.store_id + '_' + ( doc.storeName || 'store' ),
3590
+ )}.pdf`;
3628
3591
 
3629
3592
  res.set( {
3630
3593
  'Content-Type': 'application/pdf',
@@ -4039,123 +4002,3 @@ async function LamdaServiceCall( url, data ) {
4039
4002
  return false;
4040
4003
  }
4041
4004
  }
4042
-
4043
- export async function sendAIEmailList( req, res ) {
4044
- try {
4045
- let spocList;
4046
- let result = [];
4047
- let getChecklistDetails = await CLconfig.findOne( { client_id: req.body.clientId, publish: true, checkListName: req.body.checklistName }, { aiConfig: 1 } );
4048
- if ( getChecklistDetails && getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.enabled && getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.email?.type?.length ) {
4049
- let userList = [ ...getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.email?.users.map( ( ele ) => ele.value ) ];
4050
- if ( getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.email?.type.includes( 'store' ) ) {
4051
- let checklistAssignList = await CLassign.find( { checkListId: getChecklistDetails._id } );
4052
- let storeSpoc = await storeService.find( { storeName: { $in: checklistAssignList.map( ( ele ) => ele.storeName ) } }, { spocDetails: 1, storeName: 1, storeId: 1 } );
4053
- if ( storeSpoc.length ) {
4054
- spocList = storeSpoc.map( ( ele ) => {
4055
- return { email: ele?.spocDetails?.[0]?.email, store: ele?.storeId };
4056
- } );
4057
- }
4058
- }
4059
- if ( getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.email?.type.includes( 'teams' ) ) {
4060
- let teamDetails = await teamsServices.findteams( { teamName: { $in: getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.email?.teams } }, { spocDetails: 1 } );
4061
- if ( teamDetails.length ) {
4062
- userList.push( teamDetails.flatMap( ( team ) => team.users.map( ( user ) => user.email ) ) );
4063
- }
4064
- }
4065
- if ( getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.email?.type.includes( 'users' ) ) {
4066
- userList.push( getChecklistDetails?.aiConfig?.alertConfig?.alertSendTo?.email.users.map( ( team ) => team.value ) );
4067
- }
4068
-
4069
-
4070
- await Promise.all( userList.map( async ( email ) => {
4071
- let stores = [];
4072
- let userDetails = await userService.findOne( { email: email } );
4073
- if ( userDetails ) {
4074
- if ( userDetails.userType === 'client' && userDetails.role !== 'superadmin' ) {
4075
- let storeIds = new Set( userDetails.assignedStores.map( ( store ) => store.storeId ) );
4076
-
4077
- // Fetch clusters and teams in parallel
4078
- const [ clustersList, teamsList ] = await Promise.all( [
4079
- clusterServices.findcluster( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: userDetails.email } } } ),
4080
- teamsServices.findteams( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: userDetails.email } } } ),
4081
- ] );
4082
-
4083
- // Process clusters
4084
- if ( clustersList.length > 0 ) {
4085
- for ( let cluster of clustersList ) {
4086
- cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
4087
- }
4088
- }
4089
-
4090
- // Process teams
4091
- if ( teamsList.length > 0 ) {
4092
- for ( let team of teamsList ) {
4093
- for ( let user of team.users ) {
4094
- let findUser = await userService.findOne( { _id: user.userId } );
4095
- if ( findUser && findUser.assignedStores?.length > 0 ) {
4096
- findUser.assignedStores.forEach( ( store ) => storeIds.add( store.storeId ) );
4097
- }
4098
-
4099
- // Fetch clusters for the user
4100
- let userClustersList = await clusterServices.findcluster( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: findUser.email } } } );
4101
- if ( userClustersList.length > 0 ) {
4102
- for ( let cluster of userClustersList ) {
4103
- cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
4104
- }
4105
- }
4106
- }
4107
- }
4108
- }
4109
- let TeamMember = await teamsServices.findteams( { clientId: userDetails.clientId, users: { $elemMatch: { email: userDetails.email } } } );
4110
- if ( TeamMember&&TeamMember.length>0 ) {
4111
- for ( let team of TeamMember ) {
4112
- let clusterList = await clusterServices.findcluster( { clientId: userDetails.clientId, teams: { $elemMatch: { name: team.teamName } } } );
4113
- if ( clusterList.length > 0 ) {
4114
- for ( let cluster of clusterList ) {
4115
- cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
4116
- }
4117
- }
4118
- }
4119
- }
4120
- let TeamLeader = await teamsServices.findteams( { clientId: userDetails.clientId, Teamlead: { $elemMatch: { email: userDetails.email } } } );
4121
- if ( TeamLeader&&TeamLeader.length>0 ) {
4122
- for ( let team of TeamLeader ) {
4123
- let clusterList = await clusterServices.findcluster( { clientId: userDetails.clientId, teams: { $elemMatch: { name: team.teamName } } } );
4124
- if ( clusterList.length > 0 ) {
4125
- for ( let cluster of clusterList ) {
4126
- cluster.stores.forEach( ( store ) => storeIds.add( store.storeId ) );
4127
- }
4128
- }
4129
- }
4130
- }
4131
- stores = Array.from( storeIds );
4132
- }
4133
- let data = {
4134
- email: email,
4135
- stores,
4136
- role: userDetails.role,
4137
- };
4138
- result.push( data );
4139
- }
4140
- } ) );
4141
-
4142
- spocList.forEach( ( ele ) => {
4143
- let findIndex = result.findIndex( ( element ) => element.email == ele.email );
4144
- if ( findIndex != -1 ) {
4145
- result[findIndex].stores = new Set( ...result[findIndex].stores, ele.store );
4146
- } else {
4147
- result.push( {
4148
- email: ele.email,
4149
- stores: ele.store,
4150
- role: 'admin',
4151
- } );
4152
- }
4153
- } );
4154
- }
4155
-
4156
- return res.sendSuccess( result );
4157
- } catch ( e ) {
4158
- logger.error( { functionName: 'sendAIEmailList', error: e } );
4159
- return res.sendError( e, 500 );
4160
- }
4161
- }
@@ -3822,7 +3822,6 @@ export async function taskQuestionList( req, res ) {
3822
3822
  rawImageUpload: { $ifNull: [ '$rawImageUpload', false ] },
3823
3823
  rawVideoUpload: { $ifNull: [ '$rawVideoUpload', false ] },
3824
3824
  videoUploadTimeLimit: { $ifNull: [ '$videoUploadTimeLimit', 0 ] },
3825
- aiType: 1,
3826
3825
  },
3827
3826
  } );
3828
3827
 
@@ -608,8 +608,6 @@ export const zoneList = async ( req, res ) => {
608
608
  'Mobile usage detection',
609
609
  'Store TV Compliance',
610
610
  'Queue Wait Time Breach',
611
- 'Uniform detection',
612
- 'Store Hygiene Monitoring',
613
611
  ];
614
612
  const allowedChecklistsStreams = [
615
613
  'Camera Angle Change Compliance',
@@ -5165,14 +5163,3 @@ export async function downloadQuestionTemplate( req, res ) {
5165
5163
  return res.sendError( e, 500 );
5166
5164
  }
5167
5165
  }
5168
-
5169
-
5170
- export async function getTeamList( req, res ) {
5171
- try {
5172
- let teamsDetails = await teamsServices.findteams( { clientId: req.query.clientId, status: 'active' } );
5173
- return res.sendSuccess( teamsDetails );
5174
- } catch ( e ) {
5175
- logger.error( { functionName: 'getTeamList', error: e } );
5176
- return res.sendError( e, 500 );
5177
- }
5178
- }
@@ -1,244 +1,244 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8"/>
5
- <meta name="viewport" content="width=device-width,initial-scale=1"/>
6
- <title>{{checklistTitle}}</title>
7
- <style>
8
- *{box-sizing:border-box;margin:0;padding:0}
9
- body{font-family:Arial,Helvetica,sans-serif;background:#fff;padding:0}
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
- /* Cover — match brand PDF (cyan title, right geometry, grey footer rule) */
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;bottom:0;left:auto;width:450px;pointer-events:none;z-index:0;}
15
- .cover-brand{position:absolute;top:48px;left:48px;display:flex;align-items:center;gap:10px;z-index:1}
16
- .cover-brand-name{font-size:22px;font-weight:600;color:#1a1a1a;letter-spacing:.02em;text-transform:lowercase}
17
- .cover-title-block{position:absolute;top:188px;left:48px;max-width:400px;z-index:1}
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
- .cover-meta-block{position:absolute;top:418px;left:48px;z-index:1}
20
- .cover-ref{font-size:22px;font-weight:700;color:#1a1a1a;letter-spacing:.02em}
21
- .cover-datetime{font-size:15px;color:#1a1a1a;font-weight:400;margin-top:10px}
22
- .cover-summary{position:absolute;top:548px;left:48px;width:400px;display:flex;flex-direction:column;gap:16px;z-index:1}
23
- .cover-sum-row{display:grid;grid-template-columns:182px 14px 1fr;column-gap:4px;align-items:baseline;font-size:16px;line-height:1.35}
24
- .cover-sum-label{color:#1a1a1a;font-weight:400;text-align:left}
25
- .cover-sum-colon{color:#1a1a1a;font-weight:400;text-align:center}
26
- .cover-sum-val{color:#000;font-weight:700}
27
- .cover-footer{position:absolute;bottom:36px;left:48px;right:48px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid #d9d9d9;padding-top:14px;z-index:1}
28
- .cover-footer-page{font-size:11px;color:#999}
29
- .cover-footer-gen{display:flex;align-items:center;gap:8px;font-size:12px;color:#00AEEF;font-weight:600}
30
- /* Score page */
31
- .score-page{margin-bottom:40px;padding-bottom:20px;border-bottom:1px solid #e0e0e0}
32
- .score-hero{text-align:center;margin-bottom:36px}
33
- .score-pct{font-size:72px;font-weight:700;color:#00AEEF;line-height:1}
34
- .score-sub{font-size:18px;color:#444;margin-top:8px}
35
- .score-date{font-size:14px;color:#888;margin-top:4px}
36
- .section-title{font-size:16px;font-weight:700;color:#1a1a2e;margin-bottom:30px;padding-bottom:10px;border-bottom:2px solid #00AEEF}
37
- .history-bars{display:flex;align-items:flex-end;gap:8px;height:160px;margin-bottom:36px}
38
- .bar-wrap{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px}
39
- .bar{width:100%;background:#00AEEF;border-radius:4px 4px 0 0;display:flex;align-items:flex-start;justify-content:center}
40
- .bar-pct{font-size:10px;color:#fff;padding-top:4px;font-weight:600}
41
- .bar-date{font-size:9px;color:#888;text-align:center;white-space:nowrap}
42
- .bar-axis{width:100%;height:1px;background:#ddd;margin-bottom:8px}
43
- /* Section wise table */
44
- .sw-table{width:100%;border-collapse:collapse;font-size:14px}
45
- .sw-table th{background:#f0f7ff;color:#1a1a2e;font-weight:600;padding:10px 14px;text-align:left}
46
- .sw-table td{padding:10px 14px;border-bottom:1px solid #f0f0f0;color:#444}
47
- .sw-table tr:last-child td{border-bottom:none}
48
- .pct-pill{display:inline-block;padding:2px 10px;border-radius:20px;font-size:12px;font-weight:600}
49
- .pct-hi{background:#e1f5ee;color:#0f6e56}
50
- .pct-mid{background:#faeeda;color:#854f0b}
51
- .pct-lo{background:#fcebeb;color:#a32d2d}
52
- /* Detail pages */
53
- .detail-page{padding:32px 40px}
54
- .q-row{break-inside:auto}
55
- .q-answer-item{break-inside:auto}
56
- .dp-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:12px;border-bottom:2px solid #00AEEF}
57
- .dp-header h2{font-size:18px;font-weight:700;color:#1a1a2e}
58
- .dp-score{font-size:15px;font-weight:700;color:#000000}
59
- .q-row{display:flex;gap:12px;margin-bottom:14px;padding:12px;border-radius:8px;background:transparent}
60
- .q-num{font-size:12px;font-weight:700;color:#000;min-width:22px}
61
- .q-body{flex:1}
62
- .q-text{font-size:13px;color:#444;margin-bottom:4px}
63
- .q-ans{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;padding:2px 8px;border-radius:12px}
64
- .ans-yes{background:#e1f5ee;color:#0f6e56}
65
- .ans-no{background:#fcebeb;color:#a32d2d}
66
- .flag-badge{background:#faeeda;color:#854f0b;font-size:10px;font-weight:600;padding:1px 6px;border-radius:8px;margin-left:6px}
67
- .q-answer-list{margin-top:6px}
68
- .q-answer-item{margin-top:6px;padding:0;background:transparent;border:none;border-radius:0}
69
- .q-answer-text{font-size:12px;color:#1a1a1a;line-height:1.5}
70
- .q-answer-text.flagged{color:#a32d2d}
71
- .q-answer-media{margin-top:8px}
72
- .q-answer-media img,.q-answer-media video{display:block;max-width:220px;max-height:180px;border-radius:6px}
73
- .q-answer-link{font-size:12px;color:#0085D2;text-decoration:underline;word-break:break-all}
74
- .q-answer-caption{font-size:11px;color:#666;margin-bottom:4px}
75
- .q-answer-remarks{font-size:11px;color:#666;margin-top:6px}
76
- /* Footer */
77
- .page-footer{position:absolute;bottom:20px;left:40px;right:40px;display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#999;border-top:1px solid #d9d9d9;padding-top:10px}
78
- .footer-brand{display:flex;align-items:center;gap:8px;font-weight:600;color:#0066CC;font-size:11px}
79
- .footer-gen-by{color:#666;font-weight:400}
80
- .cover-footer-gen .footer-gen-by{color:#1a1a1a}
81
- .footer-tangoye-logo{display:block;flex-shrink:0}
82
- @page{size:A4;margin:5mm 0mm 15mm 0mm}
83
- @page:first{margin:0}
84
- </style>
85
- </head>
86
- <body>
87
-
88
- {{!-- PAGE 1: COVER --}}
89
- <div class="page cover-wrap">
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" />
96
- </svg>
97
-
98
- <div class="cover-brand">
99
- <img src="{{brandLogo}}" width="100px" height="100px" />
100
- </div>
101
-
102
- <div class="cover-title-block">
103
- <div class="cover-title-line1">{{#if titleLine1}}{{titleLine1}}{{else}}{{checklistType}}{{/if}}</div>
104
- </div>
105
-
106
- <div class="cover-meta-block">
107
- <div class="cover-ref">{{referenceId}}</div>
108
- <div class="cover-datetime">{{date}} | {{time}}</div>
109
- </div>
110
-
111
- <div class="cover-summary">
112
- <div class="cover-sum-row"><span class="cover-sum-label">No. of questions</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{numQuestions}}</span></div>
113
- <div class="cover-sum-row"><span class="cover-sum-label">No. of flags</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{numFlags}}</span></div>
114
- <div class="cover-sum-row"><span class="cover-sum-label">AI Breached</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{aiBreached}}</span></div>
115
- <div class="cover-sum-row"><span class="cover-sum-label">Submitted By</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{submittedBy}}</span></div>
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>
117
- </div>
118
-
119
- {{!-- <div class="cover-footer">
120
- <span class="cover-footer-page"></span>
121
- <div class="cover-footer-gen">
122
- <span class="footer-gen-by">Generated by</span>
123
- {{> tangoyeFooterLogo gid="cover"}}
124
- </div>
125
- </div> --}}
126
- </div>
127
-
128
- {{!-- SCORE SUMMARY + DETAIL (continuous flow) --}}
129
- <div class="detail-page">
130
- {{#if hasCompliancePage}}
131
- <div class="score-page">
132
- <div class="score-hero">
133
- <div class="score-pct">{{totalPercentage}}%</div>
134
- <div class="score-sub">Total: <strong>{{totalScore}}</strong> out of <strong>{{maxScore}}</strong></div>
135
- <div class="score-date">{{reportDate}}</div>
136
- </div>
137
-
138
- {{#if historyData}}
139
- <div class="section-title">History — Last 7 Days</div>
140
- <div style="display:flex;flex-direction:column;margin-bottom:36px">
141
- <div class="history-bars">
142
- {{#each historyData}}
143
- <div class="bar-wrap">
144
- <div class="bar" style="height:{{this.barHeight}}px"><span class="bar-pct">{{this.value}}%</span></div>
145
- <span class="bar-date">{{this.date}}</span>
146
- </div>
147
- {{/each}}
148
- </div>
149
- <div class="bar-axis"></div>
150
- </div>
151
- {{/if}}
152
-
153
- <div class="section-title">Section Wise Insights</div>
154
- <table class="sw-table">
155
- <thead><tr><th>Sections</th><th>Target Score</th><th>Actual Scrore</th><th>%</th></tr></thead>
156
- <tbody>
157
- {{#each sectionInsights}}
158
- <tr>
159
- <td>{{this.sectionName}}</td>
160
- <td>{{this.targetScore}}</td>
161
- <td>{{this.actualScore}}</td>
162
- <td>{{this.percentage}}%</td>
163
- </tr>
164
- {{/each}}
165
- </tbody>
166
- </table>
167
- </div>
168
- {{/if}}
169
- {{#each sections}}
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>
171
- <div class="sec-questions">
172
- {{#each this.questions}}
173
- <div class="q-row">
174
- <span class="q-num">{{this.qno}}</span>
175
- <div class="q-body">
176
- <div class="q-text" style="display:flex;justify-content:space-between">
177
- <div>{{this.qname}}</div>
178
- {{#if this.compliance}}
179
- <div>Score:{{this.score}}</div>
180
- {{/if}}
181
- </div>
182
- {{!-- <span class="q-ans {{#if this.isYes}}ans-yes{{else}}{{#if this.isNo}}ans-no{{/if}}{{/if}}">{{#if this.isYes}}✓ Yes{{else}}{{#if this.isNo}}✗ No{{else}}{{this.answerDisplay}}{{/if}}{{/if}}</span> --}}
183
- {{#if this.userAnswer.length}}
184
- <div class="q-answer-list">
185
- {{#each this.userAnswer}}
186
- <div class="q-answer-item">
187
- {{#if this.answer}}
188
- {{#eq this.answerType 'image'}}
189
- <div class="q-answer-media">
190
- {{#if this.referenceImage}}
191
- <div class="q-answer-caption">Reference Image</div>
192
- <img src="{{this.referenceImage}}" alt="Reference Image" />
193
- {{/if}}
194
- <div class="q-answer-caption">Uploaded Image</div>
195
- <img src="{{this.answer}}" alt="Uploaded Image" />
196
- </div>
197
- {{/eq}}
198
- {{#eq this.answerType 'video'}}
199
- <div class="q-answer-media">
200
- <div class="q-answer-caption">Uploaded Video</div>
201
- <a class="q-answer-link" href="{{this.answer}}" target="_blank">{{this.answer}}</a>
202
- </div>
203
- {{/eq}}
204
- {{#eq this.answerType 'text'}}
205
- <div class="q-answer-text {{#if this.sopFlag}}flagged{{/if}}">{{this.answer}}</div>
206
- {{/eq}}
207
- {{/if}}
208
-
209
- {{#if this.validation}}
210
- {{#if this.validationAnswer}}
211
- {{#eq this.validationDisplayType 'image'}}
212
- <div class="q-answer-media">
213
- <div class="q-answer-caption">Validation Image</div>
214
- <img src="{{this.validationAnswer}}" alt="Validation Image" />
215
- </div>
216
- {{/eq}}
217
- {{#eq this.validationDisplayType 'video'}}
218
- <div class="q-answer-media">
219
- <div class="q-answer-caption">Validation Video</div>
220
- <a class="q-answer-link" href="{{this.validationAnswer}}" target="_blank">{{this.validationAnswer}}</a>
221
- </div>
222
- {{/eq}}
223
- {{#eq this.validationDisplayType 'text'}}
224
- <div class="q-answer-text">{{this.validationAnswer}}</div>
225
- {{/eq}}
226
- {{/if}}
227
- {{/if}}
228
-
229
- {{#if this.remarks}}
230
- <div class="q-answer-remarks">Remarks: {{this.remarks}}</div>
231
- {{/if}}
232
- </div>
233
- {{/each}}
234
- </div>
235
- {{/if}}
236
- </div>
237
- </div>
238
- {{/each}}
239
- </div>
240
- {{/each}}
241
- </div>
242
-
243
- </body>
244
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
6
+ <title>{{checklistTitle}}</title>
7
+ <style>
8
+ *{box-sizing:border-box;margin:0;padding:0}
9
+ body{font-family:Arial,Helvetica,sans-serif;background:#fff;padding:0}
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
+ /* Cover — match brand PDF (cyan title, right geometry, grey footer rule) */
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;bottom:0;left:auto;width:450px;pointer-events:none;z-index:0;}
15
+ .cover-brand{position:absolute;top:48px;left:48px;display:flex;align-items:center;gap:10px;z-index:1}
16
+ .cover-brand-name{font-size:22px;font-weight:600;color:#1a1a1a;letter-spacing:.02em;text-transform:lowercase}
17
+ .cover-title-block{position:absolute;top:188px;left:48px;max-width:400px;z-index:1}
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
+ .cover-meta-block{position:absolute;top:418px;left:48px;z-index:1}
20
+ .cover-ref{font-size:22px;font-weight:700;color:#1a1a1a;letter-spacing:.02em}
21
+ .cover-datetime{font-size:15px;color:#1a1a1a;font-weight:400;margin-top:10px}
22
+ .cover-summary{position:absolute;top:548px;left:48px;width:400px;display:flex;flex-direction:column;gap:16px;z-index:1}
23
+ .cover-sum-row{display:grid;grid-template-columns:182px 14px 1fr;column-gap:4px;align-items:baseline;font-size:16px;line-height:1.35}
24
+ .cover-sum-label{color:#1a1a1a;font-weight:400;text-align:left}
25
+ .cover-sum-colon{color:#1a1a1a;font-weight:400;text-align:center}
26
+ .cover-sum-val{color:#000;font-weight:700}
27
+ .cover-footer{position:absolute;bottom:36px;left:48px;right:48px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid #d9d9d9;padding-top:14px;z-index:1}
28
+ .cover-footer-page{font-size:11px;color:#999}
29
+ .cover-footer-gen{display:flex;align-items:center;gap:8px;font-size:12px;color:#00AEEF;font-weight:600}
30
+ /* Score page */
31
+ .score-page{margin-bottom:40px;padding-bottom:20px;border-bottom:1px solid #e0e0e0}
32
+ .score-hero{text-align:center;margin-bottom:36px}
33
+ .score-pct{font-size:72px;font-weight:700;color:#00AEEF;line-height:1}
34
+ .score-sub{font-size:18px;color:#444;margin-top:8px}
35
+ .score-date{font-size:14px;color:#888;margin-top:4px}
36
+ .section-title{font-size:16px;font-weight:700;color:#1a1a2e;margin-bottom:30px;padding-bottom:10px;border-bottom:2px solid #00AEEF}
37
+ .history-bars{display:flex;align-items:flex-end;gap:8px;height:160px;margin-bottom:36px}
38
+ .bar-wrap{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px}
39
+ .bar{width:100%;background:#00AEEF;border-radius:4px 4px 0 0;display:flex;align-items:flex-start;justify-content:center}
40
+ .bar-pct{font-size:10px;color:#fff;padding-top:4px;font-weight:600}
41
+ .bar-date{font-size:9px;color:#888;text-align:center;white-space:nowrap}
42
+ .bar-axis{width:100%;height:1px;background:#ddd;margin-bottom:8px}
43
+ /* Section wise table */
44
+ .sw-table{width:100%;border-collapse:collapse;font-size:14px}
45
+ .sw-table th{background:#f0f7ff;color:#1a1a2e;font-weight:600;padding:10px 14px;text-align:left}
46
+ .sw-table td{padding:10px 14px;border-bottom:1px solid #f0f0f0;color:#444}
47
+ .sw-table tr:last-child td{border-bottom:none}
48
+ .pct-pill{display:inline-block;padding:2px 10px;border-radius:20px;font-size:12px;font-weight:600}
49
+ .pct-hi{background:#e1f5ee;color:#0f6e56}
50
+ .pct-mid{background:#faeeda;color:#854f0b}
51
+ .pct-lo{background:#fcebeb;color:#a32d2d}
52
+ /* Detail pages */
53
+ .detail-page{padding:32px 40px}
54
+ .q-row{break-inside:auto}
55
+ .q-answer-item{break-inside:auto}
56
+ .dp-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:12px;border-bottom:2px solid #00AEEF}
57
+ .dp-header h2{font-size:18px;font-weight:700;color:#1a1a2e}
58
+ .dp-score{font-size:15px;font-weight:700;color:#000000}
59
+ .q-row{display:flex;gap:12px;margin-bottom:14px;padding:12px;border-radius:8px;background:transparent}
60
+ .q-num{font-size:12px;font-weight:700;color:#000;min-width:22px}
61
+ .q-body{flex:1}
62
+ .q-text{font-size:13px;color:#444;margin-bottom:4px}
63
+ .q-ans{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;padding:2px 8px;border-radius:12px}
64
+ .ans-yes{background:#e1f5ee;color:#0f6e56}
65
+ .ans-no{background:#fcebeb;color:#a32d2d}
66
+ .flag-badge{background:#faeeda;color:#854f0b;font-size:10px;font-weight:600;padding:1px 6px;border-radius:8px;margin-left:6px}
67
+ .q-answer-list{margin-top:6px}
68
+ .q-answer-item{margin-top:6px;padding:0;background:transparent;border:none;border-radius:0}
69
+ .q-answer-text{font-size:12px;color:#1a1a1a;line-height:1.5}
70
+ .q-answer-text.flagged{color:#a32d2d}
71
+ .q-answer-media{margin-top:8px}
72
+ .q-answer-media img,.q-answer-media video{display:block;max-width:220px;max-height:180px;border-radius:6px}
73
+ .q-answer-link{font-size:12px;color:#0085D2;text-decoration:underline;word-break:break-all}
74
+ .q-answer-caption{font-size:11px;color:#666;margin-bottom:4px}
75
+ .q-answer-remarks{font-size:11px;color:#666;margin-top:6px}
76
+ /* Footer */
77
+ .page-footer{position:absolute;bottom:20px;left:40px;right:40px;display:flex;justify-content:space-between;align-items:center;font-size:11px;color:#999;border-top:1px solid #d9d9d9;padding-top:10px}
78
+ .footer-brand{display:flex;align-items:center;gap:8px;font-weight:600;color:#0066CC;font-size:11px}
79
+ .footer-gen-by{color:#666;font-weight:400}
80
+ .cover-footer-gen .footer-gen-by{color:#1a1a1a}
81
+ .footer-tangoye-logo{display:block;flex-shrink:0}
82
+ @page{size:A4;margin:5mm 0mm 15mm 0mm}
83
+ @page:first{margin:0}
84
+ </style>
85
+ </head>
86
+ <body>
87
+
88
+ {{!-- PAGE 1: COVER --}}
89
+ <div class="page cover-wrap">
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" />
96
+ </svg>
97
+
98
+ <div class="cover-brand">
99
+ <img src="{{brandLogo}}" width="100px" height="100px" />
100
+ </div>
101
+
102
+ <div class="cover-title-block">
103
+ <div class="cover-title-line1">{{#if titleLine1}}{{titleLine1}}{{else}}{{checklistType}}{{/if}}</div>
104
+ </div>
105
+
106
+ <div class="cover-meta-block">
107
+ <div class="cover-ref">{{referenceId}}</div>
108
+ <div class="cover-datetime">{{date}} | {{time}}</div>
109
+ </div>
110
+
111
+ <div class="cover-summary">
112
+ <div class="cover-sum-row"><span class="cover-sum-label">No. of questions</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{numQuestions}}</span></div>
113
+ <div class="cover-sum-row"><span class="cover-sum-label">No. of flags</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{numFlags}}</span></div>
114
+ <div class="cover-sum-row"><span class="cover-sum-label">AI Breached</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{aiBreached}}</span></div>
115
+ <div class="cover-sum-row"><span class="cover-sum-label">Submitted By</span><span class="cover-sum-colon">:</span><span class="cover-sum-val">{{submittedBy}}</span></div>
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>
117
+ </div>
118
+
119
+ {{!-- <div class="cover-footer">
120
+ <span class="cover-footer-page"></span>
121
+ <div class="cover-footer-gen">
122
+ <span class="footer-gen-by">Generated by</span>
123
+ {{> tangoyeFooterLogo gid="cover"}}
124
+ </div>
125
+ </div> --}}
126
+ </div>
127
+
128
+ {{!-- SCORE SUMMARY + DETAIL (continuous flow) --}}
129
+ <div class="detail-page">
130
+ {{#if hasCompliancePage}}
131
+ <div class="score-page">
132
+ <div class="score-hero">
133
+ <div class="score-pct">{{totalPercentage}}%</div>
134
+ <div class="score-sub">Total: <strong>{{totalScore}}</strong> out of <strong>{{maxScore}}</strong></div>
135
+ <div class="score-date">{{reportDate}}</div>
136
+ </div>
137
+
138
+ {{#if historyData}}
139
+ <div class="section-title">History — Last 7 Days</div>
140
+ <div style="display:flex;flex-direction:column;margin-bottom:36px">
141
+ <div class="history-bars">
142
+ {{#each historyData}}
143
+ <div class="bar-wrap">
144
+ <div class="bar" style="height:{{this.barHeight}}px"><span class="bar-pct">{{this.value}}%</span></div>
145
+ <span class="bar-date">{{this.date}}</span>
146
+ </div>
147
+ {{/each}}
148
+ </div>
149
+ <div class="bar-axis"></div>
150
+ </div>
151
+ {{/if}}
152
+
153
+ <div class="section-title">Section Wise Insights</div>
154
+ <table class="sw-table">
155
+ <thead><tr><th>Sections</th><th>Target Score</th><th>Actual Scrore</th><th>%</th></tr></thead>
156
+ <tbody>
157
+ {{#each sectionInsights}}
158
+ <tr>
159
+ <td>{{this.sectionName}}</td>
160
+ <td>{{this.targetScore}}</td>
161
+ <td>{{this.actualScore}}</td>
162
+ <td>{{this.percentage}}%</td>
163
+ </tr>
164
+ {{/each}}
165
+ </tbody>
166
+ </table>
167
+ </div>
168
+ {{/if}}
169
+ {{#each sections}}
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>
171
+ <div class="sec-questions">
172
+ {{#each this.questions}}
173
+ <div class="q-row">
174
+ <span class="q-num">{{this.qno}}</span>
175
+ <div class="q-body">
176
+ <div class="q-text" style="display:flex;justify-content:space-between">
177
+ <div>{{this.qname}}</div>
178
+ {{#if this.compliance}}
179
+ <div>Score:{{this.score}}</div>
180
+ {{/if}}
181
+ </div>
182
+ {{!-- <span class="q-ans {{#if this.isYes}}ans-yes{{else}}{{#if this.isNo}}ans-no{{/if}}{{/if}}">{{#if this.isYes}}✓ Yes{{else}}{{#if this.isNo}}✗ No{{else}}{{this.answerDisplay}}{{/if}}{{/if}}</span> --}}
183
+ {{#if this.userAnswer.length}}
184
+ <div class="q-answer-list">
185
+ {{#each this.userAnswer}}
186
+ <div class="q-answer-item">
187
+ {{#if this.answer}}
188
+ {{#eq this.answerType 'image'}}
189
+ <div class="q-answer-media">
190
+ {{#if this.referenceImage}}
191
+ <div class="q-answer-caption">Reference Image</div>
192
+ <img src="{{this.referenceImage}}" alt="Reference Image" />
193
+ {{/if}}
194
+ <div class="q-answer-caption">Uploaded Image</div>
195
+ <img src="{{this.answer}}" alt="Uploaded Image" />
196
+ </div>
197
+ {{/eq}}
198
+ {{#eq this.answerType 'video'}}
199
+ <div class="q-answer-media">
200
+ <div class="q-answer-caption">Uploaded Video</div>
201
+ <a class="q-answer-link" href="{{this.answer}}" target="_blank">{{this.answer}}</a>
202
+ </div>
203
+ {{/eq}}
204
+ {{#eq this.answerType 'text'}}
205
+ <div class="q-answer-text {{#if this.sopFlag}}flagged{{/if}}">{{this.answer}}</div>
206
+ {{/eq}}
207
+ {{/if}}
208
+
209
+ {{#if this.validation}}
210
+ {{#if this.validationAnswer}}
211
+ {{#eq this.validationDisplayType 'image'}}
212
+ <div class="q-answer-media">
213
+ <div class="q-answer-caption">Validation Image</div>
214
+ <img src="{{this.validationAnswer}}" alt="Validation Image" />
215
+ </div>
216
+ {{/eq}}
217
+ {{#eq this.validationDisplayType 'video'}}
218
+ <div class="q-answer-media">
219
+ <div class="q-answer-caption">Validation Video</div>
220
+ <a class="q-answer-link" href="{{this.validationAnswer}}" target="_blank">{{this.validationAnswer}}</a>
221
+ </div>
222
+ {{/eq}}
223
+ {{#eq this.validationDisplayType 'text'}}
224
+ <div class="q-answer-text">{{this.validationAnswer}}</div>
225
+ {{/eq}}
226
+ {{/if}}
227
+ {{/if}}
228
+
229
+ {{#if this.remarks}}
230
+ <div class="q-answer-remarks">Remarks: {{this.remarks}}</div>
231
+ {{/if}}
232
+ </div>
233
+ {{/each}}
234
+ </div>
235
+ {{/if}}
236
+ </div>
237
+ </div>
238
+ {{/each}}
239
+ </div>
240
+ {{/each}}
241
+ </div>
242
+
243
+ </body>
244
+ </html>
@@ -39,8 +39,6 @@ internalTraxRouter
39
39
  .post( '/runAIFlag', isAllowedInternalAPIHandler, internalController.runAIFlag )
40
40
  .post( '/downloadInsertPdf', isAllowedInternalAPIHandler, internalController.downloadInsertPdf )
41
41
  .get( '/checklistAutoMailList', isAllowedInternalAPIHandler, internalController.checklistAutoMailList )
42
- .post( '/sendAIEmailList', isAllowedInternalAPIHandler, internalController.sendAIEmailList )
43
- .post( '/liveAiPushNotificationAlert', isAllowedInternalAPIHandler, internalController.liveAiPushNotificationAlert )
44
42
  ;
45
43
 
46
44
 
@@ -36,8 +36,6 @@ traxRouter
36
36
  .post( '/updateRunAIRequest', isAllowedSessionHandler, validate( runAIRequestValidation ), traxController.updateRunAIRequest )
37
37
  .post( '/createChecklistName', isAllowedSessionHandler, validate( createChecklistNameValidation ), traxController.createChecklistName )
38
38
  .post( '/updateOSProcessedData', isAllowedInternalAPIHandler, validate( updateOSDataValidation ), traxController.updateOSProcessedData )
39
- .post( '/exportQuestions', isAllowedSessionHandler, traxController.downloadQuestionTemplate )
40
- .get( '/getTeamList', isAllowedSessionHandler, traxController.getTeamList )
41
- ;
39
+ .post( '/exportQuestions', isAllowedSessionHandler, traxController.downloadQuestionTemplate );
42
40
 
43
41
  // isAllowedSessionHandler, isAllowedClient, accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [ ] } ] } ),
@@ -447,6 +447,7 @@ export function buildVisitChecklistTemplateDataFromProcessed( processedDoc, bran
447
447
 
448
448
  let referenceId = doc.coverage == 'store' ? doc?.storeName : doc?.userName;
449
449
 
450
+
450
451
  return {
451
452
 
452
453