Subversion Repositories SmartDukaan

Rev

Rev 36916 | Rev 36929 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 36916 Rev 36917
Line 31... Line 31...
31
  .btn-r{background:var(--brand);border:0;color:#fff;font-weight:650;padding:9px 15px;border-radius:9px;cursor:pointer;font-size:13px;height:36px}
31
  .btn-r{background:var(--brand);border:0;color:#fff;font-weight:650;padding:9px 15px;border-radius:9px;cursor:pointer;font-size:13px;height:36px}
32
  .lenses{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:20px}
32
  .lenses{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:20px}
33
  .lens{padding:11px 4px;margin-right:22px;font-size:14px;font-weight:650;color:var(--muted);cursor:pointer;border:0;background:transparent;border-bottom:2.5px solid transparent;margin-bottom:-1px}
33
  .lens{padding:11px 4px;margin-right:22px;font-size:14px;font-weight:650;color:var(--muted);cursor:pointer;border:0;background:transparent;border-bottom:2.5px solid transparent;margin-bottom:-1px}
34
  .lens.active{color:var(--brand);border-bottom-color:var(--brand)}
34
  .lens.active{color:var(--brand);border-bottom-color:var(--brand)}
35
  .lens .b{display:inline-block;background:#eef1fb;color:var(--brand);font-size:11px;font-weight:700;border-radius:999px;padding:1px 7px;margin-left:6px}
35
  .lens .b{display:inline-block;background:#eef1fb;color:var(--brand);font-size:11px;font-weight:700;border-radius:999px;padding:1px 7px;margin-left:6px}
36
  .kpis{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:20px}
36
  .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}
37
  .kpi{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:14px 15px;box-shadow:0 1px 2px rgba(20,30,60,.04)}
37
  .kpi{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:14px 15px;box-shadow:0 1px 2px rgba(20,30,60,.04)}
38
  .kpi.clickable{cursor:pointer;transition:border-color .12s,box-shadow .12s,transform .12s}
38
  .kpi.clickable{cursor:pointer;transition:border-color .12s,box-shadow .12s,transform .12s}
39
  .kpi.clickable:hover{border-color:#c7d0e8;box-shadow:0 3px 10px rgba(20,30,60,.09);transform:translateY(-1px)}
39
  .kpi.clickable:hover{border-color:#c7d0e8;box-shadow:0 3px 10px rgba(20,30,60,.09);transform:translateY(-1px)}
40
  .kpi .top{display:flex;align-items:center;gap:8px}
40
  .kpi .top{display:flex;align-items:center;gap:8px}
41
  .kpi .ic{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px;flex:none}
41
  .kpi .ic{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px;flex:none}
Line 95... Line 95...
95
  .bdot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:middle}
95
  .bdot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:middle}
96
  .ptime{display:inline-block;background:#ebfbee;color:#2f9e44;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
96
  .ptime{display:inline-block;background:#ebfbee;color:#2f9e44;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
97
  .potime{display:inline-block;background:#ebfbee;color: #D60303A8;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
97
  .potime{display:inline-block;background:#ebfbee;color: #D60303A8;font-weight:700;font-size:11px;padding:2px 7px;border-radius:6px;font-variant-numeric:tabular-nums}
98
  td.cnt{font-weight:700;font-variant-numeric:tabular-nums}
98
  td.cnt{font-weight:700;font-variant-numeric:tabular-nums}
99
  .cgreen{color:#2f9e44}.camber{color:#f08c00}.cred{color:#e03131}.cmuted{color:#69748c}
99
  .cgreen{color:#2f9e44}.camber{color:#f08c00}.cred{color:#e03131}.cmuted{color:#69748c}
100
  tr.boardtot td{background:#f4f6fb;border-top:2px solid #cdd3df;font-weight:700}
100
  tr.boardtot td{background:#f4f6fb;border-bottom:2px solid #cdd3df;font-weight:700}
101
  .actbtn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:7px;border:0;cursor:pointer;color:#fff;font-size:12px;margin-left:3px}
101
  .actbtn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:7px;border:0;cursor:pointer;color:#fff;font-size:12px;margin-left:3px}
102
  .actbtn.map{background:#12b886}.actbtn.view{background:#3b5bdb}
102
  .actbtn.map{background:#12b886}.actbtn.view{background:#3b5bdb}
103
  .actbtn.edit{background:#f08c00}.actbtn.add{background:#2f9e44;font-size:15px}
103
  .actbtn.edit{background:#f08c00}.actbtn.add{background:#2f9e44;font-size:15px}
104
  .actbtn:active{transform:translateY(1px)}
104
  .actbtn:active{transform:translateY(1px)}
105
  .att{display:flex;align-items:center;gap:12px;padding:11px 12px;border:1px solid var(--line);border-radius:11px;margin-bottom:9px}
105
  .att{display:flex;align-items:center;gap:12px;padding:11px 12px;border:1px solid var(--line);border-radius:11px;margin-bottom:9px}
Line 363... Line 363...
363
  window._canApprove = !!d.canApprove;
363
  window._canApprove = !!d.canApprove;
364
  window._mediaUrl = d.mediaUrl || window._mediaUrl || '';
364
  window._mediaUrl = d.mediaUrl || window._mediaUrl || '';
365
  window._board = d.board || [];          // funnel drill-down filters this
365
  window._board = d.board || [];          // funnel drill-down filters this
366
  window._boardDate = d.date || '';
366
  window._boardDate = d.date || '';
367
  window._leadsList = d.leadsList || [];  // leads drill-down
367
  window._leadsList = d.leadsList || [];  // leads drill-down
-
 
368
  window._execList = d.execList || [];    // main-card drill-downs
368
  var h='';
369
  var h='';
369
  h+='<div class="kpis">';
370
  h+='<div class="kpis">';
370
  h+=kpi('&#128101;','#eef1fb','#3b5bdb','Executives scheduled',num(k.scheduled),k.notStarted+' not started yet');
371
  h+=kpi('&#128101;','#eef1fb','#3b5bdb','Total Executives',num(k.totalExecs),'in your team',null,"openCards('total')");
371
  h+=kpi('&#9201;','#ebfbee','#2f9e44','Punched in',num(k.punchedIn)+' <small>/'+k.scheduled+'</small>',k.notStarted+' pending');
372
  h+=kpi('&#128197;','#ebfbee','#2f9e44','Scheduled PJP',num(k.scheduledPjp)+' <small>/'+k.totalExecs+'</small>',k.unscheduled+' unscheduled',null,"openCards('scheduled')");
372
  h+=kpi('&#128205;','#e7f5ff','#1098ad','On route now',num(k.onRouteNow),'live journeys');
-
 
373
  h+=kpi('&#10003;','#eef1fb','#3b5bdb','Visit vs Planned',num(k.visitsDone)+' <small>/'+k.visitsPlanned+'</small>',k.inProgress+' in progress');
373
  h+=kpi('&#9998;','#fff4e6','#e8590c','Agenda Filled',num(k.agendaFilled)+' <small>/'+k.scheduledPjp+'</small>',(k.scheduledPjp-k.agendaFilled)+' pending',null,"openCards('agenda')");
374
  h+=kpi('&#8635;','#fff4e6','#e8590c','Deferred today',num(k.deferred),'shop closed / unavailable');
-
 
375
  h+=kpi('&#9733;','#fff0f6','#d6336c','Leads on route',num(k.leads),'new shops logged',null,'openLeads()');
374
  h+=kpi('&#10060;','#fff0f6','#d6336c','Unscheduled',num(k.unscheduled),'no PJP today',null,"openCards('unscheduled')");
376
  h+='</div>';
375
  h+='</div>';
377
 
376
 
378
  h+='<div class="sectionlabel">Today\'s Journey Funnel</div><div class="card"><div class="funnel">';
377
  h+='<div class="sectionlabel">Today\'s Journey Funnel</div><div class="card"><div class="funnel">';
379
  var sc=f.scheduled||1;
378
  var sp=f.scheduledPjp||1, pin=f.punchedIn||1;
380
  h+=fstep('Scheduled executives',f.scheduled,100,'start of day',false,false,'scheduled');
379
  h+=fstep('Punched In',f.punchedIn,pct(f.punchedIn,sp),f.punchedIn+' of '+f.scheduledPjp+' scheduled',false,false,'punchedIn');
381
  h+=fstep('Punched in',f.punchedIn,pct(f.punchedIn,sc),'&minus;'+(f.scheduled-f.punchedIn)+' never started',true,false,'punchedIn');
380
  h+=fstep('Traveling',f.travelling,pct(f.travelling,pin),'on the road',false,false,'travelling');
382
  h+=fstep('Made a check-in',f.madeCheckin,pct(f.madeCheckin,sc),'&minus;'+(f.punchedIn-f.madeCheckin)+' punched, idle',true,false,'madeCheckin');
381
  h+=fstep('In Store',f.inStore,pct(f.inStore,pin),'at a stop',false,false,'inStore');
383
  h+=fstep('Completed all visits',f.completedAll,pct(f.completedAll,sc),(f.madeCheckin-f.completedAll)+' still mid-journey',true,false,'completedAll');
382
  h+=fstep('Idle / Break',f.idle,pct(f.idle,pin),'off-store &gt; 40 min',true,false,'idle');
384
  h+=fstep('Punched out',f.punchedOut,pct(f.punchedOut,sc),'journey ended',false,true,'punchedOut');
383
  h+=fstep('Punched Out',f.punchedOut,pct(f.punchedOut,pin),'journey ended',false,true,'punchedOut');
385
  h+='</div></div>';
384
  h+='</div></div>';
386
 
385
 
387
  window._boardFilter = null;             // funnel-card filter (null = all rows)
386
  window._boardFilter = null;             // funnel-card filter (null = all rows)
388
  h+='<div class="grid g3">';
387
  h+='<div class="grid g3">';
389
  h+='<div class="card"><h2>Live executive board</h2><p class="desc">Each executive\'s journey right now. Click a row for the day\'s visit detail.</p><div id="bj-board">'+boardCard()+'</div></div>';
388
  h+='<div class="card"><h2>Live executive board</h2><p class="desc">Each executive\'s journey right now. Click a row for the day\'s visit detail.</p><div id="bj-board">'+boardCard()+'</div></div>';
Line 450... Line 449...
450
    '<th>Agenda</th><th>Executive</th><th>Punch In/Out</th>'+
449
    '<th>Agenda</th><th>Executive</th><th>Punch In/Out</th>'+
451
    '<th class="num" title="Completed − Deferred − Pending / Planned">Planned - Visits</th>'+
450
    '<th class="num" title="Completed − Deferred − Pending / Planned">Planned - Visits</th>'+
452
    '<th class="num" title="Completed − Deferred − Pending / Planned">Self Assigned Lead</th>'+
451
    '<th class="num" title="Completed − Deferred − Pending / Planned">Self Assigned Lead</th>'+
453
    '<th class="num">Total</th><th>State</th><th>Action</th>'+
452
    '<th class="num">Total</th><th>State</th><th>Action</th>'+
454
    '</tr></thead><tbody>';
453
    '</tr></thead><tbody>';
-
 
454
  // column totals, computed up front so the Total row can lead the table
455
  var t={cp:0,dp:0,pp:0,cl:0,dl:0,pl:0,tot:0};   // column totals
455
  var t={cp:0,dp:0,pp:0,cl:0,dl:0,pl:0,tot:0};
456
  rows.forEach(function(r){
456
  rows.forEach(function(r){
457
    t.cp+=r.completedPartner||0; t.dp+=r.deferredPartner||0; t.pp+=r.plannedPartner||0;
457
    t.cp+=r.completedPartner||0; t.dp+=r.deferredPartner||0; t.pp+=r.plannedPartner||0;
458
    t.cl+=r.completedLead||0;    t.dl+=r.deferredLead||0;    t.pl+=r.plannedLead||0;
458
    t.cl+=r.completedLead||0;    t.dl+=r.deferredLead||0;    t.pl+=r.plannedLead||0;
459
    t.tot+=r.totalVisits||0;
459
    t.tot+=r.totalVisits||0;
-
 
460
  });
-
 
461
  h+='<tr class="boardtot">'+
-
 
462
     '<td></td><td><b>Total</b><br><span style="color:#97a1b5;font-size:11px">'+rows.length+' executives</span></td><td></td>'+
-
 
463
     '<td class="num cnt">'+cdp(t.cp,t.dp,t.pp)+'</td>'+
-
 
464
     '<td class="num cnt">'+cdp(t.cl,t.dl,t.pl)+'</td>'+
-
 
465
     '<td class="num cnt cmuted">'+t.tot+'</td><td></td><td></td>'+
-
 
466
     '</tr>';
-
 
467
  rows.forEach(function(r){
460
    var rowcls = r.state==='NOT_STARTED' ? 'rowbad' : (r.idleAway?'rowwarn':'');
468
    var rowcls = r.state==='NOT_STARTED' ? 'rowbad' : (r.idleAway?'rowwarn':'');
461
    var A = r.userId+",'"+js(r.name)+"','"+date+"'";   // openExec args
469
    var A = r.userId+",'"+js(r.name)+"','"+date+"'";   // openExec args
462
    var go = 'onclick="openExec('+A+')"';
470
    var go = 'onclick="openExec('+A+')"';
463
    h+='<tr class="clickable '+rowcls+'" '+go+'>'+
471
    h+='<tr class="clickable '+rowcls+'" '+go+'>'+
464
       '<td style="text-align:center">'+(r.agendaFilled?'<span style="color:#2f9e44;font-size:15px">&#10003;</span>':'<span class="badflag" style="font-size:15px">&#10007;</span>')+'</td>'+
472
       '<td style="text-align:center">'+(r.agendaFilled?'<span style="color:#2f9e44;font-size:15px">&#10003;</span>':'<span class="badflag" style="font-size:15px">&#10007;</span>')+'</td>'+
Line 474... Line 482...
474
         '<button class="actbtn view" title="Journey detail" onclick="openExec('+A+')">&#128065;</button>'+
482
         '<button class="actbtn view" title="Journey detail" onclick="openExec('+A+')">&#128065;</button>'+
475
         '<button class="actbtn edit" title="Edit beat in Beat Planner" onclick="openPlanner('+r.authUserId+','+(r.beatId||0)+',\''+date+'\',\'edit\')">&#9998;</button>'+
483
         '<button class="actbtn edit" title="Edit beat in Beat Planner" onclick="openPlanner('+r.authUserId+','+(r.beatId||0)+',\''+date+'\',\'edit\')">&#9998;</button>'+
476
         '<button class="actbtn add" title="Assign / add visit in Beat Planner" onclick="openPlanner('+r.authUserId+','+(r.beatId||0)+',\''+date+'\',\'add\')">&#43;</button>'+
484
         '<button class="actbtn add" title="Assign / add visit in Beat Planner" onclick="openPlanner('+r.authUserId+','+(r.beatId||0)+',\''+date+'\',\'add\')">&#43;</button>'+
477
       '</td></tr>';
485
       '</td></tr>';
478
  });
486
  });
479
  h+='</tbody><tfoot><tr class="boardtot">'+
-
 
480
     '<td></td><td><b>Total</b><br><span style="color:#97a1b5;font-size:11px">'+rows.length+' executives</span></td><td></td>'+
-
 
481
     '<td class="num cnt">'+cdp(t.cp,t.dp,t.pp)+'</td>'+
-
 
482
     '<td class="num cnt">'+cdp(t.cl,t.dl,t.pl)+'</td>'+
-
 
483
     '<td class="num cnt cmuted">'+t.tot+'</td><td></td><td></td>'+
-
 
484
     '</tr></tfoot>';
-
 
485
  return h+'</table></div>';
487
  return h+'</tbody></table></div>';
486
}
488
}
487
// Funnel drill-down: each stage shows the executives who reached it AND the
489
// Funnel drill-down: each stage shows the executives who reached it AND the
488
// drop-off cohort that fell out at this step (the "−N …" sub-line). Filters
490
// drop-off cohort that fell out at this step (the "−N …" sub-line). Filters
489
// mirror the backend funnel conditions exactly (rows carry the per-stage flags).
491
// mirror the backend funnel conditions exactly (rows carry the per-stage flags).
490
var FUNNEL_META = {
492
var FUNNEL_META = {
491
  scheduled:    {title:'Scheduled executives', desc:'Have a beat today, or any activity', f:function(r){ return true; }},
-
 
492
  punchedIn:    {title:'Punched in',           desc:'Started the day (punch-in recorded)', f:function(r){ return r.punchedIn; },
493
  punchedIn:  {title:'Punched In',   desc:'Started the day (punch-in recorded)', f:function(r){ return r.punchedIn; }},
493
                 dropLabel:'Never started (scheduled, no punch-in)', drop:function(r){ return !r.punchedIn; }},
494
  travelling: {title:'Traveling',    desc:'On the road between stops',           f:function(r){ return r.state==='TRAVELLING'; }},
494
  madeCheckin:  {title:'Made a check-in',      desc:'Checked in at one or more stops', f:function(r){ return r.madeCheckin; },
495
  inStore:    {title:'In Store',     desc:'Currently checked in at a stop',      f:function(r){ return r.state==='IN_STORE'; }},
495
                 dropLabel:'Punched in but idle (no check-in)', drop:function(r){ return r.punchedIn && !r.madeCheckin; }},
-
 
496
  completedAll: {title:'Completed all visits', desc:'Finished every planned stop', f:function(r){ return r.completedAll; },
496
  idle:       {title:'Idle / Break', desc:'Idle off-store beyond the threshold', f:function(r){ return r.idleAway; }},
497
                 dropLabel:'Still mid-journey (not all stops done)', drop:function(r){ return r.madeCheckin && !r.completedAll; }},
-
 
498
  punchedOut:   {title:'Punched out',          desc:'Ended the day (punch-out recorded)', f:function(r){ return r.punchedOut; },
497
  punchedOut: {title:'Punched Out',  desc:'Ended the day (punch-out recorded)',  f:function(r){ return r.punchedOut; }}
499
                 dropLabel:'Still out (punched in, not punched out)', drop:function(r){ return r.punchedIn && !r.punchedOut; }}
-
 
500
};
498
};
501
function funnelRows(list, date){
499
function funnelRows(list, date){
502
  if(!list.length) return '<p class="desc">None.</p>';
500
  if(!list.length) return '<p class="desc">None.</p>';
503
  var h='<table><thead><tr><th>Executive / Beat</th><th>Punch In/Out</th><th class="num">Completed</th><th class="num">Planned</th><th>State</th></tr></thead><tbody>';
501
  var h='<table><thead><tr><th>Executive / Beat</th><th>Punch In/Out</th><th class="num">Completed</th><th class="num">Planned</th><th>State</th></tr></thead><tbody>';
504
  list.slice().sort(function(a,b){ return (''+a.name).localeCompare(b.name); }).forEach(function(r){
502
  list.slice().sort(function(a,b){ return (''+a.name).localeCompare(b.name); }).forEach(function(r){
Line 528... Line 526...
528
       funnelRows(dropped, date);
526
       funnelRows(dropped, date);
529
  }
527
  }
530
  h+='<p class="desc" style="margin-top:10px">Click a row to open that executive\'s journey detail.</p>';
528
  h+='<p class="desc" style="margin-top:10px">Click a row to open that executive\'s journey detail.</p>';
531
  md.innerHTML=h; mb.classList.add('show');
529
  md.innerHTML=h; mb.classList.add('show');
532
}
530
}
-
 
531
// Main-card drill-downs: roster of executives behind each top KPI card.
-
 
532
// 'drop' (optional) adds a second, highlighted section for the complementary
-
 
533
// cohort — e.g. who's scheduled but hasn't filled the agenda.
-
 
534
var CARD_META = {
-
 
535
  total:       {title:'Total Executives', desc:'Everyone in your team',        f:function(r){ return true; }},
-
 
536
  scheduled:   {title:'Scheduled PJP',     desc:'Executives with a beat today', f:function(r){ return r.hasBeat; }},
-
 
537
  agenda:      {title:'Agenda Filled',     desc:'Beats with the agenda filled', f:function(r){ return r.agendaFilled; },
-
 
538
                dropLabel:'Agenda not filled (scheduled)', drop:function(r){ return r.hasBeat && !r.agendaFilled; }},
-
 
539
  unscheduled: {title:'Unscheduled',       desc:'No PJP scheduled today',       f:function(r){ return !r.hasBeat; }}
-
 
540
};
-
 
541
// Renders an executive roster table (used by the card drill-downs).
-
 
542
function execRows(list, date){
-
 
543
  if(!list.length) return '<p class="desc">None.</p>';
-
 
544
  var h='<table><thead><tr><th>Executive</th><th>Beat</th><th>Agenda</th><th>Status</th></tr></thead><tbody>';
-
 
545
  list.forEach(function(r){
-
 
546
    var A=r.userId+",'"+js(r.name)+"','"+date+"'";
-
 
547
    var go = r.userId ? ' class="clickable" onclick="openExec('+A+')"' : '';
-
 
548
    var agenda = !r.hasBeat ? '<span style="color:#97a1b5">&mdash;</span>'
-
 
549
      : (r.agendaFilled ? '<span style="color:#2f9e44">&#10003;</span>' : '<span class="badflag">&#10007;</span>');
-
 
550
    h+='<tr'+go+'>'+
-
 
551
       '<td><b>'+esc(r.name)+'</b></td>'+
-
 
552
       '<td>'+esc(r.beatName||'-')+'</td>'+
-
 
553
       '<td style="text-align:center">'+agenda+'</td>'+
-
 
554
       '<td>'+esc(r.statusLabel||'')+'</td></tr>';
-
 
555
  });
-
 
556
  return h+'</tbody></table>';
-
 
557
}
-
 
558
function openCards(key){
-
 
559
  var m=CARD_META[key]; if(!m) return;
-
 
560
  var all=(window._execList||[]), date=window._boardDate||'';
-
 
561
  var list=all.filter(m.f);
-
 
562
  var dropped=m.drop ? all.filter(m.drop) : [];
-
 
563
  var mb=el('bj-modal-back'), md=el('bj-modal');
-
 
564
  var h='<button class="x" onclick="closeModal()">&times;</button><h3>'+esc(m.title)+'</h3>'+
-
 
565
        '<p class="desc">'+list.length+' '+(list.length===1?'executive':'executives')+' &middot; '+esc(m.desc)+'</p>'+
-
 
566
        execRows(list, date);
-
 
567
  if(m.drop){
-
 
568
    h+='<div class="sectionlabel" style="margin-top:18px;color:#e8590c">'+esc(m.dropLabel)+' &middot; '+dropped.length+'</div>'+
-
 
569
       execRows(dropped, date);
-
 
570
  }
-
 
571
  h+='<p class="desc" style="margin-top:10px">Click a row to open that executive\'s journey detail.</p>';
-
 
572
  md.innerHTML=h; mb.classList.add('show');
-
 
573
}
533
// Leads drill-down: lists every lead visit logged today (matches the KPI count).
574
// Leads drill-down: lists every lead visit logged today (matches the KPI count).
534
function openLeads(){
575
function openLeads(){
535
  var list=window._leadsList||[], date=window._boardDate||'';
576
  var list=window._leadsList||[], date=window._boardDate||'';
536
  var mb=el('bj-modal-back'), md=el('bj-modal');
577
  var mb=el('bj-modal-back'), md=el('bj-modal');
537
  var h='<button class="x" onclick="closeModal()">&times;</button><h3>Leads on route</h3>'+
578
  var h='<button class="x" onclick="closeModal()">&times;</button><h3>Leads on route</h3>'+