Subversion Repositories SmartDukaan

Rev

Rev 36836 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
36645 vikas 1
<section class="wrapper">
2
    <div class="row">
3
        <div class="col-lg-12">
4
            <h3 class="page-header"><i class="icon_document_alt"></i> $username - Visit Details ($reportDate)</h3>
5
            <ol class="breadcrumb">
6
                <li><i class="fa fa-home"></i><a href="${rc.contextPath}/dashboard">Home</a></li>
7
                <li><a href="javascript:void(0);" class="beat-report">Beat Report</a></li>
8
                <li><i class="fa fa-user"></i>$username</li>
9
            </ol>
10
        </div>
11
    </div>
12
    <br>
13
    <div class="row col-lg-12" style="float: right;">
36664 vikas 14
        <div class="col-lg-2">
15
            <label>Level</label>
16
            <select id="user-detail-level" class="form-control" data-selected="$!userLevel">
17
                <option value="">All Levels</option>
18
                #foreach($et in $escalationTypes)
19
                    <option value="$et" #if($et.name() == $userLevel)selected#end>$et</option>
20
                #end
21
            </select>
22
        </div>
36645 vikas 23
        <div class="col-lg-3">
36664 vikas 24
            <label>User</label>
25
            <select id="user-detail-user" class="form-control" data-selected-auth="$!authUserId">
26
                <option value="$!authUserId">$username</option>
27
            </select>
28
        </div>
29
        <div class="col-lg-3">
36645 vikas 30
            <label>Select Date</label>
31
            <div class="input-group">
32
                <input type="date" id="user-detail-date" class="form-control" value="$reportDate" data-userid="$userId">
33
                <span class="input-group-btn">
34
                    <button type="button" class="btn btn-info user-detail-refresh" data-userid="$userId" style="margin-left:10px;">
35
                        <i class="fa fa-refresh"></i> Refresh
36
                    </button>
36664 vikas 37
                    <button type="button" class="btn btn-success live-tracking-btn" data-userid="$userId" data-username="$username" style="margin-left:5px;">
38
                        <i class="fa fa-map-marker"></i> Live Tracking
39
                    </button>
36645 vikas 40
                </span>
41
            </div>
42
        </div>
43
    </div>
44
    <br><br><br>
45
 
46
    <div class="col-lg-12">
47
        <div class="row" style="margin-bottom:15px;">
48
            <div class="col-lg-3">
49
                <div class="panel panel-default" style="text-align:center; padding:10px;">
50
                    <h4 style="margin:0; color:#1abc9c; font-weight:bold;">$visits.size()</h4>
51
                    <small>Total Visits</small>
52
                </div>
53
            </div>
54
            #set($completedCount = 0)
55
            #set($pendingCount = 0)
56
            #set($deferredCount = 0)
57
            #foreach($v in $visits)
36783 vikas 58
                #if($v.getMarkType() == "CHECKIN-CHECKOUT" || $v.getMarkType() == "CHECKIN_CHECKOUT" || $v.getMarkType() == "CHECKOUT")
36645 vikas 59
                    #set($completedCount = $completedCount + 1)
60
                #elseif($v.getMarkType() == "PENDING" || $v.getMarkType() == "CHECKIN")
61
                    #set($pendingCount = $pendingCount + 1)
62
                #elseif($v.getMarkType() == "DEFERRED")
63
                    #set($deferredCount = $deferredCount + 1)
64
                #end
65
            #end
66
            <div class="col-lg-3">
67
                <div class="panel panel-default" style="text-align:center; padding:10px;">
68
                    <h4 style="margin:0; color:#27ae60; font-weight:bold;">$completedCount</h4>
69
                    <small>Completed</small>
70
                </div>
71
            </div>
72
            <div class="col-lg-3">
73
                <div class="panel panel-default" style="text-align:center; padding:10px;">
74
                    <h4 style="margin:0; color:#e67e22; font-weight:bold;">$pendingCount</h4>
75
                    <small>Pending</small>
76
                </div>
77
            </div>
78
            <div class="col-lg-3">
79
                <div class="panel panel-default" style="text-align:center; padding:10px;">
80
                    <h4 style="margin:0; color:#e74c3c; font-weight:bold;">$deferredCount</h4>
81
                    <small>Deferred</small>
82
                </div>
83
            </div>
84
        </div>
85
 
86
        <table class="table table-border table-condensed table-bordered table-striped" id="userDetailTable" style="width:100%">
87
            <thead>
36656 vikas 88
            <tr >
36645 vikas 89
                <th>#</th>
36836 ranu 90
                <th>Agenda</th>
91
                <th>Partner Name</th>
36730 vikas 92
                <th>Description</th>
36645 vikas 93
                <th>Status</th>
94
                <th>Address</th>
95
                <th>Check In Time</th>
96
                <th>Check Out Time</th>
36666 vikas 97
                <th>Time Spent at Outlet</th>
36645 vikas 98
                <th>Transit Time</th>
99
                <th>Distance (km)</th>
36666 vikas 100
                <th>Estimated Time</th>
36645 vikas 101
            </tr>
102
            </thead>
103
            <tbody>
36656 vikas 104
            #if($punchIn)
105
                <tr style="background:#d5f5e3;">
106
                    <td>--</td>
36836 ranu 107
                    <td>--</td>
36656 vikas 108
                    <td><strong>PUNCH IN</strong></td>
36730 vikas 109
                    <td>$!punchIn.getTaskDescription()</td>
36717 vikas 110
                    <td><span class="label label-info">PUNCHIN</span> #if($punchIn.getAttachment() && $punchIn.getAttachment() != "")<a href="javascript:void(0);" class="view-attachment-link" data-attachment="$punchIn.getAttachment()" data-checkin-latlng="$!punchIn.getCheckInLatLng()" data-visit-location="$!punchIn.getVisitLocation()" data-tracking-id="$punchIn.getId()" title="View Attachment"><i class="fa fa-paperclip" style="color:#27ae60; margin-left:5px;"></i></a>#end</td>
36656 vikas 111
                    <td>$!punchIn.getAddress()</td>
112
                    <td>$!punchIn.getCheckInTime()</td>
113
                    <td>--</td>
114
                    <td>--</td>
115
                    <td>--</td>
116
                    <td>$!punchIn.getTotalDistance()</td>
36666 vikas 117
                    <td>$!punchIn.getEstimatedTime()</td>
36656 vikas 118
                </tr>
119
            #end
36645 vikas 120
            #set($idx = 1)
121
            #foreach($v in $visits)
36836 ranu 122
                #set($tn = "$!v.getTaskName()")
123
                #if($tn.contains("|"))
124
                    #set($pipeIdx = $tn.indexOf("|"))
125
                    #set($afterPipe = $pipeIdx + 1)
126
                    #set($agendaPart = $tn.substring(0, $pipeIdx).trim())
127
                    #set($partnerPart = $tn.substring($afterPipe).trim())
128
                #else
129
                    #set($agendaPart = "--")
130
                    #set($partnerPart = $tn)
131
                #end
36645 vikas 132
                <tr>
133
                    <td>$idx</td>
36836 ranu 134
                    <td>$agendaPart</td>
135
                    <td>$partnerPart</td>
36730 vikas 136
                    <td>#if($v.getTaskDescription() && $v.getTaskDescription() != "")#if($v.getTaskDescription().length() > 50)<span class="desc-short">$v.getTaskDescription().substring(0, 50)... <a href="javascript:void(0);" class="read-more-link" style="color:#3498db;">read more</a></span><span class="desc-full" style="display:none;">$v.getTaskDescription() <a href="javascript:void(0);" class="read-less-link" style="color:#3498db;">read less</a></span>#else$v.getTaskDescription()#end#else--#end</td>
36645 vikas 137
                    <td>
36733 vikas 138
                    #if($v.getAttachment() && $v.getAttachment() != "")
36732 vikas 139
                        <img src="/download-attachment?documentId=$v.getAttachment()" class="view-attachment-link" alt="IMAGE" width="50" data-attachment="$v.getAttachment()">
36733 vikas 140
                    #end
36783 vikas 141
                        #if($v.getMarkType() == "CHECKIN-CHECKOUT" || $v.getMarkType() == "CHECKIN_CHECKOUT" || $v.getMarkType() == "CHECKOUT")
36645 vikas 142
                            <span class="label label-success">$v.getMarkType()</span>
143
                        #elseif($v.getMarkType() == "PENDING" || $v.getMarkType() == "CHECKIN")
144
                            <span class="label label-warning">$v.getMarkType()</span>
145
                        #elseif($v.getMarkType() == "DEFERRED")
146
                            <span class="label label-danger">$v.getMarkType()</span>
147
                        #else
148
                            <span class="label label-default">$!v.getMarkType()</span>
149
                        #end
36730 vikas 150
 
151
                        #if($v.getTaskType() == "attendance")
152
                            <span class="label label-success">$v.getTaskType()</span>
153
                        #elseif($v.getTaskType() == "lead")
154
                            <span class="label label-primary">$v.getTaskType()</span>
155
                        #else
156
                            <span class="label label-warning">$!v.getTaskType()</span>
157
                        #end
158
 
36656 vikas 159
                        <a href="javascript:void(0);" class="beat-history-link" data-userid="$userId" data-taskid="$v.getTaskId()" data-tasktype="$!v.getTaskType()" data-taskname="$!v.getTaskName()" title="View History" style="margin-left:5px;"><i class="fa fa-history" style="color:#3498db; font-size:14px;"></i></a>
36717 vikas 160
                        #if($v.getAttachment() && $v.getAttachment() != "")<a href="javascript:void(0);" class="view-attachment-link" data-attachment="$v.getAttachment()" data-checkin-latlng="$!v.getCheckInLatLng()" data-visit-location="$!v.getVisitLocation()" data-tracking-id="$v.getId()" title="View Attachment"><i class="fa fa-paperclip" style="color:#27ae60; margin-left:3px; font-size:14px;"></i></a>#end
36645 vikas 161
                    </td>
162
                    <td>$!v.getAddress()</td>
163
                    <td>$!v.getCheckInTime()</td>
164
                    <td>$!v.getCheckOutTime()</td>
165
                    <td>$!v.getTimeSpent()</td>
166
                    <td>$!v.getTransitTime()</td>
167
                    <td>$!v.getTotalDistance()</td>
36666 vikas 168
                    <td>$!v.getEstimatedTime()</td>
36645 vikas 169
                </tr>
170
                #set($idx = $idx + 1)
171
            #end
36656 vikas 172
            #if($punchOut)
173
                <tr style="background:#fadbd8;">
174
                    <td>--</td>
36836 ranu 175
                    <td>--</td>
36656 vikas 176
                    <td><strong>PUNCH OUT</strong></td>
36730 vikas 177
                    <td>$!punchOut.getTaskDescription()</td>
36717 vikas 178
                    <td><span class="label label-danger">PUNCHOUT</span> #if($punchOut.getAttachment() && $punchOut.getAttachment() != "")<a href="javascript:void(0);" class="view-attachment-link" data-attachment="$punchOut.getAttachment()" data-checkin-latlng="$!punchOut.getCheckInLatLng()" data-visit-location="$!punchOut.getVisitLocation()" data-tracking-id="$punchOut.getId()" title="View Attachment"><i class="fa fa-paperclip" style="color:#27ae60; margin-left:5px;"></i></a>#end</td>
36656 vikas 179
                    <td>$!punchOut.getAddress()</td>
180
                    <td>$!punchOut.getCheckInTime()</td>
181
                    <td>--</td>
182
                    <td>--</td>
183
                    <td>--</td>
184
                    <td>$!punchOut.getTotalDistance()</td>
36666 vikas 185
                    <td>$!punchOut.getEstimatedTime()</td>
36656 vikas 186
                </tr>
187
            #end
36645 vikas 188
            </tbody>
36656 vikas 189
            <tfoot>
190
                <tr style="background:#f2f3f4; font-weight:bold;">
36836 ranu 191
                    <td colspan="6" style="text-align:right;">Totals</td>
36656 vikas 192
                    <td>--</td>
193
                    <td>--</td>
194
                    <td>$totalTimeSpentStr</td>
195
                    <td>$totalTransitStr</td>
196
                    <td>$totalDistanceStr km | Visits: $visitCount | Leads: $leadCount</td>
36666 vikas 197
                    <td></td>
36656 vikas 198
                </tr>
199
            </tfoot>
36645 vikas 200
        </table>
201
    </div>
202
</section>
203
 
36656 vikas 204
<!-- Task History Modal -->
205
<div class="modal fade" id="taskHistoryModal" tabindex="-1" role="dialog">
206
    <div class="modal-dialog modal-lg" role="document" style="width:85%;">
207
        <div class="modal-content">
208
            <div class="modal-header" style="background:#3498db; color:white;">
209
                <button type="button" class="close" data-dismiss="modal" style="color:white;">&times;</button>
210
                <h4 class="modal-title" id="taskHistoryModalTitle">Task History</h4>
211
            </div>
212
            <div class="modal-body" id="taskHistoryModalBody">
213
                <div class="text-center"><i class="fa fa-spinner fa-spin fa-2x"></i> Loading...</div>
214
            </div>
215
            <div class="modal-footer">
216
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
217
            </div>
218
        </div>
219
    </div>
220
</div>
221
 
36664 vikas 222
<!-- Live Tracking Modal -->
223
<div class="modal fade" id="liveTrackingModal" tabindex="-1" role="dialog">
224
    <div class="modal-dialog modal-lg" role="document" style="width:90%;">
225
        <div class="modal-content">
226
            <div class="modal-header" style="background:#27ae60; color:white;">
227
                <button type="button" class="close" data-dismiss="modal" style="color:white;">&times;</button>
36729 vikas 228
                <h4 class="modal-title" id="liveTrackingModalTitle"><img src="${rc.contextPath}/resources/images/salesman.png" style="height:24px; vertical-align:middle; margin-right:6px;"> Live Tracking</h4>
36664 vikas 229
            </div>
230
            <div class="modal-body" style="padding:0;">
231
                <div id="liveTrackingInfo" style="padding:8px 15px; background:#f8f9fa; border-bottom:1px solid #ddd; font-size:13px;"></div>
232
                <div id="liveTrackingMap" style="height:500px; width:100%;"></div>
233
            </div>
234
            <div class="modal-footer">
36709 vikas 235
                #*<label style="float:left; margin-top:7px; font-weight:normal; font-size:13px;">
236
                    <input type="checkbox" id="autoRefreshToggle" style="margin-right:5px;"> Auto-refresh (30s)
237
                </label>*#
36664 vikas 238
                <button type="button" class="btn btn-primary" id="refreshTrackingBtn"><i class="fa fa-refresh"></i> Refresh</button>
239
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
240
            </div>
241
        </div>
242
    </div>
243
</div>
244
 
36709 vikas 245
<!-- Attachment Viewer Modal -->
246
<div class="modal fade" id="attachmentViewerModal" tabindex="-1" role="dialog">
247
    <div class="modal-dialog" role="document" style="width:600px;">
248
        <div class="modal-content">
249
            <div class="modal-header" style="background:#e67e22; color:white;">
250
                <button type="button" class="close" data-dismiss="modal" style="color:white;">&times;</button>
251
                <h4 class="modal-title"><i class="fa fa-paperclip"></i> Attachment</h4>
252
            </div>
253
            <div class="modal-body" style="text-align:center; padding:15px;">
254
                <img id="attachmentImage" src="" style="max-width:100%; max-height:450px; border:1px solid #ddd; border-radius:4px;" />
255
                <div id="attachmentDistanceInfo" style="margin-top:10px; font-size:13px;"></div>
256
            </div>
257
            <div class="modal-footer">
258
                <button type="button" class="btn btn-success" id="approveCheckinBtn" style="display:none;">
259
                    <i class="fa fa-check"></i> Approve
260
                </button>
261
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
262
            </div>
263
        </div>
264
    </div>
265
</div>
266
 
36645 vikas 267
<script>
268
    $('#userDetailTable').DataTable({
269
        "scrollX": true,
36685 vikas 270
        "pageLength": 50,
36645 vikas 271
        "bPaginate": true,
272
        "bLengthChange": true,
273
        "bFilter": true,
274
        "bInfo": true,
36709 vikas 275
        "bAutoWidth": false,
276
        "ordering": false
36645 vikas 277
    });
36656 vikas 278
 
36717 vikas 279
    $('.view-attachment-link').each(function () {
280
        var checkinLatLng = $(this).data('checkin-latlng') || '';
281
        var visitLocation = $(this).data('visit-location') || '';
282
        if (checkinLatLng && visitLocation && checkinLatLng.toString().indexOf(',') > 0 && visitLocation.toString().indexOf(',') > 0) {
283
            var cParts = checkinLatLng.toString().split(',');
284
            var vParts = visitLocation.toString().split(',');
285
            var cLat = parseFloat(cParts[0]), cLng = parseFloat(cParts[1]);
286
            var vLat = parseFloat(vParts[0]), vLng = parseFloat(vParts[1]);
287
            if (!isNaN(cLat) && !isNaN(cLng) && !isNaN(vLat) && !isNaN(vLng) && !(cLat === 0 && cLng === 0) && !(vLat === 0 && vLng === 0)) {
288
                if (haversineMeters(cLat, cLng, vLat, vLng) > 50) {
289
                    $(this).find('i').css('color', '#e74c3c');
290
                }
291
            }
292
        }
293
    });
294
 
36664 vikas 295
    (function () {
296
        var level = $('#user-detail-level').data('selected');
297
        var selectedAuth = $('#user-detail-user').data('selected-auth');
298
        if (level && selectedAuth) {
299
            $.ajax({
300
                url: context + "/beatPlan/getAuthUsers", type: "GET", dataType: "json",
301
                data: { categoryId: 4, escalationType: level },
302
                success: function (r) {
303
                    var data = r.response || r;
304
                    var html = '<option value="">Select User</option>';
305
                    data.forEach(function (u) {
306
                        html += '<option value="' + u.id + '"' + (u.id == selectedAuth ? ' selected' : '') + '>' + u.name + '</option>';
307
                    });
308
                    $('#user-detail-user').html(html);
309
                }
310
            });
311
        }
312
    })();
313
 
37498 amit 314
    // Fragment is re-injected with .html(), which re-runs this script; clear the
315
    // namespace first so handlers replace rather than stack.
316
    $(document).off('.beatreportuserdetail');
317
    $(document).on('click.beatreportuserdetail', '.read-more-link', function () {
36717 vikas 318
        $(this).closest('td').find('.desc-short').hide();
319
        $(this).closest('td').find('.desc-full').show();
320
    });
37498 amit 321
    $(document).on('click.beatreportuserdetail', '.read-less-link', function () {
36717 vikas 322
        $(this).closest('td').find('.desc-full').hide();
323
        $(this).closest('td').find('.desc-short').show();
324
    });
325
 
36664 vikas 326
    $(document).off('click.beatdetail').off('click.livetrack');
327
 
328
    $(document).on('click.beatdetail', '.beat-history-link', function () {
36656 vikas 329
        var userId = $(this).data('userid');
330
        var taskId = $(this).data('taskid');
331
        var taskType = $(this).data('tasktype');
332
        var taskName = $(this).data('taskname');
333
 
334
        $('#taskHistoryModalTitle').text('History - ' + taskName);
335
        $('#taskHistoryModalBody').html('<div class="text-center"><i class="fa fa-spinner fa-spin fa-2x"></i> Loading...</div>');
336
        $('#taskHistoryModal').modal('show');
337
 
338
        doGetAjaxRequestHandler(context + "/beat-report/task-history?userId=" + userId + "&taskId=" + taskId + "&taskType=" + encodeURIComponent(taskType),
339
            function (response) {
340
                $('#taskHistoryModalBody').html(response);
341
            });
342
    });
36664 vikas 343
 
344
    var liveTrackingMap = null;
345
    var liveTrackingUserId = null;
346
    var liveTrackingInterval = null;
347
 
348
    function getMarkerColor(markType) {
36783 vikas 349
        if (markType === 'CHECKIN-CHECKOUT' || markType === 'CHECKIN_CHECKOUT' || markType === 'CHECKOUT') return '#0F9D58';
36664 vikas 350
        if (markType === 'PENDING' || markType === 'CHECKIN') return '#4285F4';
351
        if (markType === 'DEFERRED') return '#DB4437';
352
        if (markType === 'PUNCHIN') return '#1abc9c';
353
        if (markType === 'PUNCHOUT') return '#e74c3c';
354
        return '#F4B400';
355
    }
356
 
357
    function createTrackingMarkerIcon(color, label) {
36709 vikas 358
        var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="42" height="60" viewBox="0 0 42 60">' +
359
            '<path d="M21 0C9.402 0 0 9.402 0 21c0 15.75 21 39 21 39s21-23.25 21-39C42 9.402 32.598 0 21 0z" fill="' + color + '"/>' +
360
            '<circle cx="21" cy="21" r="10" fill="white"/>' +
361
            '<text x="21" y="26" text-anchor="middle" font-size="14" font-weight="bold" fill="' + color + '">' + label + '</text></svg>';
36664 vikas 362
        return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
363
    }
364
 
365
    function loadLiveTracking(userId, username) {
366
        var selectedDate = $('#user-detail-date').val();
367
        $.ajax({
368
            url: context + "/beat-report/live-tracking",
369
            type: "GET",
370
            dataType: "json",
371
            data: { userId: userId, date: selectedDate },
372
            success: function (data) {
373
                renderLiveTrackingMap(data, username);
374
            },
375
            error: function () {
376
                $('#liveTrackingInfo').html('<span style="color:red;">Failed to load tracking data.</span>');
377
            }
378
        });
379
    }
380
 
36685 vikas 381
    function clearMapOverlays() {
382
        if (!liveTrackingMap) return;
383
        if (liveTrackingMap._markers) {
384
            liveTrackingMap._markers.forEach(function (m) { m.setMap(null); });
385
        }
386
        if (liveTrackingMap._polyline) {
387
            liveTrackingMap._polyline.setMap(null);
388
        }
389
        if (liveTrackingMap._gpsDots) {
390
            liveTrackingMap._gpsDots.forEach(function (m) { m.setMap(null); });
391
        }
392
        if (liveTrackingMap._stopMarkers) {
393
            liveTrackingMap._stopMarkers.forEach(function (m) { m.setMap(null); });
394
        }
395
        if (liveTrackingMap._currentMarker) {
396
            liveTrackingMap._currentMarker.setMap(null);
397
        }
398
        if (liveTrackingMap._directionsRenderer) {
399
            liveTrackingMap._directionsRenderer.setMap(null);
400
        }
401
        liveTrackingMap._markers = [];
402
        liveTrackingMap._gpsDots = [];
403
        liveTrackingMap._stopMarkers = [];
404
    }
405
 
406
    function formatEpoch(ts) {
407
        if (!ts) return 'N/A';
408
        var n = parseFloat(ts);
409
        if (isNaN(n)) return ts;
410
        if (n < 1e12) n = n * 1000;
411
        var d = new Date(n);
412
        return d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0') + ':' + d.getSeconds().toString().padStart(2,'0');
413
    }
414
 
415
    function formatDuration(ms) {
416
        var sec = Math.floor(ms / 1000);
417
        var h = Math.floor(sec / 3600);
418
        var m = Math.floor((sec % 3600) / 60);
419
        var s = sec % 60;
420
        if (h > 0) return h + 'h ' + m + 'm';
421
        if (m > 0) return m + 'm ' + s + 's';
422
        return s + 's';
423
    }
424
 
425
    function haversineMeters(lat1, lon1, lat2, lon2) {
426
        var R = 6371000;
427
        var dLat = (lat2 - lat1) * Math.PI / 180;
428
        var dLon = (lon2 - lon1) * Math.PI / 180;
429
        var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
430
                Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
431
                Math.sin(dLon/2) * Math.sin(dLon/2);
432
        return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
433
    }
434
 
435
    function detectStops(points, radiusM, minTimeMs) {
436
        var stops = [];
437
        var i = 0;
438
        while (i < points.length) {
439
            var cluster = [points[i]];
440
            var cLat = points[i].lat, cLng = points[i].lng;
441
            var j = i + 1;
442
            while (j < points.length) {
443
                if (haversineMeters(cLat, cLng, points[j].lat, points[j].lng) <= radiusM) {
444
                    cluster.push(points[j]);
445
                    j++;
446
                } else {
447
                    break;
448
                }
449
            }
450
            if (cluster.length >= 2) {
451
                var t0 = parseFloat(cluster[0].ts);
452
                var t1 = parseFloat(cluster[cluster.length - 1].ts);
453
                if (t0 < 1e12) t0 *= 1000;
454
                if (t1 < 1e12) t1 *= 1000;
455
                var duration = t1 - t0;
456
                if (duration >= minTimeMs) {
457
                    var avgLat = 0, avgLng = 0;
458
                    cluster.forEach(function(p) { avgLat += p.lat; avgLng += p.lng; });
459
                    stops.push({
460
                        lat: avgLat / cluster.length,
461
                        lng: avgLng / cluster.length,
462
                        startTime: t0,
463
                        endTime: t1,
464
                        duration: duration,
465
                        pointCount: cluster.length
466
                    });
467
                }
468
            }
469
            i = j;
470
        }
471
        return stops;
472
    }
473
 
36664 vikas 474
    function renderLiveTrackingMap(data, username) {
475
        var visits = data.visits || [];
476
        var pathPoints = data.pathPoints || [];
477
 
478
        if (!liveTrackingMap) {
479
            liveTrackingMap = new google.maps.Map(document.getElementById('liveTrackingMap'), {
480
                zoom: 12,
481
                center: { lat: 20.5937, lng: 78.9629 },
482
                mapTypeControl: true,
483
                streetViewControl: false
484
            });
485
        }
486
 
36685 vikas 487
        clearMapOverlays();
36664 vikas 488
 
489
        var bounds = new google.maps.LatLngBounds();
490
        var hasPoints = false;
491
        var visitIdx = 0;
492
 
36783 vikas 493
        // Pre-compute first/last valid path point as fallback for punch records with no coordinates
494
        var firstPathLat = null, firstPathLng = null;
495
        var lastPathLat = null, lastPathLng = null;
496
        for (var pi = 0; pi < pathPoints.length; pi++) {
497
            var ppLat = parseFloat(pathPoints[pi].lat), ppLng = parseFloat(pathPoints[pi].lng);
498
            if (!isNaN(ppLat) && !isNaN(ppLng) && !(ppLat === 0 && ppLng === 0)) {
499
                if (firstPathLat === null) { firstPathLat = ppLat; firstPathLng = ppLng; }
500
                lastPathLat = ppLat; lastPathLng = ppLng;
501
            }
502
        }
503
 
36664 vikas 504
        for (var i = 0; i < visits.length; i++) {
505
            var v = visits[i];
36783 vikas 506
            var lat = v.lat ? parseFloat(v.lat) : NaN;
507
            var lng = v.lng ? parseFloat(v.lng) : NaN;
508
            if (isNaN(lat) || isNaN(lng) || (lat === 0 && lng === 0)) {
509
                if (v.markType === 'PUNCHIN' && firstPathLat !== null) {
510
                    lat = firstPathLat; lng = firstPathLng;
511
                } else if (v.markType === 'PUNCHOUT' && lastPathLat !== null) {
512
                    lat = lastPathLat; lng = lastPathLng;
513
                } else {
514
                    continue;
515
                }
516
            }
36664 vikas 517
 
518
            hasPoints = true;
519
            var pos = { lat: lat, lng: lng };
520
            bounds.extend(pos);
521
 
522
            var label = v.markType === 'PUNCHIN' ? 'P' : (v.markType === 'PUNCHOUT' ? 'X' : String(++visitIdx));
523
            var marker = new google.maps.Marker({
524
                position: pos,
525
                map: liveTrackingMap,
526
                icon: {
527
                    url: createTrackingMarkerIcon(getMarkerColor(v.markType), label),
36709 vikas 528
                    scaledSize: new google.maps.Size(42, 60),
529
                    anchor: new google.maps.Point(21, 60)
36664 vikas 530
                },
36685 vikas 531
                title: v.taskName + ' (' + v.markType + ')',
532
                zIndex: 100
36664 vikas 533
            });
534
 
36685 vikas 535
            var infoContent = '<div style="font-size:13px; max-width:280px;">' +
36664 vikas 536
                '<strong>' + (v.taskName || '') + '</strong><br>' +
36685 vikas 537
                '<span class="label" style="background:' + getMarkerColor(v.markType) + '; color:white;">' + v.markType + '</span>' +
538
                (v.taskType ? ' <span class="label label-default">' + v.taskType + '</span>' : '') + '<br>' +
539
                (v.checkInTime ? '<i class="fa fa-sign-in" style="color:#27ae60;"></i> In: <strong>' + v.checkInTime + '</strong><br>' : '') +
540
                (v.checkOutTime ? '<i class="fa fa-sign-out" style="color:#e74c3c;"></i> Out: <strong>' + v.checkOutTime + '</strong><br>' : '') +
541
                (v.timeSpent ? '<i class="fa fa-clock-o" style="color:#f39c12;"></i> Time Spent: <strong>' + v.timeSpent + '</strong><br>' : '') +
542
                (v.transitTime ? '<i class="fa fa-car" style="color:#3498db;"></i> Transit: <strong>' + v.transitTime + '</strong><br>' : '') +
543
                (v.totalDistance && v.totalDistance !== '0.0' ? '<i class="fa fa-road" style="color:#8e44ad;"></i> Distance: <strong>' + v.totalDistance + ' km</strong><br>' : '') +
544
                (v.address ? '<i class="fa fa-map-marker" style="color:#e74c3c;"></i> ' + v.address : '') +
36664 vikas 545
                '</div>';
546
            (function (m, c) {
547
                var iw = new google.maps.InfoWindow({ content: c });
548
                m.addListener('click', function () { iw.open(liveTrackingMap, m); });
549
            })(marker, infoContent);
550
 
551
            liveTrackingMap._markers.push(marker);
552
        }
553
 
36685 vikas 554
        // Draw GPS tracked path with red dots + connecting line
555
        var totalGpsDistanceM = 0;
556
        var accuracySum = 0;
557
        var accuracyCount = 0;
558
        var stopsDetected = [];
559
 
36664 vikas 560
        if (pathPoints.length > 0) {
36685 vikas 561
            var validPoints = [];
36664 vikas 562
            for (var j = 0; j < pathPoints.length; j++) {
563
                var pt = pathPoints[j];
564
                var pLat = parseFloat(pt.lat);
565
                var pLng = parseFloat(pt.lng);
566
                if (isNaN(pLat) || isNaN(pLng) || (pLat === 0 && pLng === 0)) continue;
36685 vikas 567
                var acc = parseFloat(pt.accuracy);
568
                if (!isNaN(acc)) { accuracySum += acc; accuracyCount++; }
569
                validPoints.push({ lat: pLat, lng: pLng, ts: pt.timestamp, accuracy: pt.accuracy });
36664 vikas 570
                bounds.extend({ lat: pLat, lng: pLng });
571
                hasPoints = true;
572
            }
573
 
36685 vikas 574
            // Calculate total GPS distance
575
            for (var d = 1; d < validPoints.length; d++) {
576
                totalGpsDistanceM += haversineMeters(validPoints[d-1].lat, validPoints[d-1].lng, validPoints[d].lat, validPoints[d].lng);
577
            }
578
 
579
            if (validPoints.length > 0) {
580
                var pathCoords = validPoints.map(function(p) { return { lat: p.lat, lng: p.lng }; });
581
 
582
                // Blue connecting line
36664 vikas 583
                liveTrackingMap._polyline = new google.maps.Polyline({
584
                    path: pathCoords,
585
                    geodesic: true,
36685 vikas 586
                    strokeColor: '#e74c3c',
587
                    strokeOpacity: 0.7,
36664 vikas 588
                    strokeWeight: 3,
36685 vikas 589
                    zIndex: 50,
36664 vikas 590
                    map: liveTrackingMap
591
                });
592
 
36688 vikas 593
                // Direction arrows for each GPS point
36685 vikas 594
                liveTrackingMap._gpsDots = [];
595
                for (var gi = 0; gi < validPoints.length; gi++) {
596
                    var gp = validPoints[gi];
36688 vikas 597
                    var heading = 0;
598
                    if (gi < validPoints.length - 1) {
599
                        heading = google.maps.geometry.spherical.computeHeading(
600
                            new google.maps.LatLng(gp.lat, gp.lng),
601
                            new google.maps.LatLng(validPoints[gi + 1].lat, validPoints[gi + 1].lng)
602
                        );
603
                    } else if (gi > 0) {
604
                        heading = google.maps.geometry.spherical.computeHeading(
605
                            new google.maps.LatLng(validPoints[gi - 1].lat, validPoints[gi - 1].lng),
606
                            new google.maps.LatLng(gp.lat, gp.lng)
607
                        );
608
                    }
36685 vikas 609
                    var dot = new google.maps.Marker({
610
                        position: { lat: gp.lat, lng: gp.lng },
611
                        map: liveTrackingMap,
612
                        icon: {
36688 vikas 613
                            path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
614
                            scale: 3,
36685 vikas 615
                            fillColor: '#e74c3c',
616
                            fillOpacity: 1,
617
                            strokeColor: '#b71c1c',
36688 vikas 618
                            strokeWeight: 1,
619
                            rotation: heading
36685 vikas 620
                        },
621
                        zIndex: 80
622
                    });
623
                    (function(marker, point, idx) {
624
                        var iw = new google.maps.InfoWindow({
625
                            content: '<div style="font-size:12px;">' +
626
                                '<strong>GPS Point #' + (idx + 1) + '</strong><br>' +
627
                                '<i class="fa fa-clock-o"></i> ' + formatEpoch(point.ts) + '<br>' +
628
                                '<i class="fa fa-crosshairs"></i> Accuracy: ' + (point.accuracy || 'N/A') + 'm<br>' +
629
                                '<i class="fa fa-map-marker"></i> ' + point.lat.toFixed(6) + ', ' + point.lng.toFixed(6) +
630
                                '</div>'
631
                        });
632
                        marker.addListener('click', function() { iw.open(liveTrackingMap, marker); });
633
                    })(dot, gp, gi);
634
                    liveTrackingMap._gpsDots.push(dot);
635
                }
636
 
637
                // Detect stationary stops (within 50m for >5 min)
638
                stopsDetected = detectStops(validPoints, 50, 5 * 60 * 1000);
639
                liveTrackingMap._stopMarkers = [];
640
                for (var si = 0; si < stopsDetected.length; si++) {
641
                    var stop = stopsDetected[si];
642
                    var stopMarker = new google.maps.Marker({
643
                        position: { lat: stop.lat, lng: stop.lng },
644
                        map: liveTrackingMap,
645
                        icon: {
646
                            path: google.maps.SymbolPath.CIRCLE,
36709 vikas 647
                            scale: 20,
36685 vikas 648
                            fillColor: '#f39c12',
649
                            fillOpacity: 0.9,
650
                            strokeColor: '#e67e22',
651
                            strokeWeight: 2
652
                        },
36709 vikas 653
                        label: { text: formatDuration(stop.duration), fontSize: '12px', fontWeight: 'bold', color: '#000' },
36685 vikas 654
                        title: 'Stopped for ' + formatDuration(stop.duration),
655
                        zIndex: 200
656
                    });
657
                    (function(marker, s) {
658
                        var iw = new google.maps.InfoWindow({
659
                            content: '<div style="font-size:13px;">' +
660
                                '<strong><i class="fa fa-pause-circle" style="color:#f39c12;"></i> Stationary Stop</strong><br>' +
661
                                '<i class="fa fa-clock-o"></i> Duration: <strong>' + formatDuration(s.duration) + '</strong><br>' +
662
                                '<i class="fa fa-sign-in"></i> From: <strong>' + new Date(s.startTime).toLocaleTimeString() + '</strong><br>' +
663
                                '<i class="fa fa-sign-out"></i> To: <strong>' + new Date(s.endTime).toLocaleTimeString() + '</strong><br>' +
664
                                '<i class="fa fa-map-marker"></i> ' + s.lat.toFixed(6) + ', ' + s.lng.toFixed(6) + '<br>' +
665
                                '<i class="fa fa-dot-circle-o"></i> Points in cluster: ' + s.pointCount +
666
                                '</div>'
667
                        });
668
                        marker.addListener('click', function() { iw.open(liveTrackingMap, marker); });
669
                    })(stopMarker, stop);
670
                    liveTrackingMap._stopMarkers.push(stopMarker);
671
                }
672
 
36688 vikas 673
                // Last known location marker (bike icon)
36664 vikas 674
                var lastPoint = pathCoords[pathCoords.length - 1];
675
                liveTrackingMap._currentMarker = new google.maps.Marker({
676
                    position: lastPoint,
677
                    map: liveTrackingMap,
678
                    icon: {
36729 vikas 679
                        url: '${rc.contextPath}/resources/images/salesman.png',
36709 vikas 680
                        scaledSize: new google.maps.Size(56, 56),
681
                        anchor: new google.maps.Point(28, 28)
36664 vikas 682
                    },
683
                    title: 'Last Known Location',
684
                    zIndex: 999
685
                });
36685 vikas 686
                var lastGp = validPoints[validPoints.length - 1];
36664 vikas 687
                var currentIw = new google.maps.InfoWindow({
36685 vikas 688
                    content: '<div style="font-size:13px;"><strong><i class="fa fa-crosshairs" style="color:#27ae60;"></i> Last Known Location</strong><br>' +
689
                        '<i class="fa fa-clock-o"></i> Time: ' + formatEpoch(lastGp.ts) + '<br>' +
690
                        '<i class="fa fa-crosshairs"></i> Accuracy: ' + (lastGp.accuracy || 'N/A') + 'm</div>'
36664 vikas 691
                });
692
                liveTrackingMap._currentMarker.addListener('click', function () {
693
                    currentIw.open(liveTrackingMap, liveTrackingMap._currentMarker);
694
                });
695
            }
696
        }
697
 
698
        if (hasPoints) {
699
            liveTrackingMap.fitBounds(bounds);
700
        }
701
 
36685 vikas 702
        // Info bar
36664 vikas 703
        var totalVisits = visits.filter(function (v) { return v.markType !== 'PUNCHIN' && v.markType !== 'PUNCHOUT'; }).length;
36783 vikas 704
        var completedVisits = visits.filter(function (v) { return v.markType === 'CHECKIN-CHECKOUT' || v.markType === 'CHECKIN_CHECKOUT' || v.markType === 'CHECKOUT'; }).length;
36682 vikas 705
        var now = new Date();
706
        var timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');
36685 vikas 707
 
708
        var avgAccuracy = accuracyCount > 0 ? (accuracySum / accuracyCount).toFixed(0) : null;
709
        var accuracyLabel = '';
710
        var accuracyBadge = '';
711
        if (avgAccuracy) {
712
            if (avgAccuracy <= 10) { accuracyLabel = 'High'; accuracyBadge = 'background:#27ae60;'; }
713
            else if (avgAccuracy <= 30) { accuracyLabel = 'Good'; accuracyBadge = 'background:#2ecc71;'; }
714
            else if (avgAccuracy <= 100) { accuracyLabel = 'Average'; accuracyBadge = 'background:#f39c12;'; }
715
            else { accuracyLabel = 'Low'; accuracyBadge = 'background:#e74c3c;'; }
716
        }
717
 
718
        var totalGpsKm = (totalGpsDistanceM / 1000).toFixed(2);
719
 
36783 vikas 720
        var punchInRecord = visits.filter(function (v) { return v.markType === 'PUNCHIN'; })[0];
721
        var punchOutRecord = visits.filter(function (v) { return v.markType === 'PUNCHOUT'; })[0];
722
 
723
        var infoHtml = '<strong>' + username + '</strong> | ';
724
        if (punchInRecord && punchInRecord.checkInTime) {
725
            infoHtml += '<i class="fa fa-sign-in" style="color:#1abc9c;"></i> In: <strong>' + punchInRecord.checkInTime + '</strong> | ';
726
        }
727
        if (punchOutRecord && punchOutRecord.checkInTime) {
728
            infoHtml += '<i class="fa fa-sign-out" style="color:#e74c3c;"></i> Out: <strong>' + punchOutRecord.checkInTime + '</strong> | ';
729
        }
730
        infoHtml += 'Visits: <strong>' + totalVisits + '</strong> | ' +
36685 vikas 731
            'Completed: <strong>' + completedVisits + '</strong>';
732
 
733
        if (pathPoints.length > 0) {
734
            infoHtml += ' | GPS Distance: <strong>' + totalGpsKm + ' km</strong>';
735
            infoHtml += ' | Points: <strong>' + pathPoints.length + '</strong>';
736
            if (accuracyLabel) {
737
                infoHtml += ' | Accuracy: <span class="label" style="' + accuracyBadge + ' color:white; padding:2px 8px; border-radius:3px; font-size:11px;">' + accuracyLabel + '</span> (' + avgAccuracy + 'm)';
738
            }
739
            if (stopsDetected.length > 0) {
740
                infoHtml += ' | <span style="color:#f39c12;"><i class="fa fa-pause-circle"></i></span> Stops: <strong>' + stopsDetected.length + '</strong>';
741
            }
36729 vikas 742
            infoHtml += ' | <img src="${rc.contextPath}/resources/images/salesman.png" style="height:16px; vertical-align:middle;"> Last Known';
36685 vikas 743
        } else {
744
            infoHtml += ' | <span style="color:#999;"><i class="fa fa-exclamation-triangle"></i> No GPS data yet</span>';
745
        }
746
 
36688 vikas 747
        infoHtml += ' | <span style="color:#e74c3c;"><i class="fa fa-arrow-right" style="font-size:10px;"></i></span> GPS Trail';
36783 vikas 748
        var battery = data.batteryLevel;
749
        if (battery !== null && battery !== undefined) {
750
            var batteryColor = battery > 50 ? '#27ae60' : (battery > 20 ? '#f39c12' : '#e74c3c');
751
            var batteryIcon = battery > 50 ? 'fa-battery-full' : (battery > 20 ? 'fa-battery-half' : 'fa-battery-quarter');
752
            infoHtml += ' | <span style="color:' + batteryColor + ';"><i class="fa ' + batteryIcon + '"></i> Battery: <strong>' + battery + '%</strong></span>';
753
        }
754
 
36690 vikas 755
        var locToggle = data.locationToggleCount || 0;
756
        if (locToggle > 0) {
757
            infoHtml += ' | <span class="label" style="background:#e74c3c; color:white; padding:2px 8px; border-radius:3px; font-size:11px;"><i class="fa fa-power-off"></i> Loc Off: ' + locToggle + '</span>';
758
        }
759
 
36685 vikas 760
        infoHtml += ' | <span style="color:#999;">Updated: ' + timeStr + '</span>';
761
 
762
        $('#liveTrackingInfo').html(infoHtml);
763
 
764
        // Low data warning
765
        if (pathPoints.length > 0 && pathPoints.length < 10) {
766
            $('#liveTrackingInfo').append(
767
                '<div style="margin-top:4px; padding:4px 8px; background:#fff3cd; border:1px solid #ffc107; border-radius:3px; font-size:11px; color:#856404;">' +
768
                '<i class="fa fa-exclamation-triangle"></i> Received less data points from device due to: Low Battery level, ' +
769
                'GPS/location switched off, or phone automatically switched off the location service.' +
770
                '</div>'
771
            );
772
        }
36664 vikas 773
    }
774
 
775
    function ensureGoogleMaps(callback) {
776
        if (window.google && window.google.maps) {
777
            callback();
778
            return;
779
        }
780
        var s = document.createElement('script');
36688 vikas 781
        s.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyAckO0y4Z6WhBOuMjNjioWLSYZDhGEvGBc&libraries=geometry&v=weekly';
36664 vikas 782
        s.onload = callback;
783
        document.head.appendChild(s);
784
    }
785
 
36682 vikas 786
    var liveTrackingUsername = '';
787
 
36664 vikas 788
    $(document).on('click.livetrack', '.live-tracking-btn', function () {
789
        liveTrackingUserId = $(this).data('userid');
36682 vikas 790
        liveTrackingUsername = $(this).data('username');
36729 vikas 791
        $('#liveTrackingModalTitle').html('<img src="${rc.contextPath}/resources/images/salesman.png" style="height:24px; vertical-align:middle; margin-right:6px;"> Live Tracking - ' + liveTrackingUsername);
36664 vikas 792
        $('#liveTrackingInfo').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
793
        $('#liveTrackingModal').modal('show');
794
 
795
        setTimeout(function () {
796
            liveTrackingMap = null;
797
            ensureGoogleMaps(function () {
36682 vikas 798
                loadLiveTracking(liveTrackingUserId, liveTrackingUsername);
36664 vikas 799
            });
800
        }, 300);
801
    });
802
 
803
    $(document).on('click.livetrack', '#refreshTrackingBtn', function () {
804
        if (liveTrackingUserId) {
805
            $('#liveTrackingInfo').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Refreshing...</div>');
36682 vikas 806
            loadLiveTracking(liveTrackingUserId, liveTrackingUsername);
36664 vikas 807
        }
808
    });
809
 
37498 amit 810
    $(document).on('change.beatreportuserdetail', '#autoRefreshToggle', function () {
36682 vikas 811
        if ($(this).is(':checked')) {
812
            startAutoRefresh();
813
        } else {
814
            stopAutoRefresh();
36664 vikas 815
        }
816
    });
36682 vikas 817
 
818
    $('#liveTrackingModal').on('hidden.bs.modal', function () {
819
        stopAutoRefresh();
820
        liveTrackingUserId = null;
821
    });
36709 vikas 822
 
823
    // Attachment Viewer
37498 amit 824
    $(document).on('click.beatreportuserdetail', '.view-attachment-link', function () {
36709 vikas 825
        var attachment = $(this).data('attachment');
826
        var checkinLatLng = $(this).data('checkin-latlng') || '';
827
        var visitLocation = $(this).data('visit-location') || '';
828
        var trackingId = $(this).data('tracking-id');
829
 
36710 vikas 830
        var imgUrl = context + '/download-attachment?documentId=' + attachment;
36709 vikas 831
        $('#attachmentImage').attr('src', imgUrl);
832
        $('#approveCheckinBtn').hide().removeData('tracking-id');
833
        $('#attachmentDistanceInfo').html('');
834
 
835
        var distanceM = -1;
836
        if (checkinLatLng && visitLocation && checkinLatLng.indexOf(',') > 0 && visitLocation.indexOf(',') > 0) {
837
            var cParts = checkinLatLng.toString().split(',');
838
            var vParts = visitLocation.toString().split(',');
839
            var cLat = parseFloat(cParts[0]);
840
            var cLng = parseFloat(cParts[1]);
841
            var vLat = parseFloat(vParts[0]);
842
            var vLng = parseFloat(vParts[1]);
843
            if (!isNaN(cLat) && !isNaN(cLng) && !isNaN(vLat) && !isNaN(vLng) && !(cLat === 0 && cLng === 0) && !(vLat === 0 && vLng === 0)) {
844
                distanceM = haversineMeters(cLat, cLng, vLat, vLng);
845
            }
846
        }
847
 
848
        if (distanceM > 50) {
849
            $('#attachmentDistanceInfo').html(
850
                '<span class="label label-danger" style="font-size:12px; padding:4px 10px;">' +
851
                '<i class="fa fa-exclamation-triangle"></i> Checkin Distance: ' + Math.round(distanceM) + 'm (exceeds 50m radius)</span>'
852
            );
853
            $('#approveCheckinBtn').show().data('tracking-id', trackingId);
854
        } else if (distanceM >= 0) {
855
            $('#attachmentDistanceInfo').html(
856
                '<span class="label label-success" style="font-size:12px; padding:4px 10px;">' +
857
                '<i class="fa fa-check-circle"></i> Checkin Distance: ' + Math.round(distanceM) + 'm (within 50m radius)</span>'
858
            );
859
        }
860
 
861
        $('#attachmentViewerModal').modal('show');
862
    });
863
 
37498 amit 864
    $(document).on('click.beatreportuserdetail', '#approveCheckinBtn', function () {
36709 vikas 865
        var btn = $(this);
866
        var trackingId = btn.data('tracking-id');
867
        if (!trackingId) return;
868
 
869
        btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Approving...');
870
 
871
        $.ajax({
872
            url: context + '/beat-report/approve-checkin',
873
            type: 'POST',
874
            data: { trackingId: trackingId },
875
            success: function () {
876
                btn.html('<i class="fa fa-check"></i> Approved').removeClass('btn-success').addClass('btn-default').prop('disabled', true);
877
                alert('Checkin approved successfully');
878
            },
879
            error: function () {
880
                btn.prop('disabled', false).html('<i class="fa fa-check"></i> Approve');
881
                alert('Failed to approve checkin');
882
            }
883
        });
884
    });
36645 vikas 885
</script>