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.0"/> 6<title>MVS Foundation â Employee Portal</title> 7<link rel="preconnect" href="https://fonts.googleapis.com"> 8<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> 9<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/3.34.1/tabler-icons.min.css">
10<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
vendor: 1 bytes, line 10
10
11<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
11 12<style> 13 :root{ 14 --bg0:#08080a; --bg1:#111114; --navy:#17171c; --navy2:#25252b; 15 --gold:#c89b3c; --gold-soft:#e8c977; --gold-ink:#e8c977; 16 --txt:#eef2f8; --muted:#a9b6c9; --faint:#7c889a; 17 --line:rgba(255,255,255,.14); --card:rgba(255,255,255,.055); --card-2:rgba(255,255,255,.085); 18 --ok:#3ec77f; --ok-d:#1f9d57; --red:#e0533f; --warn:#e8c977; 19 } 20 body.night{ 21 --bg0:#04060a; --bg1:#0a0e16; --navy:#0f0f12; --navy2:#1e1e25; 22 --card:rgba(255,255,255,.05); --card-2:rgba(255,255,255,.075); --line:rgba(255,255,255,.09); 23 background:radial-gradient(1100px 600px at 12% -8%,#0a0a0c 0,transparent 58%), 24 linear-gradient(160deg,#06090f 0,#0a0e16 55%,#000 100%)!important;background-attachment:fixed} 25 body.night::before{opacity:.03} 26 body.night .topbar{background:rgba(0,0,0,.66)} 27 body.night .bpanel,body.night #toast,body.night .lcard,body.night .mcard{background:#0c1119} 28 body.night .login{background:radial-gradient(1100px 600px at 12% -8%,#0a0a0c 0,transparent 58%),linear-gradient(160deg,#06090f,#000)} 29 *{box-sizing:border-box;margin:0;padding:0} 30 body{font-family:'Outfit',sans-serif;color:var(--txt);min-height:100vh;padding-bottom:60px; 31 background:radial-gradient(1100px 600px at 12% -8%,#25252b 0,transparent 58%), 32 radial-gradient(900px 500px at 112% 8%,#08080a 0,transparent 55%), 33 linear-gradient(160deg,#111114 0,#17171c 48%,#08080a 100%);background-attachment:fixed} 34 body::before{content:"";position:fixed;inset:0;pointer-events:none;opacity:.04;z-index:0; 35 background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='6' height='6'><circle cx='1' cy='1' r='.6' fill='white'/></svg>");} 36 .wrap{max-width:1000px;margin:0 auto;padding:0 16px;position:relative;z-index:1} 37 /* topbar */ 38 .topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:13px;padding:13px 20px; 39 background:rgba(11,18,30,.72);backdrop-filter:blur(16px);border-bottom:1px solid var(--line)} 40 .topbar img{width:42px;height:42px;border-radius:11px;object-fit:cover;box-shadow:0 4px 14px -4px rgba(0,0,0,.6)} 41 #topLogo{border-radius:50%!important;transition:border .2s,box-shadow .2s} 42 #topLogo.has-photo{border:2px solid rgba(232,201,119,.55);box-shadow:0 2px 12px -3px rgba(232,201,119,.6)} 43 .tback{color:var(--gold-soft)} 44 .topbar .tt b{font-size:15px;font-weight:600;display:block;line-height:1.15} 45 .topbar .tt span{font-size:11px;color:var(--gold-soft);letter-spacing:.04em} 46 .topbar .sp{flex:1} 47 .tbtn{background:var(--card);border:1px solid var(--line);color:var(--muted);width:40px;height:40px;border-radius:11px;cursor:pointer;font-size:17px;display:grid;place-items:center;transition:.15s;position:relative} 48 .tbtn:hover{color:var(--gold-soft);border-color:rgba(200,155,60,.4)} 49 .bell{background:linear-gradient(180deg,rgba(224,83,63,.22),rgba(224,83,63,.1))!important;border-color:rgba(224,83,63,.5)!important;color:#ffd9d0!important;animation:bg 2.4s ease-in-out infinite} 50 @keyframes bg{0%,100%{box-shadow:0 0 0 0 rgba(224,83,63,.5)}50%{box-shadow:0 0 0 7px rgba(224,83,63,0)}} 51 .bdot{position:absolute;top:-5px;right:-4px;min-width:18px;height:18px;padding:0 4px;border-radius:9px;background:#e0533f;color:#fff;font-size:10.5px;font-weight:700;display:grid;place-items:center;box-shadow:0 0 0 2px #111114} 52 .spin{animation:sp 1s linear infinite}@keyframes sp{to{transform:rotate(360deg)}} 53 /* bell panel */ 54 .bpanel{position:absolute;top:60px;right:16px;width:min(360px,92vw);max-height:60vh;overflow:auto;background:#17171c;border:1px solid var(--line);border-radius:14px;box-shadow:0 22px 55px -12px rgba(0,0,0,.8);padding:7px;display:none;z-index:130} 55 .bitem{background:rgba(255,255,255,.06);border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin:6px 4px;font-size:13px;line-height:1.5} 56 .bitem.urgent{border-color:rgba(224,83,63,.5);background:rgba(224,83,63,.1)} 57 .bitem b{display:block;margin-bottom:2px} .bitem .bt{font-size:11px;color:var(--faint);margin-top:4px} 58 .btag{font-size:10px;background:#e0533f;color:#fff;padding:1px 7px;border-radius:6px;margin-left:6px}
59 .ntag-new{display:inline-block;font-size:9.5px;font-weight:800;letter-spacing:.06em;background:linear-gradient(135deg,#2f8f5b,#4ec77f);color:#fff;padding:2px 8px;border-radius:20px;margin-left:8px;vertical-align:middle;animation:blinkNew 1.1s ease-in-out infinite} 60 @keyframes blinkNew{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(78,199,127,.55)}50%{opacity:.5;box-shadow:0 0 0 5px rgba(78,199,127,0)}} 61 /* CRM strip */ 62 .crm{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:20px 0 6px} 63 @media(max-width:760px){.crm{grid-template-columns:repeat(2,1fr)}.crm .ccard:last-child:nth-child(odd){grid-column:1/-1}} 64 .ccard{background:linear-gradient(160deg,rgba(255,255,255,.075),rgba(255,255,255,.025));border:1px solid rgba(255,255,255,.13);border-radius:16px;padding:16px 18px;position:relative;overflow:hidden;box-shadow:0 10px 30px -16px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.05);transition:transform .18s ease,border-color .18s ease} 65 .ccard:hover{border-color:rgba(200,155,60,.5);transform:translateY(-2px)} 66 .ccard::before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;background:linear-gradient(180deg,var(--gold-soft),var(--gold))} 67 .ccard::after{content:"";position:absolute;right:-24px;top:-24px;width:82px;height:82px;border-radius:50%;background:radial-gradient(circle,rgba(200,155,60,.10),transparent 70%);pointer-events:none} 68 .ccard .cl{font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.05em;display:flex;align-items:center;gap:6px} 69 .ccard .cv{font-size:17px;font-weight:700;margin-top:8px;line-height:1.3;letter-spacing:-.1px} 70 .ccard .cv small{font-size:12px;font-weight:500;color:var(--muted)} 71 .stars i{color:#3a3a44;font-size:15px}.stars i.on{color:var(--gold-soft)} 72 /* hero */ 73 .ref{background:linear-gradient(150deg,rgba(200,155,60,.14),rgba(92,92,104,.25));border:1px solid rgba(200,155,60,.35);border-radius:18px;padding:24px 26px;margin:14px 0;box-shadow:0 0 40px -18px rgba(200,155,60,.4)} 74 .ref h1{font-size:23px;font-weight:700} .ref h1 .wave{display:inline-block;animation:wv 2.2s ease-in-out infinite;transform-origin:70% 70%} 75 @keyframes wv{0%,60%,100%{transform:rotate(0)}10%,30%{transform:rotate(14deg)}20%{transform:rotate(-8deg)}} 76 .chip{display:inline-flex;align-items:center;gap:7px;margin-top:11px;font-size:13px;color:var(--gold-soft);background:rgba(200,155,60,.12);border:1px solid rgba(200,155,60,.35);padding:6px 13px;border-radius:999px} 77 .ref p{color:var(--muted);font-size:14px;margin-top:12px;line-height:1.6} 78 .ref-top{display:flex;align-items:center;gap:14px} 79 .ref-av{width:58px;height:58px;border-radius:50%;background:linear-gradient(140deg,#f2d98e,var(--gold-soft,#e8c977) 42%,#c2922f);color:#1a1305;display:grid;place-items:center;font-weight:800;font-size:26px;flex:none;letter-spacing:-.5px;box-shadow:0 9px 24px -9px rgba(232,201,119,.7),0 0 0 4px rgba(200,155,60,.13),inset 0 2px 5px rgba(255,255,255,.45),inset 0 -4px 7px rgba(120,85,20,.28)} 80 .ref-hi{flex:1;min-width:0} 81 .ref-hi p{margin-top:6px} 82 .ref-chips{display:flex;flex-wrap:wrap;gap:9px;margin-top:16px} 83 .ref-chips .chip{margin-top:0} 84 /* progress */ 85 .card{background:linear-gradient(160deg,rgba(255,255,255,.055),rgba(255,255,255,.02));border:1px solid rgba(255,255,255,.11);border-radius:16px;padding:20px 22px;margin:14px 0;box-shadow:0 8px 28px -18px rgba(0,0,0,.6)} 86 .sectitle{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--gold-soft);margin-bottom:14px;display:flex;align-items:center;gap:9px} 87 .progbar{height:9px;border-radius:6px;background:rgba(255,255,255,.08);overflow:hidden;margin-bottom:14px} 88 .progfill{height:100%;background:linear-gradient(90deg,var(--gold),var(--gold-soft));border-radius:6px;transition:width .6s} 89 .pstep{display:flex;align-items:center;gap:11px;padding:8px 0;font-size:14px} 90 .pi{width:26px;height:26px;border-radius:50%;border:1px solid var(--line);display:grid;place-items:center;font-size:13px;flex:none} 91 .pstep.done .pi{background:var(--ok-d);border-color:transparent;color:#fff} .pstep.done{color:var(--txt)} 92 .pstep.todo{color:var(--warn);font-weight:600} .pstep.todo .pi{border-color:rgba(232,201,119,.6);color:var(--warn);animation:bg 1.3s ease-in-out infinite} 93 .pmsg{margin-top:10px;font-size:13px} 94 /* tabs */ 95 .tabs{display:flex;flex-wrap:wrap;gap:8px;padding:4px 0 2px;margin-top:6px} 96 .tab{white-space:nowrap;background:var(--card);border:1px solid var(--line);color:var(--muted);padding:10px 15px;border-radius:11px;cursor:pointer;font-family:inherit;font-size:13.5px;display:flex;align-items:center;gap:7px;transition:.15s} 97 .tab:hover{color:var(--gold-soft)} .tab.active{background:linear-gradient(180deg,var(--gold-soft),var(--gold));
97color:#1a1207;border-color:transparent;font-weight:600} 98 .panel{display:none;animation:fade .3s} .panel.show{display:block}@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}} 99 /* doc rows */ 100 .drow{display:flex;align-items:center;gap:14px;background:var(--card-2);border:1px solid var(--line);border-radius:13px;padding:15px 17px;margin-bottom:11px} 101 .drow .ic{width:42px;height:42px;border-radius:11px;display:grid;place-items:center;font-size:20px;flex:none;background:rgba(200,155,60,.16);color:var(--gold-soft)} 102 .drow .dt{flex:1} .drow .dt b{font-size:14.5px} .drow .dt span{display:block;font-size:12px;color:var(--faint);margin-top:2px} 103 .dl{background:linear-gradient(180deg,var(--gold-soft),var(--gold));color:#1a1207;border:none;border-radius:10px;padding:9px 15px;font-family:inherit;font-weight:600;font-size:13px;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:6px;white-space:nowrap} 104 .dl.lock{background:rgba(255,255,255,.07);color:var(--faint);cursor:not-allowed} 105 .dl.ghost{background:var(--card);color:var(--gold-soft);border:1px solid var(--line)} 106 .empty{color:var(--faint);font-size:13.5px;padding:14px;text-align:center} 107 .kv{display:grid;grid-template-columns:auto 1fr;gap:9px 18px;font-size:14px}.kv .k{color:var(--faint)} 108 .kv .k i{color:var(--gold-soft);margin-right:7px;font-size:15px;vertical-align:-1px} 109 .dept-sec{grid-column:1/-1;display:flex;align-items:center;gap:8px;color:var(--gold-soft);font-weight:800;font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;margin:6px 0 2px;padding-bottom:7px;border-bottom:1px solid var(--line)} 110 .dept-sec:not(:first-child){margin-top:14px} 111 .kv .vhi{color:var(--gold-soft);font-weight:800} 112 .dept-block{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:15px;padding:14px 16px;margin-bottom:14px;box-shadow:0 8px 26px -18px rgba(0,0,0,.7)} 113 .dept-block:last-of-type{margin-bottom:0} 114 .dept-bh{display:flex;align-items:center;gap:8px;color:var(--gold-soft);font-weight:800;font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;margin-bottom:8px;padding-bottom:9px;border-bottom:1px solid var(--line)} 115 .dept-bh i{font-size:15px} 116 .dq{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:8px 0;border-bottom:1px dashed rgba(255,255,255,.06)} 117 .dq:last-child{border-bottom:none} 118 .dq-k{color:var(--faint);font-size:13.5px;display:flex;align-items:center;gap:8px;flex-shrink:0} 119 .dq-k i{color:var(--gold-soft);font-size:15px;vertical-align:-1px} 120 .dq-v{font-weight:600;text-align:right;font-size:14px} 121 .dq-v.hi{color:var(--gold-soft);font-weight:800} 122 .dq-long{padding:9px 0;border-bottom:1px dashed rgba(255,255,255,.06)} 123 .dq-long .dq-k{margin-bottom:7px} 124 .dq-longv{font-size:13.5px;line-height:1.62;color:var(--txt);font-weight:500;text-align:left} 125 .dept-brows>.dq:last-child,.dept-brows>.dq-long:last-child{border-bottom:none;padding-bottom:0} 126 @media(max-width:420px){.dq{gap:10px}.dq-k{font-size:12.5px}.dq-v{font-size:13px}.dq-longv{font-size:13px}} 127 .sal-annex{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:16px;padding:16px 16px 14px;margin-bottom:18px} 128 .sa-head{display:flex;align-items:center;gap:9px;font-weight:800;font-size:15px;margin-bottom:14px} 129 .sa-head i{color:var(--gold-soft)} 130 .sa-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px} 131 @media(max-width:620px){.sa-grid{grid-template-columns:1fr}} 132 .sa-col{background:rgba(0,0,0,.16);border:1px solid var(--line);border-radius:13px;overflow:hidden} 133 .sa-ctitle{font-weight:800;font-size:12px;letter-spacing:.06em;text-transform:uppercase;padding:10px 14px;color:#fff} 134 .sa-ctitle.earn{background:rgba(58,160,110,.16);color:#7fe0ad;border-bottom:1px solid rgba(58,160,110,.22)} 135 .sa-ctitle.ded{background:rgba(224,120,60,.14);color:#ffb488;border-bottom:1px solid rgba(224,120,60,.22)} 136 .sa-row{display:flex;justify-content:space-between;gap:10px;padding:9px 14px;font-size:13.5px;border-bottom:1px solid rgba(255,255,255,.05)} 137 .sa-row span{color:var(--muted)}.sa-row b{color:#fff;font-weight:600} 138 .sa-total{display:flex;justify-content:space-between;gap:10px;padding:11px 14px;font-weight:800;font-size:13.5px} 139 .sa-total.earn{background:rgba(58,160,110,.1);color:#7fe0ad} 140 .sa-total.ded{background:rgba(224,120,60,.1);color:#ffb488} 141 .sa-net{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:12px;background:linear-gradient(135deg,#153a2b,#0f2a1f);border:1px solid rgba(58,160,110,.3);border-left:4px solid #3aa06e;border-radius:13px;padding:13px 16px} 142 .sa-net span{color:#c9e8d8;font-weight:700;font-size:13.5px} 143 .sa-net b{color:#8fe9b5;font-weight:800;font-size:20px} 144 .sa-sub{margin-top:10px;font-size:12px;color:var(--faint);line-height:1.55} 145 .sa-sub b{color:var(--gold-soft)} 146 .inp,textarea,select{width:100%;background:rgba(0,0,0,.2);border:1px solid var(--line);border-radius:11px;padding:11px 13px;color:#fff;font-family:inherit;font-size:14px;margin-top:6px} 147 .inp:focus,textarea:focus,select:focus{outline:none;border-color:var(--gold)} 148 textarea{resize:vertical;min-height:64px} label{font-size:12.5px;color:var(--muted)} 149 .btn{background:linear-gradient(180deg,var(--gold-soft),var(--gold));
149color:#1a1207;border:none;border-radius:11px;padding:12px 18px;font-family:inherit;font-weight:600;font-size:14px;cursor:pointer;margin-top:10px} 150 .btn:disabled{filter:grayscale(.4) brightness(.85)} 151 .stars-in i{font-size:26px;color:#3a3a44;cursor:pointer;transition:.1s}.stars-in i.on{color:var(--gold-soft)} 152 .slip{background:var(--card-2);border:1px solid var(--line);border-radius:13px;padding:15px 17px;margin-bottom:11px;display:flex;align-items:center;gap:13px;flex-wrap:wrap} 153 .slip .m{font-weight:600;font-size:15px;flex:1} 154 .cmp-item{background:var(--card-2);border:1px solid var(--line);border-radius:12px;padding:12px 14px;margin-bottom:9px;font-size:13px} 155 .cmp-item .ctop{display:flex;justify-content:space-between;gap:8px;margin-bottom:6px} 156 .cs{font-size:11px;padding:2px 9px;border-radius:7px}.cs.open{background:rgba(232,201,119,.2);color:var(--gold-soft)}.cs.resolved{background:rgba(62,199,127,.2);color:var(--ok)} 157 .creply{margin-top:7px;padding:8px 11px;background:rgba(62,199,127,.08);border-left:2px solid var(--ok);border-radius:6px;color:#cfeede;font-size:12.5px} 158 .reqmore{margin-top:4px;background:var(--card);border:1px solid var(--line);color:var(--gold-soft);font-family:inherit;font-size:12.5px;font-weight:600;border-radius:9px;padding:8px 14px;cursor:pointer} 159 /* login */ 160 .login{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;padding:20px; 161 background:radial-gradient(1100px 600px at 12% -8%,#25252b 0,transparent 58%),radial-gradient(900px 500px at 112% 8%,#08080a 0,transparent 55%),linear-gradient(160deg,#111114 0,#17171c 48%,#08080a 100%)} 162 .login.hidden{display:none} 163 .lcard{width:min(400px,100%);background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid rgba(255,255,255,.12);border-radius:24px;padding:38px 32px 30px;backdrop-filter:blur(16px);box-shadow:0 40px 90px -34px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.05);text-align:center;position:relative;overflow:hidden} 164 .lcard::before{content:"";position:absolute;top:0;left:50%;transform:translateX(-50%);width:118px;height:3px;border-radius:0 0 6px 6px;background:linear-gradient(90deg,transparent,var(--gold),transparent)} 165 .lcard img{width:74px;height:74px;border-radius:20px;object-fit:cover;margin:8px auto 16px;display:block;box-shadow:0 12px 34px -10px rgba(0,0,0,.7)} 166 .lcard h2{font-size:22px;font-weight:700;letter-spacing:-.01em} .lcard p{color:var(--faint);font-size:12.5px;margin:6px 0 24px} 167 .lcard .inp{text-align:left;margin-bottom:13px;padding:13px 15px} 168 .lcard .inp:focus{border-color:var(--gold);outline:none} 169 .lrel{position:relative} .ltgl{position:absolute;right:12px;top:55%;transform:translateY(-50%);background:none;border:none;color:#8b98ab;cursor:pointer;font-size:12px;font-family:inherit} 170 .lmsg{color:#ff9c8b;font-size:13px;min-height:18px;margin-top:8px} 171 .lbtn{width:100%;padding:14px;border:none;border-radius:12px;background:linear-gradient(180deg,var(--gold-soft),var(--gold));color:#1a1207;font-family:inherit;font-weight:700;font-size:15px;cursor:pointer} 172 /* toast */ 173 #toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#17171c;border:1px solid var(--line);color:#fff;padding:12px 18px;border-radius:12px;font-size:13.5px;display:none;z-index:200;box-shadow:0 16px 40px -12px rgba(0,0,0,.7)} 174 .pdfstage{position:fixed;left:-3000px;top:0} 175 /* collapsible dropdown for long lists */ 176 .acc{border:1px solid var(--line);border-radius:12px;margin-bottom:9px;overflow:hidden;background:var(--card-2)} 177 .acc-h{display:flex;align-items:center;gap:11px;padding:13px 15px;cursor:pointer;user-select:none} 178 .acc-h .ai{width:34px;height:34px;border-radius:9px;display:grid;place-items:center;flex:none;background:rgba(200,155,60,.16);color:var(--gold-soft);font-size:17px} 179 .acc-h .at{flex:1;min-width:0} .acc-h .at b{font-size:14px;display:block} .acc-h .at span{font-size:11.5px;color:var(--faint)} 180 .acc-h .chev{color:var(--faint);transition:.22s;flex:none} .acc.open .acc-h .chev{transform:rotate(180deg)} 181 .acc-b{display:grid;grid-template-rows:0fr;transition:grid-template-rows .24s cubic-bezier(.4,0,.2,1)} 182 .acc.open .acc-b{grid-template-rows:1fr} 183 .acc-bi{overflow:hidden;min-height:0;padding:0 15px} 184 .acc.open .acc-bi{padding:2px 15px 15px} 185 .tabs{display:none!important} 186 #eVeil{position:fixed;inset:0;background:rgba(0,0,0,.55);backdrop-filter:blur(2px);opacity:0;visibility:hidden;transition:.25s;z-index:140} 187 #eVeil.on{opacity:1;visibility:visible} 188 #eSide{position:fixed;top:0;left:0;height:100%;width:290px;max-width:86vw;background:#14141a;border-right:1px solid var(--line);transform:translateX(-102%);transition:transform .28s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;box-shadow:0 0 60px rgba(0,0,0,.5);z-index:141} 189 #eSide.on{transform:none} 190 body.light #eSide{background:#f7f8fa} 191 .eside-h{display:flex;align-items:center;gap:11px;padding:16px 16px 14px;border-bottom:1px solid var(--line)} 192 .eside-logo{width:34px;height:34px;border-radius:9px;background:linear-gradient(135deg,var(--gold-soft),#c99b3c);
192color:#1a1305;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:11.5px;flex:none} 193 .eside-h .et{flex:1;line-height:1.25} 194 .eside-h .et b{font-size:13.5px;display:block} 195 .eside-h .et span{font-size:10px;color:var(--faint);letter-spacing:.6px} 196 .eside-x{background:transparent;border:none;color:var(--muted);cursor:pointer;font-size:24px;line-height:1;padding:2px 6px} 197 .eside-scroll{flex:1;overflow-y:auto;padding:10px 10px 26px} 198 .snav-grp{margin-bottom:3px} 199 .snav-head{display:flex;align-items:center;gap:11px;padding:11px 12px;border-radius:11px;cursor:pointer;user-select:none;color:#e7ecf3;font-weight:600;font-size:13.5px;transition:.15s} 200 body.light .snav-head{color:#1c2230} 201 .snav-head:hover{background:var(--card)} 202 .snav-head .gi{color:var(--gold-soft);font-size:18px;flex:none} 203 .snav-head .gl{flex:1} 204 .snav-head .gc{color:var(--faint);transition:.22s;flex:none;font-size:16px} 205 .snav-grp.open .snav-head .gc{transform:rotate(180deg)} 206 .snav-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows .22s cubic-bezier(.4,0,.2,1)} 207 .snav-grp.open .snav-body{grid-template-rows:1fr} 208 .snav-bi{overflow:hidden;min-height:0} 209 .snav-item{display:flex;align-items:center;gap:11px;padding:10px 12px 10px 15px;margin:2px 0 2px 13px;border-left:2px solid var(--line);color:var(--muted);cursor:pointer;font-size:13px;border-radius:0 9px 9px 0;transition:.15s} 210 .snav-item:hover{background:var(--card);color:#fff} 211 body.light .snav-item:hover{color:#111} 212 .snav-item.active{background:linear-gradient(90deg,rgba(232,201,119,.16),transparent);color:var(--gold-soft);border-left-color:var(--gold-soft);font-weight:600} 213 /* dept: equal cols desktop, stack on phone */ 214 #deptKv{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;align-items:start} 215 @media(max-width:700px){#deptKv{grid-template-columns:1fr}} 216 /* desktop calendar: compact, not stretched */ 217 @media(min-width:760px){ 218 #attCalendar,.att-stats,.acal-head,.acal-legend{max-width:600px;margin-left:auto;margin-right:auto} 219 } 220 /* mobile topbar compact (footer removed, buttons back in bar) */ 221 @media(max-width:560px){ 222 .tbtn{width:34px;height:34px;font-size:15px} 223 .topbar{gap:6px;padding:9px 11px} 224 .topbar .tt span{display:none} 225 .topbar img{width:34px;height:34px} 226 } 227 /* premium micro-interactions + panel entry */ 228 @keyframes fadeUp{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}} 229 .panel{animation:fadeUp .34s cubic-bezier(.4,0,.2,1)} 230 .dept-block{transition:transform .16s ease,border-color .16s ease} 231 .dept-block:hover{transform:translateY(-2px);border-color:rgba(232,201,119,.3)} 232 .snav-item,.snav-head,.snav-dash,.tbtn,.ef-btn,.dl,.btn{transition:all .16s ease} 233 .btn:active,.dl:active,.p2-btn:active{transform:scale(.97)} 234 .snav-dash{margin:2px 0 10px 0;border-left:none;border-radius:11px;padding:11px 13px;font-weight:600;font-size:13.5px;color:#e7ecf3} 235 body.light .snav-dash{color:#1c2230} 236 .snav-dash .ti{font-size:18px;color:var(--gold-soft)} 237 .snav-dash:hover{background:var(--card)} 238 .snav-dash.active{background:linear-gradient(90deg,rgba(232,201,119,.2),rgba(232,201,119,.04));color:var(--gold-soft);border-left:none} 239 /* ===== DESKTOP: permanent sidebar (admin-style) ===== */ 240 @media(min-width:900px){ 241 .hamb{display:none!important} 242 #eVeil{display:none!important} 243 #eSide{position:fixed;top:60px;height:calc(100vh - 60px);width:266px;max-width:266px;transform:none!important;box-shadow:none;z-index:20} 244 .eside-h{display:none} 245 .eside-scroll{padding-top:16px} 246 .wrap{max-width:1060px;margin-left:max(266px, calc((100vw - 1060px)/2));margin-right:auto;padding-left:34px;padding-right:34px} 247 } 248 /* premium card polish */ 249 .panel.card{box-shadow:0 18px 46px -28px rgba(0,0,0,.7)} 250 .sectitle{letter-spacing:.02em} 251 .snav-item .ti{font-size:16px;flex:none} 252 .eside-foot{display:none!important;border-top:1px solid var(--line);padding:12px;display:grid;grid-template-columns:1fr 1fr;gap:8px} 253 .ef-btn{background:var(--card);border:1px solid var(--line);color:var(--muted);border-radius:11px;padding:11px;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:7px;transition:.15s} 254 .ef-btn:hover{color:#fff;border-color:var(--gold-soft)} 255 .ef-btn.danger{grid-column:1/-1;color:#ff9c8b;border-color:rgba(224,83,63,.3)} 256 .punch2{background:linear-gradient(140deg,rgba(200,155,60,.13),rgba(255,255,255,.02));border:1px solid rgba(200,155,60,.3);border-radius:18px;padding:16px 18px;margin:16px 0;box-shadow:0 14px 38px -20px rgba(200,155,60,.55)} 257 .p2-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap} 258 .p2-title{font-size:12px;color:var(--gold-soft);font-weight:800;text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;gap:7px} 259 .p2-badge{font-size:11.5px;font-weight:700;padding:5px 11px;border-radius:999px;display:inline-flex;align-items:center;gap:6px} 260 .p2-badge.wait{background:rgba(255,255,255,.06);color:var(--muted)} 261 .p2-badge.live,.p2-badge.done{background:rgba(126,224,168,.14);color:#7fe0a8} 262 .p2-dot{width:7px;height:7px;border-radius:50%;background:#7fe0a8;animation:p2pulse 1.6s infinite} 263 @keyframes p2pulse{0%{box-shadow:0 0 0 0 rgba(127,224,168,.5)}70%{box-shadow:0 0 0 7px rgba(127,224,168,0)}100%{box-shadow:0 0 0 0 rgba(127,224,168,0)}} 264 .p2-times{display:flex;align-items:stretch;gap:9px;margin:14px 0} 265 .p2-time{flex:1;background:rgba(255,255,255,.04);border:1px solid var(--line);border-radius:13px;padding:11px 10px;text-align:center;min-width:0} 266 body.light .p2-time{background:rgba(0,0,0,.03)}
267 .p2-time span{display:block;font-size:9.5px;letter-spacing:.09em;color:var(--faint);font-weight:800} 268 .p2-time b{display:block;font-size:17px;margin-top:3px;font-variant-numeric:tabular-nums} 269 .p2-time.in b{color:#7fe0a8}.p2-time.out b{color:#ff9c8b}.p2-time.worked b{color:var(--gold-soft)} 270 .p2-time.worked.live{background:linear-gradient(140deg,rgba(232,201,119,.14),rgba(255,255,255,.02));border-color:rgba(232,201,119,.4);box-shadow:0 8px 22px -14px rgba(232,201,119,.6)} 271 .p2-time.worked.live b{color:var(--gold-soft);letter-spacing:.6px;font-weight:800;text-shadow:0 0 14px rgba(232,201,119,.5);animation:p2glow 2s ease-in-out infinite} 272 @keyframes p2glow{0%,100%{text-shadow:0 0 10px rgba(232,201,119,.35)}50%{text-shadow:0 0 18px rgba(232,201,119,.65)}} 273 .p2-sep{display:flex;align-items:center;color:var(--faint);font-size:15px} 274 .p2-foot{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap} 275 .p2-month{flex:1;min-width:150px} 276 .p2-mlbl{font-size:12px;color:var(--muted)}.p2-mlbl b{color:#fff} 277 body.light .p2-mlbl b{color:#111} 278 .p2-bar{height:7px;border-radius:20px;background:rgba(255,255,255,.09);margin-top:6px;overflow:hidden} 279 .p2-bar span{display:block;height:100%;
279background:linear-gradient(90deg,var(--gold-soft),#e8c977);border-radius:20px} 280 .p2-btn{border:none;border-radius:12px;padding:12px 22px;font-weight:700;font-size:14.5px;font-family:inherit;cursor:pointer;display:inline-flex;align-items:center;gap:8px} 281 .p2-btn.in{background:linear-gradient(135deg,#7fe0a8,#5bc48a);color:#0c2417} 282 .p2-btn.out{background:linear-gradient(135deg,#ffb38a,#ff8f6b);color:#3a1508} 283 .dir-dep{font-size:11.5px;font-weight:800;color:var(--gold-soft);text-transform:uppercase;letter-spacing:.08em;margin:16px 0 8px;display:flex;align-items:center;gap:8px} 284 .dir-dep span{background:rgba(232,201,119,.15);border-radius:20px;padding:1px 8px;font-size:11px} 285 .dir-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px} 286 .dir-card{display:flex;align-items:center;gap:12px;background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:13px;padding:12px 14px;transition:transform .16s ease,border-color .16s ease} 287 .dir-card:hover{transform:translateY(-2px);border-color:rgba(232,201,119,.3)} 288 .dir-av{width:40px;height:40px;border-radius:11px;background:linear-gradient(135deg,var(--gold-soft),#c99b3c);color:#1a1305;display:grid;place-items:center;font-weight:800;font-size:16px;flex:none} 289 .dir-info{min-width:0} 290 .dir-info b{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} 291 .dir-info span{font-size:12px;color:var(--muted)} 292 .org-children{margin-left:20px;padding-left:18px;border-left:2px solid var(--line);margin-top:2px} 293 .org-card{display:flex;align-items:center;gap:11px;background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:13px;padding:11px 14px;margin:5px 0;transition:transform .16s,border-color .16s} 294 .org-card:hover{transform:translateX(2px);border-color:rgba(232,201,119,.3)} 295 .org-av{width:38px;height:38px;border-radius:10px;background:linear-gradient(135deg,var(--gold-soft),#c99b3c);color:#1a1305;display:grid;place-items:center;font-weight:800;font-size:15px;flex:none} 296 .org-info{flex:1;min-width:0} 297 .org-info b{display:block;font-size:14px} 298 .org-info span{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block} 299 .org-count{background:rgba(232,201,119,.15);color:var(--gold-soft);border-radius:20px;padding:2px 9px;font-size:11.5px;font-weight:700;flex:none} 300 .tl{position:relative;padding-left:8px} 301 .tl-item{position:relative;padding:0 0 20px 40px} 302 .tl-item::before{content:"";position:absolute;left:15px;top:30px;bottom:-2px;width:2px;background:var(--line)} 303 .tl-item:last-child::before{display:none} 304 .tl-dot{position:absolute;left:0;top:2px;width:32px;height:32px;border-radius:10px;display:grid;place-items:center;color:#141419;font-size:16px;box-shadow:0 4px 14px -6px rgba(0,0,0,.6)} 305 .tl-body{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:13px;padding:12px 15px;transition:transform .16s,border-color .16s} 306 .tl-body:hover{transform:translateY(-2px);border-color:rgba(232,201,119,.3)} 307 .tl-top{display:flex;align-items:center;gap:8px}.tl-top b{font-size:14.5px} 308 .tl-pin{color:var(--gold-soft);font-size:13px} 309 .tl-date{font-size:12px;color:var(--faint);margin-top:2px} 310 .tl-desc{font-size:13.5px;color:var(--muted);margin-top:7px;line-height:1.6} 311 .kb-cats{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:14px} 312 .kb-cat{background:var(--card);border:1px solid var(--line);color:var(--muted);border-radius:20px;padding:6px 14px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;transition:.15s} 313 .kb-cat:hover{color:#fff} 314 .kb-cat.on{background:linear-gradient(135deg,var(--gold-soft),#d9b65e);color:#1a1305;border-color:var(--gold-soft)} 315 .kb-acc{margin-bottom:8px} 316 .kb-content{font-size:13.5px;color:var(--muted);line-height:1.7} 317 .kb-tags{margin-top:10px;font-size:12px;color:var(--faint)} 318 .mtg-date{font-size:11.5px;font-weight:800;color:var(--gold-soft);text-transform:uppercase;letter-spacing:.06em;margin:16px 0 8px} 319 .mtg-card{display:flex;gap:12px;background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:13px;padding:12px 14px;margin-bottom:8px;transition:transform .16s,border-color .16s} 320 .mtg-card:hover{transform:translateY(-2px);border-color:rgba(232,201,119,.3)} 321 .mtg-ic{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;color:#141419;font-size:17px;flex:none} 322 .mtg-body{min-width:0;flex:1}.mtg-body b{font-size:14.5px} 323 .mtg-meta{font-size:12.5px;color:var(--muted);margin-top:2px} 324 .mtg-desc{font-size:13px;color:var(--muted);margin-top:6px;line-height:1.5} 325 .mtg-link{display:inline-flex;align-items:center;gap:6px;margin-top:8px;color:var(--gold-soft);font-size:13px;font-weight:600;text-decoration:none} 326 .mtg-cd{display:flex;align-items:center;gap:5px;margin-top:11px;flex-wrap:wrap} 327 .cd-lbl{font-size:10px;color:var(--faint);text-transform:uppercase;letter-spacing:.06em;font-weight:700;margin-right:4px} 328 .cd-b{background:linear-gradient(160deg,rgba(232,201,119,.16),rgba(232,201,119,.04));border:1px solid rgba(232,201,119,.28);border-radius:9px;padding:5px 0;text-align:center;min-width:40px} 329 .cd-b b{display:block;font-size:16px;color:var(--gold-soft);font-variant-numeric:tabular-nums;line-height:1.1;font-weight:800} 330 .cd-b span{font-size:8.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.04em} 331 .cd-s{color:var(--gold-soft);font-weight:800;opacity:.5;font-size:15px;margin-top:-8px} 332 .mtg-live{display:inline-flex;align-items:center;gap:7px;color:#7fe0a8;font-weight:700;font-size:13px;margin-top:10px} 333 .mtg-livedot{width:8px;height:8px;border-radius:50%;background:#7fe0a8;animation:p2pulse 1.5s infinite} 334 #gsOverlay{position:fixed;inset:0;background:rgba(0,0,0,.6);backdrop-filter:blur(3px);opacity:0;visibility:hidden;transition:.2s;z-index:160;display:flex;align-items:flex-start;justify-content:center;padding:60px 16px} 335 #gsOverlay.on{opacity:1;visibility:visible} 336 .gs-box{width:100%;max-width:560px;background:#15151b;border:1px solid var(--line);border-radius:16px;box-shadow:0 30px 70px -20px rgba(0,0,0,.7);overflow:hidden;transform:translateY(-10px);transition:.25s} 337 body.light .gs-box{background:#fff} 338 #gsOverlay.on .gs-box{transform:none} 339 .gs-in{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--line)} 340 .gs-in .ti{color:var(--gold-soft);font-size:20px}
341 .gs-in input{flex:1;background:transparent;border:none;outline:none;color:inherit;font-size:15.5px;font-family:inherit} 342 .gs-x{background:transparent;border:none;color:var(--muted);font-size:24px;cursor:pointer;line-height:1} 343 #gsResults{max-height:60vh;overflow-y:auto;padding:8px} 344 .gs-hint{padding:24px 16px;text-align:center;color:var(--faint);font-size:13.5px} 345 .gs-cat{font-size:10.5px;font-weight:800;color:var(--gold-soft);text-transform:uppercase;letter-spacing:.07em;padding:10px 10px 5px} 346 .gs-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:11px;cursor:pointer;transition:background .15s} 347 .gs-item:hover{background:var(--card)} 348 .gs-item>.ti:first-child{color:var(--muted);font-size:18px;flex:none} 349 .gs-txt{flex:1;min-width:0}.gs-txt b{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} 350 .gs-txt span{font-size:12px;color:var(--faint)} 351 .gs-go{color:var(--faint);font-size:15px;flex:none} 352 .fm-card{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:15px;padding:16px 17px;margin-bottom:14px} 353 .fm-card.done{opacity:.75} 354 .fm-h{display:flex;align-items:center;justify-content:space-between;gap:10px}.fm-h b{font-size:15.5px} 355 .fm-done{color:#7fe0a8;font-size:12.5px;font-weight:700;display:inline-flex;align-items:center;gap:5px} 356 .fm-desc{color:var(--muted);font-size:13px;margin:6px 0 12px;line-height:1.5} 357 .fm-field{margin-bottom:13px} 358 .fm-lbl{display:block;font-size:13px;font-weight:600;margin-bottom:6px} 359 .fm-opts{display:flex;flex-direction:column;gap:7px} 360 .fm-opt{display:flex;align-items:center;gap:8px;font-size:13.5px;color:var(--muted);cursor:pointer} 361 .fm-submit{margin-top:6px} 362 .msg-msgs{min-height:200px;max-height:56vh;overflow-y:auto;padding:6px 2px;display:flex;flex-direction:column} 363 .msg-bub{margin-bottom:10px;max-width:80%} 364 .msg-bub.me{align-self:flex-end;text-align:right} 365 .msg-from{font-size:10.5px;color:var(--gold-soft);font-weight:700;display:block;margin-bottom:2px} 366 .msg-b{display:inline-block;padding:9px 13px;border-radius:14px;font-size:14px;line-height:1.45;text-align:left} 367 .msg-bub.them .msg-b{background:var(--card);border:1px solid var(--line)} 368 .msg-bub.me .msg-b{background:linear-gradient(135deg,var(--gold-soft),#d9b65e);color:#1a1305} 369 .msg-t{font-size:10px;color:var(--faint);margin-top:3px} 370 .msg-in{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid var(--line)} 371 .msg-in .inp{flex:1} 372 .msg-day{margin-bottom:6px} 373 .msg-day-h{display:flex;align-items:center;gap:8px;font-size:11.5px;font-weight:800;color:var(--gold-soft);text-transform:uppercase;letter-spacing:.05em;padding:9px 6px;cursor:pointer;user-select:none;border-radius:9px} 374 .msg-day-h:hover{background:var(--card)} 375 .msg-day-chev{transition:.2s;font-size:14px} 376 .msg-day:not(.open) .msg-day-chev{transform:rotate(-90deg)} 377 .msg-day-n{background:rgba(232,201,119,.15);border-radius:20px;padding:1px 9px;font-size:11px;margin-left:auto;color:var(--gold-soft)} 378 .msg-day-b{display:grid;grid-template-rows:0fr;transition:grid-template-rows .24s cubic-bezier(.4,0,.2,1)} 379 .msg-day.open .msg-day-b{grid-template-rows:1fr} 380 .msg-day-bi{overflow:hidden;display:flex;flex-direction:column;padding-top:4px} 381 .net-status{margin-top:12px} 382 .net-ok{display:inline-flex;align-items:center;gap:7px;background:rgba(127,224,168,.13);color:#7fe0a8;border:1px solid rgba(127,224,168,.3);border-radius:20px;padding:5px 13px;font-size:12.5px;font-weight:700} 383 .net-bad{display:inline-flex;align-items:center;gap:7px;background:rgba(255,156,139,.12);color:#ffb0a2;border:1px solid rgba(255,156,139,.3);border-radius:20px;padding:5px 13px;font-size:12.5px;font-weight:700}.net-warn{display:inline-flex;align-items:center;gap:7px;background:rgba(232,201,119,.14);color:#e8c977;border:1px solid rgba(232,201,119,.38);border-radius:20px;padding:5px 13px;font-size:12.5px;font-weight:700} 384 .p2-actions{display:flex;gap:9px;align-items:stretch} 385 .p2-actions .p2-btn{flex:1} 386 .p2-duty{display:inline-flex;align-items:center;gap:6px;justify-content:center;background:var(--card);border:1px solid var(--line);color:var(--muted);border-radius:13px;padding:0 15px;font-family:inherit;font-size:13px;font-weight:700;cursor:pointer;white-space:nowrap;transition:.15s} 387 .p2-duty:hover{border-color:rgba(232,201,119,.4);color:var(--gold-soft)} 388 .p2-duty.on{background:linear-gradient(135deg,var(--gold-soft),#c99b3c);
388color:#1a1305;border-color:transparent} 389 @media(max-width:560px){.p2-actions{flex-direction:column}} 390 /* ===== Dashboard workspace ===== */ 391 .snap{background:linear-gradient(135deg,rgba(200,155,60,.15),rgba(255,255,255,.015) 62%);border:1px solid rgba(200,155,60,.24);border-radius:18px;padding:20px 22px;margin:0 0 16px;box-shadow:0 12px 44px -24px rgba(200,155,60,.6),inset 0 1px 0 rgba(255,255,255,.05)} 392 .snap-l{display:flex;align-items:center;gap:16px} 393 .snap-hi{flex:1;min-width:0} 394 .snap-hi h2{font-size:25px;font-weight:800;line-height:1.15;margin:0;color:#fff;letter-spacing:-.015em;min-height:1.15em} 395 #eName.type-cursor::after{content:"";display:inline-block;width:2px;height:.8em;background:var(--gold-soft,#e8c977);margin-left:3px;border-radius:1px;vertical-align:baseline;box-shadow:0 0 6px rgba(232,201,119,.7);animation:blinkCur 1.05s step-end infinite} 396 @keyframes blinkCur{0%,50%{opacity:1}51%,100%{opacity:0}} 397 .snap-eyebrow{display:block;color:var(--gold-soft);font-size:11.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;opacity:.9;margin-bottom:3px} 398 .snap-hi p{color:var(--muted);font-size:12.5px;margin:3px 0 0} 399 .snap-hb{display:inline-flex;align-items:center;gap:7px;background:linear-gradient(180deg,var(--gold-soft),var(--gold));color:#1a1207;border-radius:11px;padding:9px 14px;font-size:12.5px;font-weight:700;text-decoration:none;white-space:nowrap;flex:none;transition:transform .15s} 400 .snap-hb:hover{transform:translateY(-1px)} 401 @media(max-width:560px){.snap-hb{padding:9px 13px}} 402 .snap-chips{display:flex;flex-wrap:wrap;gap:9px;margin-top:16px;padding-top:15px;border-top:1px solid rgba(200,155,60,.14)} 403 .snap-chips .chip{margin-top:0} 404 .dsec-h{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;color:var(--gold-soft);margin:20px 2px 2px} 405 .dsec-h .ti{font-size:16px} 406 .dash-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px;margin-top:10px} 407 .dcard{background:linear-gradient(158deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:16px;padding:15px 17px;min-width:0;transition:border-color .18s,transform .18s} 408 .dcard:hover{border-color:rgba(232,201,119,.22)} 409 .s8{grid-column:span 8}.s6{grid-column:span 6}.s4{grid-column:span 4} 410 @media(max-width:1000px){.s8,.s6,.s4{grid-column:span 6}} 411 @media(max-width:640px){.dash-grid{gap:12px}.s8,.s6,.s4{grid-column:span 12}} 412 .dcard-h{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px} 413 .dcard-h>span{display:flex;align-items:center;gap:8px;font-size:13.5px;font-weight:700} 414 .dcard-h .ti{color:var(--gold-soft)} 415 .dcard-all{font-size:11.5px;color:var(--gold-soft);cursor:pointer;display:inline-flex;align-items:center;gap:3px;white-space:nowrap;font-weight:600;opacity:.85} 416 .dcard-all:hover{opacity:1} 417 .dw-task{display:flex;align-items:center;gap:11px;padding:9px 0;border-top:1px solid var(--line)} 418 .dw-task:first-child{border-top:none} 419 .dw-pri{width:8px;height:8px;border-radius:50%;flex:none} 420 .dw-task-b{flex:1;min-width:0} 421 .dw-task-b b{display:block;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} 422 .dw-task-m{font-size:11px;color:var(--faint);text-transform:capitalize} 423 .dw-task-go{color:var(--faint);font-size:15px;flex:none} 424 .dw-quick{display:grid;grid-template-columns:1fr 1fr;gap:9px} 425 .qa{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;background:var(--card);border:1px solid var(--line);border-radius:12px;padding:13px 6px;color:inherit;font-family:inherit;font-size:11.5px;font-weight:600;cursor:pointer;transition:.15s} 426 .qa:hover{border-color:rgba(232,201,119,.4);background:rgba(232,201,119,.08);transform:translateY(-2px)} 427 .qa .ti{font-size:19px;color:var(--gold-soft)} 428 .dw-note{display:flex;gap:10px;padding:9px 0;border-top:1px solid var(--line)} 429 .dw-note:first-child{border-top:none} 430 .dw-note-i{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;flex:none;font-size:15px;background:rgba(143,182,240,.14);color:#8fb6f0} 431 .dw-note-i.success{background:rgba(127,224,168,.14);color:#7fe0a8}.dw-note-i.warn{background:rgba(255,156,139,.14);color:#ffb0a2} 432 .dw-note-b{flex:1;min-width:0} 433 .dw-note-b b{display:block;font-size:13px} 434 .dw-new{display:inline-block;width:7px;height:7px;border-radius:50%;background:#ff5a4d;margin-left:7px;vertical-align:middle;animation:dwPulse 1.5s ease-out infinite} 435 @keyframes dwPulse{0%{box-shadow:0 0 0 0 rgba(255,90,77,.55)}70%{box-shadow:0 0 0 6px rgba(255,90,77,0)}100%{box-shadow:0 0 0 0 rgba(255,90,77,0)}}
436 .dw-note-b span{display:block;font-size:11.5px;color:var(--muted);line-height:1.4;margin:1px 0} 437 .dw-note-b em{font-size:10px;color:var(--faint);font-style:normal} 438 .dw-up{display:flex;gap:12px;padding:9px 0;border-top:1px solid var(--line);align-items:center} 439 .dw-up:first-child{border-top:none} 440 .dw-up-d{text-align:center;flex:none;min-width:52px;background:rgba(232,201,119,.1);border:1px solid rgba(232,201,119,.2);border-radius:10px;padding:6px 4px} 441 .dw-up-d b{display:block;font-size:11.5px;color:var(--gold-soft)} 442 .dw-up-d span{font-size:11px;color:var(--muted)} 443 .dw-up-b{flex:1;min-width:0} 444 .dw-up-b b{display:block;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} 445 .dw-up-b span{font-size:11px;color:var(--faint);display:flex;align-items:center;gap:4px} 446 .dw-lv{margin-bottom:11px} 447 .dw-lv:last-child{margin-bottom:0} 448 .dw-lv-t{display:flex;justify-content:space-between;align-items:baseline;font-size:12.5px;margin-bottom:5px} 449 .dw-lv-t span{color:var(--muted)} 450 .dw-lv-t b{font-weight:800;color:#fff}.dw-lv-t small{color:var(--faint);font-weight:400} 451 .dw-lv-bar{height:7px;border-radius:20px;background:rgba(255,255,255,.07);overflow:hidden} 452 .dw-lv-bar span{display:block;height:100%;border-radius:20px;background:linear-gradient(90deg,var(--gold-soft),#d9b65e);transition:width .5s cubic-bezier(.4,0,.2,1)} 453 .dw-perf-top{display:flex;align-items:center;gap:14px;margin-bottom:12px} 454 .dw-perf-n{font-size:30px;font-weight:800;color:var(--gold-soft);line-height:1} 455 .dw-perf-n small{font-size:14px;color:var(--faint);font-weight:400} 456 .dw-perf-r .stars{font-size:14px}.dw-perf-r span{display:block;font-size:11px;color:var(--faint);margin-top:2px} 457 .dw-empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:20px 10px;text-align:center;color:var(--faint)} 458 .dw-empty .ti{font-size:26px;opacity:.6} 459 .dw-empty span{font-size:12.5px} 460 .dw-sk{height:34px;border-radius:9px;margin-bottom:9px;background:linear-gradient(90deg,rgba(255,255,255,.04),rgba(255,255,255,.09),rgba(255,255,255,.04));background-size:200% 100%;animation:dwsk 1.3s infinite} 461 @keyframes dwsk{0%{background-position:200% 0}100%{background-position:-200% 0}} 462 .dcard-journey{padding:0;overflow:hidden} 463 .dcard-journey #progCard{margin:0!important;border:none;background:transparent} 464 #selfieOverlay{position:fixed;inset:0;background:rgba(0,0,0,.75);backdrop-filter:blur(3px);opacity:0;visibility:hidden;transition:.2s;z-index:170;display:flex;align-items:center;justify-content:center;padding:16px} 465 #selfieOverlay.on{opacity:1;visibility:visible} 466 .selfie-box{width:100%;max-width:400px;background:#15151b;border:1px solid var(--line);border-radius:18px;box-shadow:0 30px 70px -20px rgba(0,0,0,.8);padding:18px} 467 body.light .selfie-box{background:#fff} 468 .selfie-h{display:flex;align-items:center;justify-content:space-between;font-size:15px} 469 .selfie-h .ti{color:var(--gold-soft);margin-right:6px} 470 .selfie-sub{font-size:12.5px;color:var(--muted);margin:4px 0 12px} 471 .selfie-vid{width:100%;border-radius:14px;background:#000;aspect-ratio:4/3;object-fit:cover;transform:scaleX(-1)} 472 .selfie-ph{width:100%;aspect-ratio:4/3;border-radius:14px;border:1.5px dashed var(--line);background:linear-gradient(160deg,rgba(232,201,119,.08),rgba(255,255,255,.02));display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px} 473 body.light .selfie-ph{background:linear-gradient(160deg,rgba(200,155,60,.1),rgba(22,30,48,.02));border-color:rgba(22,30,48,.16)} 474 .selfie-ph-ic{width:66px;height:66px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(232,201,119,.14);color:var(--gold-soft);font-size:32px} 475 .selfie-ph-t{font-size:14.5px;font-weight:700;color:var(--txt)} 476 .selfie-cap{width:100%;margin-top:14px;justify-content:center;font-size:15px;padding:13px} 477 @media(max-width:560px){ 478 .hamb{order:-1} 479 .topbar img{width:36px;height:36px;border-radius:10px} 480 .topbar .tt b{font-size:13.5px}.topbar .tt span{font-size:10px} 481 .tbtn{width:38px;height:38px;font-size:16px} 482 .wrap{padding:0 12px 46px} 483 .crm{gap:10px;margin:14px 0 4px} 484 .ccard{border-radius:15px} 485 .ref{border-radius:16px;padding:20px 18px;margin-top:12px} 486 .ref h1{font-size:21px;line-height:1.28} 487 .chip{font-size:12.5px} 488 .card{border-radius:16px} 489 .p2-time b{font-size:15.5px} 490 .p2-btn{flex:1;justify-content:center} 491 .p2-month{flex:1 1 100%} 492 } 493 @media(max-width:400px){ 494 .p2-times{flex-wrap:wrap}
494.p2-sep{display:none} 495 .p2-time{flex:1 1 calc(33.33% - 6px)} 496 .p2-time.worked.live b{font-size:14px;letter-spacing:.2px} 497 } 498 @media(max-width:480px){ 499 html,body{overflow-x:hidden;max-width:100vw} 500 .wrap{padding-left:13px;padding-right:13px;max-width:100%} 501 .p2-times{flex-wrap:wrap} 502 .p2-sep{display:none} 503 .p2-time{flex:1 1 calc(33.33% - 6px);min-width:0} 504 .topbar{min-width:0} 505 .topbar>*{min-width:0} 506 .crm{grid-template-columns:repeat(2,1fr)} 507 .snap-hb{flex:0 0 auto} 508 } 509 .acc-b .line{display:flex;justify-content:space-between;gap:10px;padding:5px 0;font-size:13.5px;border-bottom:1px solid rgba(255,255,255,.05)} 510 body.light .acc-b .line{border-bottom:1px solid rgba(40,40,48,.06)} 511 .acc-b .ptext{white-space:pre-wrap;font-size:13.5px;color:var(--muted);line-height:1.65} 512 .accwrap-head{font-size:12px;color:var(--faint);text-transform:uppercase;letter-spacing:.4px;margin:14px 0 8px} 513 .monthsel{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.monthsel select{max-width:230px} 514 @keyframes hbpulse{0%,100%{box-shadow:0 0 0 0 rgba(232,201,119,.6)}50%{box-shadow:0 0 0 10px rgba(232,201,119,0)}} 515 #hbBtn{animation:hbpulse 1.5s ease-in-out infinite;font-weight:700;font-size:14px} 516 select option{background:#17171c;color:#fff} 517 body.night select option{background:#0c1119;color:#fff} 518 input[type="date"]{color-scheme:dark} 519 input[type="date"]::-webkit-calendar-picker-indicator{filter:invert(.85);cursor:pointer;opacity:.9} 520 .docwarn{display:flex;align-items:center;gap:8px;background:rgba(224,83,63,.13);border:1px solid rgba(224,83,63,.5);color:#ffb3a6;border-radius:11px;padding:11px 14px;margin-bottom:12px;font-size:13px;animation:warnblink 1.5s ease-in-out infinite} 521 @keyframes warnblink{0%,100%{opacity:1}50%{opacity:.5}} 522 .docok{display:flex;align-items:center;gap:8px;background:rgba(62,199,127,.12);border:1px solid rgba(62,199,127,.4);color:#aef0c8;border-radius:11px;padding:11px 14px;margin-bottom:12px;font-size:13px} 523 .docok-pro{display:flex;align-items:center;gap:13px;background:linear-gradient(150deg,rgba(62,199,127,.16),rgba(62,199,127,.04));border:1px solid rgba(62,199,127,.45);border-radius:14px;padding:14px 16px;margin-bottom:12px;box-shadow:0 8px 26px -16px rgba(62,199,127,.5)} 524 .docok-pro i{font-size:27px;color:var(--ok);flex:none} 525 .docok-pro b{display:block;font-size:14.5px;color:#c6f5da} 526 .docok-pro span{font-size:12px;color:var(--muted)} 527 .sal-yr{display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:16px} 528 .sal-nav{width:38px;height:38px;border-radius:50%;background:var(--card-2);border:1px solid var(--line);color:var(--gold-soft);cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center} 529 .sal-nav:disabled{opacity:.32;cursor:default} 530 .sal-yrlbl{font-size:17px;font-weight:800;color:var(--gold-soft);min-width:64px;text-align:center;background:var(--card-2);border:1px solid var(--line);border-radius:20px;padding:6px 22px} 531 .sal-months{display:grid;gap:10px} 532 .sal-card{display:flex;align-items:center;gap:14px;background:linear-gradient(160deg,rgba(255,255,255,.06),rgba(255,255,255,.02));border:1px solid var(--line);border-left:3px solid var(--gold);border-radius:14px;padding:12px 15px} 533 .sal-card.locked{opacity:.5;border-left-color:var(--faint)} 534 .sal-mbadge{width:52px;text-align:center;background:rgba(200,155,60,.14);border-radius:10px;padding:8px 0;flex:none} 535 .sal-mbadge b{display:block;font-size:16px;color:var(--gold-soft);line-height:1} 536 .sal-mbadge span{font-size:10.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em} 537 .sal-mmain{flex:1;min-width:0}.sal-mmain b{display:block;font-size:14px}.sal-mmain span{font-size:12px;color:var(--faint)} 538 .sal-mbtns{display:flex;gap:8px;align-items:center} 539 .sal-dl{background:linear-gradient(180deg,var(--gold-soft),var(--gold));color:#1a1207;border:none;border-radius:9px;padding:9px 14px;font-family:inherit;font-weight:700;font-size:12.5px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;white-space:nowrap} 540 .sal-rep{background:var(--card-2);border:1px solid var(--line);color:var(--warn);border-radius:9px;padding:9px 11px;cursor:pointer} 541 .sal-na{font-size:11.5px;color:var(--faint);margin-left:auto}
542 .sal-quick{display:flex;align-items:center;gap:9px;flex-wrap:wrap;background:linear-gradient(160deg,rgba(200,155,60,.12),rgba(255,255,255,.02));border:1px solid rgba(200,155,60,.28);border-radius:14px;padding:12px 14px;margin-bottom:16px} 543 .sal-quick>i{color:var(--gold-soft);font-size:18px} 544 .sal-qlbl{font-weight:700;font-size:13px;letter-spacing:.02em} 545 .sal-quick select{flex:1 1 120px;min-width:110px;margin-bottom:0} 546 .sal-quick .sal-dl{white-space:nowrap} 547 .sal-qnote{flex:1;font-size:12px;color:var(--muted)} 548 .sal-empty{text-align:center;padding:34px 18px;border:1px dashed var(--line);border-radius:16px;background:rgba(255,255,255,.02)} 549 .sal-empty>i{font-size:30px;color:var(--gold-soft);opacity:.8} 550 .sal-empty-t{font-size:14.5px;font-weight:700;color:var(--txt);margin:10px 0 4px} 551 .sal-empty-s{font-size:12.5px;color:var(--muted);max-width:340px;margin:0 auto;line-height:1.55} 552 @media(max-width:560px){.sal-quick{gap:8px}.sal-qlbl{flex:1 1 100%}.sal-quick .sal-dl{flex:1 1 100%;justify-content:center}} 553 @media(max-width:560px){.sal-card{flex-wrap:wrap}.sal-mbtns{width:100%}.sal-dl{flex:1;justify-content:center}} 554 @media(max-width:640px){.tabs{flex-wrap:wrap;overflow:visible;padding-bottom:2px;gap:7px}.tab{flex:1 1 calc(50% - 5px);min-width:calc(50% - 5px);justify-content:center;padding:9px 10px;font-size:12px}} 555 @media(max-width:560px){.crm{grid-template-columns:repeat(2,1fr)}.ref{padding:18px 16px}.ref h1{font-size:20px}.wrap{padding:14px 12px}.topbar{padding:10px 12px;gap:8px}.card{padding:16px 15px}.ccard{padding:13px 14px}.ccard .cv{font-size:16px;line-height:1.3}.tab{padding:9px 13px;font-size:12.5px}} 556 @media(max-width:380px){.crm{grid-template-columns:repeat(2,1fr)}.ccard .cl{font-size:10px}.ccard .cv{font-size:15px;line-height:1.3}} 557 @media(max-width:480px){.drow{flex-wrap:wrap;gap:10px 12px;padding:14px 15px}.drow .dt{flex:1 1 calc(100% - 54px);min-width:0}.drow .dt b{font-size:14px}.drow .dt span{white-space:normal;word-break:break-word}.drow .dl{flex:1 1 100%;justify-content:center;padding:11px}} 558 .warncard{background:rgba(224,83,63,.07);border:1.5px solid rgba(224,83,63,.55);border-radius:16px;padding:16px 18px;margin-bottom:16px;animation:warnglow 1.8s ease-in-out infinite} 559 @keyframes warnglow{0%,100%{box-shadow:0 0 0 0 rgba(224,83,63,.35)}50%{box-shadow:0 0 0 7px rgba(224,83,63,0)}} 560 .punch-card{display:flex;align-items:center;gap:16px;justify-content:space-between;flex-wrap:wrap;background:linear-gradient(135deg,rgba(200,155,60,.14),rgba(255,255,255,.02));border:1px solid rgba(200,155,60,.28);border-radius:18px;padding:16px 18px;margin:16px 0} 561 .punch-h{font-size:12px;color:var(--gold-soft);font-weight:800;text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;gap:7px} 562 .punch-status{font-size:16px;font-weight:700;margin-top:6px} 563 .punch-meta{font-size:12.5px;color:var(--muted);margin-top:4px} 564 .punch-btn{font-size:15px;padding:12px 22px} 565 .punch-btn.in{background:linear-gradient(180deg,#4ec77f,#2e9d5e);color:#04140b} 566 .punch-btn.out{background:linear-gradient(180deg,#f3a05a,#e07a3f);color:#1a0d02} 567 .punch-done{color:var(--ok);font-weight:700;display:inline-flex;align-items:center;gap:7px;font-size:15px} 568 .onb-card{background:linear-gradient(135deg,rgba(78,199,127,.14),rgba(255,255,255,.02));border:1px solid rgba(78,199,127,.3);border-radius:18px;padding:18px;margin:16px 0} 569 .onb-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px} 570 .onb-title{font-size:16px;font-weight:800;display:flex;align-items:center;gap:8px} 571 .onb-sub{font-size:12.5px;color:var(--muted);margin-top:3px} 572 .onb-pct{font-size:24px;font-weight:800;color:#7fe0a8;line-height:1} 573 .onb-bar{height:8px;border-radius:6px;background:var(--line);overflow:hidden;margin:12px 0} 574 .onb-bar span{display:block;height:100%;background:linear-gradient(90deg,#4ec77f,#2e9d5e);border-radius:6px} 575 .onb-steps{display:flex;flex-direction:column;gap:7px} 576 .onb-step{display:flex;align-items:center;gap:9px;font-size:13.5px} 577 .onb-step .ti{color:var(--faint);font-size:18px} 578 .onb-step.done{color:var(--muted)} 579 .onb-step.done .ti{color:#4ec77f} 580 .onb-step.done span{text-decoration:line-through} 581 .asset-card{background:linear-gradient(135deg,rgba(143,182,240,.12),rgba(255,255,255,.02));border:1px solid rgba(143,182,240,.28);border-radius:18px;padding:16px 18px;margin:16px 0} 582 .asset-head{font-size:14px;font-weight:800;display:flex;align-items:center;gap:8px;margin-bottom:6px} 583 .asset-head .ti{color:#8fb6f0;font-size:18px} 584 .asset-head span{background:rgba(143,182,240,.2);color:#8fb6f0;border-radius:20px;padding:1px 9px;font-size:12px} 585 .asset-item{display:flex;align-items:center;gap:12px;padding:11px 0;border-top:1px solid var(--line)} 586 .asset-item>.ti{font-size:24px;color:#8fb6f0} 587 .asset-info{flex:1} 588 .asset-tag{font-size:11px;color:var(--faint)} 589 .asset-meta{font-size:12px;color:var(--muted);margin-top:2px} 590 .asset-ack{background:var(--gold-soft);
590color:#1a1305;border:none;border-radius:10px;padding:7px 14px;font-weight:700;font-size:12.5px;cursor:pointer} 591 .asset-ack.done{background:transparent;color:#7fe0a8;display:inline-flex;align-items:center;gap:5px;padding:7px 2px} 592 .crs-card{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.02));border:1px solid var(--line);border-radius:14px;padding:14px;margin-bottom:12px} 593 .crs-meta{font-size:12px;color:var(--muted);margin-top:3px} 594 .crs-desc{font-size:13px;color:var(--muted);margin:6px 0} 595 .crs-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:12px} 596 .crs-edit{display:flex;flex-wrap:wrap;align-items:center;gap:10px;flex:1;min-width:200px} 597 .crs-edit input[type=range]{flex:1;min-width:120px;accent-color:var(--gold-soft)} 598 .crs-edit span{font-size:12px;color:var(--gold-soft);font-weight:700;min-width:38px} 599 .pulse-card{background:linear-gradient(135deg,rgba(200,155,60,.14),rgba(255,255,255,.02));border:1px solid rgba(200,155,60,.28);border-radius:16px;padding:16px;margin-bottom:12px} 600 .pulse-q{font-size:15px;font-weight:700;display:flex;align-items:center;gap:8px;margin-bottom:12px} 601 .pulse-q .ti{color:var(--gold-soft)} 602 .pulse-scale{display:flex;gap:10px;flex-wrap:wrap} 603 .pulse-dot{width:44px;height:44px;border-radius:50%;border:1px solid var(--line);background:var(--card);color:var(--muted);font-weight:800;font-size:16px;cursor:pointer;transition:.15s} 604 .pulse-dot:hover,.pulse-dot.on{background:var(--gold-soft);color:#1a1305;border-color:var(--gold-soft)} 605 .poll-card{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.02));border:1px solid var(--line);border-radius:14px;padding:14px;margin-bottom:12px} 606 .poll-q{margin-top:12px} 607 .poll-qt{font-size:13.5px;font-weight:600;margin-bottom:6px} 608 .poll-opt{display:flex;align-items:center;gap:9px;padding:7px 0;font-size:13.5px;color:var(--muted);cursor:pointer} 609 .poll-opt input{width:16px;height:16px} 610 .sug-form{background:rgba(255,255,255,.02);border:1px solid var(--line);border-radius:14px;padding:14px} 611 .sug-item{display:flex;gap:12px;background:var(--card);border:1px solid var(--line);border-radius:12px;padding:12px;margin-bottom:8px} 612 .sug-vote{display:flex;flex-direction:column;align-items:center;gap:2px;background:transparent;border:1px solid var(--line);border-radius:10px;padding:6px 10px;color:var(--muted);cursor:pointer;min-width:44px} 613 .sug-vote.on{border-color:var(--gold-soft);color:var(--gold-soft)} 614 .sug-vote .ti{font-size:18px}.sug-vote span{font-size:12px;font-weight:700} 615 .sug-body{flex:1} 616 .sug-meta{font-size:12px;color:var(--faint);margin-top:2px} 617 .sug-msg{font-size:13px;color:var(--muted);margin-top:5px} 618 @media(max-width:480px){ 619 .pulse-scale{gap:7px} 620 .pulse-dot{width:40px;height:40px;font-size:15px} 621 .crs-edit{min-width:0} 622 .crs-edit input[type=range]{min-width:90px} 623 .crs-actions .btn,.crs-actions .dl{flex:1;justify-content:center} 624 .asset-item{gap:10px} 625 .asset-ack{padding:6px 11px} 626 .sug-item{gap:9px} 627 .sug-vote{min-width:40px;padding:5px 8px} 628 } 629 .acal-head{display:flex;align-items:center;justify-content:center;gap:16px;margin:6px 0 14px} 630 .acal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:6px} 631 .acal-dow{text-align:center;font-size:11px;color:var(--faint);font-weight:700;padding-bottom:2px} 632 .acal-cell{aspect-ratio:1;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:13.5px;font-weight:700;background:var(--card);border:1px solid var(--line);color:var(--muted);position:relative;transition:transform .12s ease} 633.acal-cell:not(.empty):hover{transform:translateY(-2px)} 634 .acal-cell.empty{background:transparent;border:none} 635 .acal-cell.present{background:linear-gradient(160deg,rgba(78,199,127,.28),rgba(78,199,127,.09));color:#a7edc4;border-color:rgba(78,199,127,.45)} 636 .acal-cell.half{background:linear-gradient(160deg,rgba(243,210,122,.26),rgba(243,210,122,.09));color:#f6dd94;border-color:rgba(243,210,122,.45)} 637 .acal-cell.leave{background:linear-gradient(160deg,rgba(143,182,240,.26),rgba(143,182,240,.09));color:#b3ccf6;border-color:rgba(143,182,240,.45)} 638 .acal-cell.holiday{background:linear-gradient(160deg,rgba(198,163,255,.22),rgba(198,163,255,.07));color:#d5bcff;border-color:rgba(198,163,255,.4)} 639 .acal-cell.absent{background:linear-gradient(160deg,rgba(224,83,63,.2),rgba(224,83,63,.06));color:#ffb0a2;border-color:rgba(224,83,63,.4)} 640 .acal-cell.weekoff{opacity:.45}.acal-cell.future{opacity:.32} 641 .acal-cell:not(.empty){cursor:pointer} 642 .attd-modal{position:fixed;inset:0;background:rgba(6,9,15,.72);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:120;padding:20px} 643 .attd-modal.open{display:flex} 644 .attd-card{width:100%;max-width:360px;background:linear-gradient(165deg,#171d29,#10151f);border:1px solid var(--line);border-radius:18px;box-shadow:0 24px 60px -20px rgba(0,0,0,.7);overflow:hidden} 645 .attd-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px 18px 12px} 646 .attd-date{font-size:15px;font-weight:800;color:var(--txt);margin-bottom:6px} 647 .attd-pill{display:inline-block;font-size:11px;font-weight:700;padding:2px 10px;border-radius:20px;border:1px solid} 648 .attd-x{background:none;border:none;color:var(--faint);cursor:pointer;font-size:18px;line-height:1;padding:2px} 649 .attd-body{padding:4px 18px 18px} 650 .attd-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 0;border-top:1px solid var(--line)} 651 .attd-row span{font-size:12.5px;color:var(--muted)} 652 .attd-row b{font-size:13.5px;color:var(--txt);font-weight:700} 653 .attd-empty{padding:14px 0 6px;font-size:12.5px;color:var(--muted);text-align:center} 654 .attd-sub{font-size:12px;color:var(--muted);font-weight:600} 655 .acal-reg{position:absolute;bottom:5px;right:5px;width:7px;height:7px;border-radius:50%;display:block} 656 .acal-reg.pending{background:#f3b04f;box-shadow:0 0 0 2px rgba(243,176,79,.22)} 657 .acal-reg.approved{background:#7fe0a8} 658 .acal-reg.rejected{background:#ff9c8b} 659 .reg-status{margin-top:12px;border:1px solid;border-radius:13px;padding:11px 13px;background:rgba(255,255,255,.02)} 660 .reg-status-h{font-size:12.5px;font-weight:700;color:var(--txt);display:flex;align-items:center;gap:6px} 661 .reg-status-b{font-size:12px;color:var(--muted);margin-top:6px;line-height:1.5} 662 .reg-reason{color:var(--faint);font-style:italic} 663 .reg-addbtn{margin-top:13px;width:100%;background:linear-gradient(135deg,#2b7de0,#1e63c4);border:none;color:#fff;font-weight:700;font-size:13.5px;border-radius:12px;padding:12px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:7px;box-shadow:0 8px 22px -10px rgba(43,125,224,.8)} 664 .reg-addbtn:active{transform:scale(.98)} 665 .reg-note{font-size:11.8px;color:var(--muted);line-height:1.5;background:rgba(43,125,224,.08);border:1px solid rgba(43,125,224,.2);border-radius:11px;padding:10px 12px;margin-bottom:14px;display:flex;gap:7px} 666 .reg-lbl{display:block;font-size:12px;font-weight:700;color:var(--muted);margin:12px 0 6px} 667 .reg-opt{font-weight:500;color:var(--faint)} 668 .reg-inp{width:100%;box-sizing:border-box;background:var(--card);border:1px solid var(--line);border-radius:11px;padding:11px 13px;color:var(--txt);font-size:14px;font-family:inherit} 669 .reg-inp:focus{outline:none;border-color:#2b7de0} 670 .reg-actions{display:flex;gap:10px;margin-top:18px} 671 .reg-cancel{flex:1;background:var(--card);border:1px solid var(--line);color:var(--muted);font-weight:700;font-size:13.5px;border-radius:12px;padding:12px;cursor:pointer} 672 .reg-submit{flex:2;background:linear-gradient(135deg,#2b7de0,#1e63c4);border:none;color:#fff;font-weight:700;font-size:13.5px;border-radius:12px;padding:12px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:7px} 673 .reg-submit:disabled{opacity:.6} 674 .acal-cell.tdy{outline:2px solid var(--gold-soft);outline-offset:2px;box-shadow:0 0 0 4px rgba(232,201,119,.12),0 8px 24px -10px rgba(232,201,119,.55);z-index:1} 675 .acal-legend{display:flex;flex-wrap:wrap;gap:12px;margin-top:14px;font-size:12px;color:var(--muted)}
676 .acal-legend span{display:inline-flex;align-items:center;gap:6px} 677 .acal-dot{width:11px;height:11px;border-radius:3px;display:inline-block} 678 .acal-dot.present{background:#4ec77f}.acal-dot.half{background:#f3d27a}.acal-dot.leave{background:#8fb6f0}.acal-dot.holiday{background:#c6a3ff}.acal-dot.absent{background:#e0533f} 679 /* forgot-to-punch-out: blinking amber caution on the day + popup treatment */ 680 .acal-cell.incomplete{border-color:rgba(243,144,47,.7)!important;box-shadow:inset 0 0 0 1.5px rgba(243,144,47,.4)} 681 .acal-warn{position:absolute;top:3px;right:3px;min-width:16px;height:16px;padding:0 2px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(180deg,#ffb44a,#f3902f);color:#3a1f00;font-size:11px;font-weight:900;line-height:1;box-shadow:0 2px 6px -1px rgba(243,144,47,.75);animation:acalWarnBlink 1.05s ease-in-out infinite;z-index:2} 682 @keyframes acalWarnBlink{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.32;transform:scale(.8)}} 683 .acal-dot.warn{background:#f3902f} 684 .attd-warn{display:flex;align-items:flex-start;gap:11px;margin:2px 0 6px;padding:12px 13px;border-radius:13px;background:linear-gradient(160deg,rgba(243,144,47,.18),rgba(243,144,47,.06));border:1px solid rgba(243,144,47,.5)} 685 .attd-warn>i{color:#ffb44a;font-size:21px;flex:none;margin-top:1px;animation:acalWarnBlink 1.2s ease-in-out infinite} 686 .attd-warn b{display:block;color:#ffc879;font-size:13.5px;font-weight:800;margin-bottom:2px} 687 .attd-warn span{display:block;color:var(--muted);font-size:12.5px;line-height:1.45} 688 .reg-addbtn.warn{background:linear-gradient(180deg,#ffb44a,#f3902f);color:#3a1f00;box-shadow:0 8px 22px -9px rgba(243,144,47,.85)} 689 @media(prefers-reduced-motion:reduce){.acal-warn,.attd-warn>i{animation:none}} 690 .att-sum{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px} 691 .att-chip{font-size:12px;font-weight:700;padding:6px 12px;border-radius:20px;background:var(--card);border:1px solid var(--line)} 692 .att-chip.present{color:#7fe0a8}.att-chip.half{color:#f3d27a}.att-chip.leave{color:#8fb6f0}.att-chip.late{color:var(--warn)} 693 .att-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin-bottom:16px} 694 .att-stat{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.015));border:1px solid var(--line);border-radius:14px;padding:12px 8px;text-align:center} 695 .att-stat b{display:block;font-size:22px;font-weight:800;line-height:1} 696 .att-stat span{font-size:10.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.05em;font-weight:700;margin-top:4px;display:block} 697 .att-stat.present b{color:#7fe0a8}.att-stat.half b{color:#f3d27a}.att-stat.leave b{color:#8fb6f0}.att-stat.late b{color:#ff9c8b} 698 @media(max-width:400px){.att-stats{gap:6px}.att-stat{padding:10px 5px}.att-stat b{font-size:18px}.att-stat span{font-size:9px}} 699 .lv-bals{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-bottom:8px} 700 .lv-bal{background:linear-gradient(160deg,rgba(255,255,255,.06),rgba(255,255,255,.02));border:1px solid var(--line);border-radius:14px;padding:14px} 701 .lv-bal-k{font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.04em;font-weight:700} 702 .lv-bal-v{font-size:26px;font-weight:800;color:var(--gold-soft);line-height:1;margin:6px 0 2px} 703 .lv-bal-s{font-size:11px;color:var(--muted)} 704 .lv-tag{display:inline-block;font-size:8.5px;font-weight:800;letter-spacing:.05em;padding:1px 6px;border-radius:20px;vertical-align:middle;margin-left:5px} 705 .lv-tag.paid{background:rgba(78,199,127,.18);color:#7fe0a8} 706 .lv-tag.unpaid{background:rgba(232,170,110,.16);color:#e9b487} 707 .hol-note{font-size:12px;color:var(--muted);background:rgba(198,163,255,.08);border:1px solid rgba(198,163,255,.22);border-radius:12px;padding:9px 12px;margin:8px 0 12px;line-height:1.5} 708 .hol-note i{color:#c6a3ff} 709 .hol-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px} 710 .hol-card{display:flex;align-items:center;gap:12px;background:linear-gradient(160deg,rgba(198,163,255,.1),rgba(255,255,255,.02));border:1px solid rgba(198,163,255,.22);border-radius:14px;padding:11px 13px} 711 .hol-card.past{opacity:.5} 712 .hol-date{flex:none;width:46px;text-align:center;background:rgba(198,163,255,.16);border-radius:10px;padding:6px 0} 713 .hol-date b{display:block;font-size:19px;font-weight:800;color:#d5bcff;line-height:1} 714 .hol-date span{font-size:10px;text-transform:uppercase;letter-spacing:.05em;
714color:#b79ff0} 715 .hol-info{min-width:0} 716 .hol-info b{display:block;font-size:13.5px;color:var(--txt);font-weight:700} 717 .hol-info span{font-size:11px;color:var(--muted)} 718 .lv-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px} 719 .lv-form label,.lv-half{font-size:12px;color:var(--muted);display:block;margin-bottom:4px} 720 .lv-half{display:flex;align-items:center;gap:8px;margin:12px 0} 721 .lv-half input{width:16px;height:16px} 722 .sal-net-badge{display:inline-block;margin-top:5px;font-size:11px;font-weight:700;padding:2px 9px;border-radius:20px;width:fit-content} 723 .sal-net-badge.paid{background:rgba(78,199,127,.2);color:#7fe0a8} 724 .sal-net-badge.fin{background:rgba(232,201,119,.18);color:var(--gold-soft)} 725 .reim-file{display:inline-flex;align-items:center;gap:7px;cursor:pointer;font-size:13px;color:var(--gold-soft);background:var(--card);border:1px solid var(--line);border-radius:10px;padding:9px 14px;margin-top:2px} 726 .goal-card{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.02));border:1px solid var(--line);border-radius:14px;padding:14px;margin-bottom:10px} 727 .goal-bar{height:8px;border-radius:6px;background:var(--line);overflow:hidden;margin-top:10px} 728 .goal-bar span{display:block;height:100%;
728background:linear-gradient(90deg,var(--gold-soft),#e8c977);border-radius:6px} 729 .goal-edit{display:flex;align-items:center;gap:10px;margin-top:10px} 730 .goal-edit input[type=range]{flex:1;accent-color:var(--gold-soft)} 731 .goal-edit span{font-size:12px;color:var(--gold-soft);font-weight:700;min-width:40px} 732 .kb{display:grid;grid-template-columns:repeat(4,1fr);gap:12px} 733 .kb-col{background:rgba(255,255,255,.02);border:1px solid var(--line);border-radius:12px;padding:10px;min-height:70px} 734 .kb-h{font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-bottom:8px;display:flex;justify-content:space-between} 735 .kb-h span{background:var(--line);border-radius:10px;padding:0 7px;color:var(--faint)} 736 .kb-card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:10px;margin-bottom:8px} 737 @media(max-width:720px){.kb{grid-template-columns:1fr 1fr}} 738 @media(max-width:460px){.kb{grid-template-columns:1fr}} 739 .perf-card{background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,.02));border:1px solid var(--line);border-radius:16px;padding:16px;margin-bottom:12px} 740 .perf-top{display:flex;justify-content:space-between;align-items:flex-start;gap:12px} 741 .perf-period{font-size:15px;font-weight:800} 742 .perf-overall{text-align:right;line-height:1} 743 .perf-overall b{font-size:30px;color:var(--gold-soft)} 744 .perf-overall span{font-size:13px;color:var(--muted)}
745 .perf-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px} 746 .perf-chip{font-size:12px;background:var(--card);border:1px solid var(--line);border-radius:20px;padding:5px 11px;color:var(--muted)} 747 .perf-fb{margin-top:12px} 748 .perf-fb b{font-size:12px;color:var(--gold-soft);text-transform:uppercase;letter-spacing:.04em} 749 .perf-fb div{margin-top:3px;color:var(--muted);font-size:13.5px} 750 .rw-hero{display:flex;justify-content:space-between;align-items:center;background:linear-gradient(135deg,rgba(200,155,60,.16),rgba(255,255,255,.02));border:1px solid rgba(200,155,60,.3);border-radius:18px;padding:20px} 751 .rw-hero .ti{font-size:44px;color:var(--gold-soft);opacity:.55} 752 .rw-pts{font-size:38px;font-weight:800;color:var(--gold-soft);line-height:1} 753 .rw-lbl{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-top:4px} 754 .rw-item{display:flex;align-items:center;gap:12px;background:var(--card);border:1px solid var(--line);border-radius:12px;padding:12px;margin-bottom:8px} 755 .rw-item .ti{font-size:22px;color:var(--gold-soft)} 756 .rw-item>div{flex:1} 757 .rw-badge{background:rgba(232,201,119,.18);color:var(--gold-soft);font-weight:700;font-size:12px;padding:3px 10px;border-radius:20px} 758 .lb-row{display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:10px;margin-bottom:5px;border:1px solid transparent} 759 .lb-row.me{background:rgba(200,155,60,.1);border-color:rgba(200,155,60,.3)} 760 .lb-rank{font-weight:800;color:var(--faint);min-width:22px} 761 .lb-name{flex:1} 762 .lb-pts{font-size:13px;font-weight:700;color:var(--gold-soft)} 763</style> 764<style id="empPremium"> 765/* Premium surface polish (dark) â lift cards clearly off the near-black background */ 766body:not(.light) .card, 767body:not(.light) .dcard, 768body:not(.light) .dept-block, 769body:not(.light) .ref{ 770 background:linear-gradient(158deg,#1b1d25 0%,#141519 100%); 771 border-color:rgba(255,255,255,.09); 772 box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 20px 44px -26px rgba(0,0,0,.92); 773} 774body:not(.light) .ccard{ 775 background:linear-gradient(158deg,#1c1e27 0%,#151620 100%); 776 border-color:rgba(255,255,255,.10); 777} 778body:not(.light) .drow, 779body:not(.light) .qa{ 780 background:linear-gradient(158deg,#1a1c23 0%,#141519 100%); 781 border-color:rgba(255,255,255,.09); 782} 783body:not(.light) .tab{ background:#171820; border-color:rgba(255,255,255,.10); } 784/* Tactile hover / active â premium feel on interaction (both themes) */ 785.dcard,.qa,.drow{transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease} 786.dcard:hover{transform:translateY(-2px);border-color:rgba(232,201,119,.32);box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 24px 50px -24px rgba(0,0,0,.95)} 787.qa:hover{transform:translateY(-2px);border-color:rgba(232,201,119,.42);box-shadow:0 16px 34px -20px rgba(0,0,0,.92),0 0 0 1px rgba(232,201,119,.14)} 788.drow:hover{transform:translateY(-1px);border-color:rgba(232,201,119,.28)} 789.tab:hover{border-color:rgba(232,201,119,.45);color:var(--gold-soft)} 790.tab.active{box-shadow:0 8px 20px -10px rgba(200,155,60,.55)} 791</style> 792</head> 793<body> 794 <!-- LOGIN --> 795 <div class="login" id="login"> 796 <div class="lcard"> 797 <img src="data:image/jpeg;base64,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" alt="MVS"/> 798 <h2>Employee Portal</h2> 799 <p>MVS Foundation â sign in to your dashboard</p> 800 <input class="inp" id="liId" placeholder="Employee ID (e.g. MVSE123456)" onkeydown="if(event.key==='Enter')doLogin()"> 801 <div class="lrel"><input class="inp" id="liPw" type="password" placeholder="Password" onkeydown="if(event.key==='Enter')doLogin()"><button class="ltgl" onclick="tgl()">SHOW</button></div> 802 <button class="lbtn" onclick="doLogin()" id="liBtn">Login</button> 803 <div class="lmsg" id="liMsg"></div> 804 </div> 805 </div> 806 807 <div id="app" style="display:none"> 808 <div class="topbar"> 809 <img src="data:image/jpeg;base64,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" alt="MVS" id="topLogo"/> 810 <button class="tbtn hamb" onclick="eSideOpen()" title="Menu" aria-label="Menu"><i class="ti ti-menu-2"></i></button> 811 <button class="tbtn tback" id="backBtn" onclick="dashGo('dashboard')" title="Back to dashboard" style="display:none"><i class="ti ti-arrow-left"></i></button> 812 <div class="tt"><b id="topName">Employee Portal</b><span id="topSub">MVS FOUNDATION</span></div> 813 <div class="sp"></div> 814 <button class="tbtn tb-desk" id="themeBtn" onclick="toggleTheme()" title="Theme"><i class="ti ti-sun"></i></button> 815 <button class="tbtn" onclick="gsOpen()" title="Search" aria-label="Search"><i class="ti ti-search"></i></button> 816 <button class="tbtn bell" id="bellBtn" onclick="toggleBell()"><i class="ti ti-bell"></i><span class="bdot" id="bdot" style="display:none">0</span></button> 817 <button class="tbtn tb-desk" id="refBtn" onclick="refresh()" title="Refresh"><i class="ti ti-refresh"></i></button> 818 <button class="tbtn tb-desk" onclick="logout()" title="Logout"><i class="ti ti-logout"></i></button> 819 </div> 820 <div id="eVeil" onclick="eSideClose()"></div> 821 <aside id="eSide"> 822 <div class="eside-h"><div class="eside-logo" id="esideAv">MVS</div><div class="et"><b id="topName2">Employee Portal</b><span id="topSub2">MVS FOUNDATION</span></div><button class="eside-x" onclick="eSideClose()" aria-label="Close">×</button></div> 823 <div class="eside-scroll"> 824 <div class="snav-item snav-dash active" data-tab="dashboard" onclick="eGo('dashboard',this)"><i class="ti ti-layout-dashboard"></i> Dashboard</div> 825 <div class="snav-grp"><div class="snav-head" onclick="eGrp(this)"><i class="ti ti-user-circle gi"></i><span class="gl">Me</span><i class="ti ti-chevron-down gc"></i></div><div class="snav-body"><div class="snav-bi"><div class="snav-item" data-tab="letters" data-mod="documents" onclick="eGo('letters',this)"><i class="ti ti-file-certificate"></i> HR Letters</div><div class="snav-item" data-tab="docs" data-mod="documents" onclick="eGo('docs',this)"><i class="ti ti-files"></i> Documents</div><div class="snav-item" data-tab="dept" data-mod="profile" onclick="eGo('dept',this)"><i class="ti ti-building"></i> Department</div><div class="snav-item" data-tab="salary" data-mod="salary" onclick="eGo('salary',this)"><i class="ti ti-receipt-2"></i> Salary</div></div></div></div> 826 <div class="snav-grp"><div class="snav-head" onclick="eGrp(this)"><i class="ti ti-clock-hour-4 gi"></i><span class="gl">Time & Leave</span><i class="ti ti-chevron-down gc"></i></div><div class="snav-body"><div class="snav-bi"><div class="snav-item" data-tab="attendance" data-mod="attendance" onclick="eGo('attendance',this)"><i class="ti ti-clock-check"></i> Attendance</div><div class="snav-item" data-tab="leave" data-mod="leave" onclick="eGo('leave',this)"><i class="ti ti-calendar-off"></i> Leave</div><div class="snav-item" data-tab="meetings" data-mod="attendance" onclick="eGo('meetings',this)"><i class="ti ti-calendar-event"></i> Meetings</div></div></div></div> 827 <div class="snav-grp"><div class="snav-head" onclick="eGrp(this)"><i class="ti ti-trending-up gi"></i><span class="gl">Growth</span><i class="ti ti-chevron-down gc"></i></div><div class="snav-body"><div class="snav-bi"><div class="snav-item" data-tab="goals" data-mod="goals" onclick="eGo('goals',this)"><i class="ti ti-target"></i> Goals</div><div class="snav-item" data-tab="tasks" data-mod="tasks" onclick="eGo('tasks',this)"><i class="ti ti-checkbox"></i> Tasks</div><div class="snav-item" data-tab="performance" data-mod="performance" onclick="eGo('performance',this)"><i class="ti ti-chart-line"></i> Performance</div><div class="snav-item" data-tab="rewards" data-mod="rewards" onclick="eGo('rewards',this)"><i class="ti ti-award"></i> Rewards</div><div class="snav-item" data-tab="learning" data-mod="learning" onclick="eGo('learning',this)"><i class="ti ti-school"></i> Learning</div></div></div></div> 828 <div class="snav-grp"><div class="snav-head" onclick="eGrp(this)"><i class="ti ti-building-community gi"></i><span class="gl">Workplace</span><i class="ti ti-chevron-down gc"></i></div><div class="snav-body"><div class="snav-bi"><div class="snav-item" data-tab="engagement" data-mod="engagement" onclick="eGo('engagement',this)"><i class="ti ti-message-2-heart"></i> Engagement</div><div class="snav-item" data-tab="directory" data-mod="directory" onclick="eGo('directory',this)"><i class="ti ti-address-book"></i> Directory</div><div class="snav-item" data-tab="orgchart" data-mod="directory" onclick="eGo('orgchart',this)"><i class="ti ti-sitemap"></i> Org Chart</div><div class="snav-item" data-tab="timeline" data-mod="notices" onclick="eGo('timeline',this)"><i class="ti ti-timeline"></i> Timeline</div><div class="snav-item" data-tab="forms" data-mod="engagement" onclick="eGo('forms',this)"><i class="ti ti-forms"></i> Forms</div><div class="snav-item" data-tab="notice" data-mod="notices" onclick="eGo('notice',this)"><i class="ti ti-speakerphone"></i> Notice</div><div class="snav-item" data-tab="policies" data-mod="policies" onclick="eGo('policies',this)"><i class="ti ti-book"></i> Policies</div></div></div></div> 829 <div class="snav-grp"><div class="snav-head" onclick="eGrp(this)"><i class="ti ti-headset gi"></i><span class="gl">Support</span><i class="ti ti-chevron-down gc"></i></div><div class="snav-body"><div class="snav-bi"><div class="snav-item" data-tab="help" data-mod="helpdesk" onclick="eGo('help',this)"><i class="ti ti-headset"></i> Help & Feedback</div><div class="snav-item" data-tab="messages" data-mod="helpdesk" onclick="eGo('messages',this)"><i class="ti ti-messages"></i> Messages</div><div class="snav-item" data-tab="kb" data-mod="helpdesk" onclick="eGo('kb',this)"><i class="ti ti-book-2"></i> Knowledge Base</div></div></div></div> 830 </div> 831 <div class="eside-foot"> 832 <button class="ef-btn" onclick="toggleTheme();eSideClose()"><i class="ti ti-moon"></i> Theme</button> 833 <button class="ef-btn" onclick="refresh();eSideClose()"><i class="ti ti-refresh"></i> Refresh</button> 834 <button class="ef-btn danger" onclick="logout()"><i class="ti ti-logout"></i> Logout</button> 835 </div> 836 </aside> 837 <div class="bpanel" id="bpanel"></div> 838 <div id="selfieOverlay"> 839 <div class="selfie-box"> 840 <div class="selfie-h"><b id="selfieTitle"><i class="ti ti-camera-selfie"></i> Verify itâs you</b><button class="gs-x" onclick="closeSelfie()" aria-label="Close">×</button></div> 841 <div class="selfie-sub" id="selfieSub">Take a quick live selfie to mark your attendance.</div> 842 <video id="selfieVid" class="selfie-vid" playsinline autoplay muted></video> 843 <div id="selfiePh" class="selfie-ph" style="display:none"><div class="selfie-ph-ic"><i class="ti ti-camera-selfie"></i></div><div class="selfie-ph-t">Take a quick selfie</div></div> 844 <div id="selfieState" style="font-size:12.5px;min-height:16px;margin:7px 2px 3px;text-align:center;color:#ffb0a2"></div> 845 <button class="btn selfie-cap" id="selfieCapBtn" onclick="captureSelfie()"><i class="ti ti-camera"></i> Capture & Punch</button> 846 <input type="file" accept="image/*" capture="user" id="selfieFileInput" style="display:none" onchange="selfieFilePicked(this)"> 847 <button id="selfieNativeBtn" onclick="selfieNativePick()" style="margin-top:9px;width:100%;background:none;border:1px solid rgba(255,255,255,.22);color:var(--muted);padding:11px;border-radius:12px;font-size:13px;font-weight:700;cursor:pointer"><i class="ti ti-camera-plus"></i> Camera not opening? Take photo instead</button> 848 </div> 849 </div> 850 <div id="gsOverlay" onclick="if(event.target===this)gsClose()"> 851 <div class="gs-box"> 852 <div class="gs-in"><i class="ti ti-search"></i><input id="gsInput" placeholder="Search everythingâ¦" oninput="gsSearch()" autocomplete="off"><button class="gs-x" onclick="gsClose()" aria-label="Close">×</button></div> 853 <div id="gsResults"><div class="gs-hint">Search letters, colleagues, meetings, articles & moreâ¦</div></div> 854 </div> 855 </div> 856 857 <div class="wrap"> 858 <section class="panel show" id="dashboard"> 859 <section class="snap"> 860 <div class="snap-l"> 861 <div class="snap-hi">
862 <span class="snap-eyebrow" id="greetPrefix">Welcome</span> 863 <h2 id="eName">Employee</h2> 864 </div> 865 <a id="hbBtn" class="snap-hb" target="_blank" title="Employee Handbook"><i class="ti ti-book-2"></i><span>Handbook</span></a> 866 </div> 867 <div class="snap-chips"> 868 <div class="chip"><i class="ti ti-id-badge-2"></i> ID: <b id="eId" style="color:#fff;margin-left:4px">â</b></div> 869 <div class="chip" id="mgrChip" style="display:none"><i class="ti ti-user-star"></i> Manager: <b id="eMgr" style="color:#fff;margin-left:4px"></b></div> 870 <span class="chip" id="eRoleBadge" style="display:none"><i class="ti ti-shield-check"></i> <b id="eRoleTxt" style="color:#fff;margin-left:4px"></b></span> 871 <div class="chip"><i class="ti ti-user-check"></i> Profile: <b id="eProfilePct" style="color:#fff;margin-left:4px">â</b></div> 872 </div> 873 </section> 874 875 <div id="punchCard"></div> 876 <div class="crm" id="crm"></div> 877 <div id="onboardCard"></div> 878 <div id="assetsCard"></div> 879 880 <div id="warnTop"></div> 881 882 <div class="dsec-h"><i class="ti ti-layout-grid"></i> Todayâs Workspace</div> 883 <div class="dash-grid"> 884 <div class="dcard s8"><div class="dcard-h"><span><i class="ti ti-checklist"></i> My Tasks</span><a class="dcard-all" onclick="dashGo('tasks')">View all <i class="ti ti-arrow-right"></i></a></div><div id="dwTasks"><div class="dw-sk"></div><div class="dw-sk"></div><div class="dw-sk"></div></div></div> 885 <div class="dcard s4"><div class="dcard-h"><span><i class="ti ti-bolt"></i> Quick Actions</span></div> 886 <div class="dw-quick"> 887 <button class="qa" onclick="dashGo('leave')"><i class="ti ti-calendar-plus"></i><span>Apply Leave</span></button> 888 <button class="qa" onclick="dashGo('salary')"><i class="ti ti-cash"></i><span>Salary Slip</span></button> 889 <button class="qa" onclick="dashGo('docs')"><i class="ti ti-files"></i><span>Documents</span></button> 890 <button class="qa" onclick="dashGo('attendance')"><i class="ti ti-clock-check"></i><span>Attendance</span></button> 891 <button class="qa" onclick="dashGo('letters')"><i class="ti ti-file-text"></i><span>HR Letters</span></button> 892 <button class="qa" onclick="dashGo('help')"><i class="ti ti-headset"></i><span>Raise Ticket</span></button> 893 </div> 894 </div> 895 <div class="dcard s6"><div class="dcard-h"><span><i class="ti ti-speakerphone"></i> Company Updates</span><a class="dcard-all" onclick="dashGo('notice')">View all <i class="ti ti-arrow-right"></i></a></div><div id="dwUpdates"><div class="dw-sk"></div><div class="dw-sk"></div></div></div> 896 <div class="dcard s6"><div class="dcard-h"><span><i class="ti ti-calendar-event"></i> Upcoming</span><a class="dcard-all" onclick="dashGo('meetings')">View all <i class="ti ti-arrow-right"></i></a></div><div id="dwUpcoming"><div class="dw-sk"></div><div class="dw-sk"></div></div></div> 897 <div class="dcard s4"><div class="dcard-h"><span><i class="ti ti-calendar-stats"></i> Leave Balance</span><a class="dcard-all" onclick="dashGo('leave')">Apply <i class="ti ti-arrow-right"></i></a></div><div id="dwLeave"><div class="dw-sk"></div><div class="dw-sk"></div></div></div> 898 <div class="dcard s4"><div class="dcard-h"><span><i class="ti ti-chart-line"></i> Performance</span><a class="dcard-all" onclick="dashGo('performance')">Details <i class="ti ti-arrow-right"></i></a></div><div id="dwPerf"><div class="dw-sk"></div></div></div> 899 <div class="dcard s4 dcard-journey"><section class="acc" id="progCard" style="margin:14px 0"> 900 <div class="acc-h" onclick="toggleAcc(this)"><div class="ai"><i class="ti ti-rocket"></i></div><div class="at"><b>Your Journey Progress</b><span id="progSub">Loadingâ¦</span></div><i class="ti ti-chevron-down chev"></i></div> 901 <div class="acc-b"><div class="acc-bi"> 902 <div class="progbar" style="margin-top:6px"><div class="progfill" id="progFill" style="width:0%"></div></div> 903 <div id="progSteps"></div> 904 <div class="pmsg" id="progMsg"></div> 905 </div></div> 906 </section></div> 907 </div> 908 </section> 909 910 <nav class="tabs"> 911 <button class="tab active" data-tab="dashboard" onclick="showTab('dashboard',this)"><i class="ti ti-layout-dashboard"></i> Dashboard</button> 912 <button class="tab" data-tab="letters" data-mod="documents" onclick="showTab('letters',this)"><i class="ti ti-file-certificate"></i> HR Letters</button> 913 <button class="tab" data-tab="docs" data-mod="documents" onclick="showTab('docs',this)"><i class="ti ti-files"></i> Documents</button> 914 <button class="tab" data-tab="dept" data-mod="profile" onclick="showTab('dept',this)"><i class="ti ti-building"></i> Department</button> 915 <button class="tab" data-tab="salary" data-mod="salary" onclick="showTab('salary',this)"><i class="ti ti-receipt-2"></i> Salary</button> 916 <button class="tab" data-tab="attendance" data-mod="attendance" onclick="showTab('attendance',this)"><i class="ti ti-clock-check"></i> Attendance</button> 917 <button class="tab" data-tab="leave" data-mod="leave" onclick="showTab('leave',this)"><i class="ti ti-calendar-off"></i> Leave</button> 918 <button class="tab" data-tab="goals" data-mod="goals" onclick="showTab('goals',this)"><i class="ti ti-target"></i> Goals</button> 919 <button class="tab" data-tab="tasks" data-mod="tasks" onclick="showTab('tasks',this)"><i class="ti ti-checkbox"></i> Tasks</button> 920 <button class="tab" data-tab="performance" data-mod="performance" onclick="showTab('performance',this)"><i class="ti ti-chart-line"></i> Performance</button> 921 <button class="tab" data-tab="rewards" data-mod="rewards" onclick="showTab('rewards',this)"><i class="ti ti-award"></i> Rewards</button> 922 <button class="tab" data-tab="learning" data-mod="learning" onclick="showTab('learning',this)"><i class="ti ti-school"></i> Learning</button> 923 <button class="tab" data-tab="engagement" data-mod="engagement" onclick="showTab('engagement',this)"><i class="ti ti-message-2-heart"></i> Engagement</button> 924 <button class="tab" data-tab="directory" data-mod="directory" onclick="showTab('directory',this)"><i class="ti ti-address-book"></i> Directory</button> 925 <button class="tab" data-tab="orgchart" data-mod="directory" onclick="showTab('orgchart',this)"><i class="ti ti-sitemap"></i> Org Chart</button> 926 <button class="tab" data-tab="timeline" data-mod="notices" onclick="showTab('timeline',this)"><i class="ti ti-timeline"></i> Timeline</button> 927 <button class="tab" data-tab="kb" data-mod="helpdesk" onclick="showTab('kb',this)"><i class="ti ti-book-2"></i> Knowledge Base</button> 928 <button class="tab" data-tab="meetings" data-mod="attendance" onclick="showTab('meetings',this)"><i class="ti ti-calendar-event"></i> Meetings</button> 929 <button class="tab" data-tab="forms" data-mod="engagement" onclick="showTab('forms',this)"><i class="ti ti-forms"></i> Forms</button> 930 <button class="tab" data-tab="messages" data-mod="helpdesk" onclick="showTab('messages',this)"><i class="ti ti-messages"></i> Messages</button> 931 <button class="tab" data-tab="notice" data-mod="notices" onclick="showTab('notice',this)"><i class="ti ti-speakerphone"></i> Notice</button> 932 <button class="tab" data-tab="policies" data-mod="policies" onclick="showTab('policies',this)"><i class="ti ti-book"></i> Policies</button> 933 <button class="tab" data-tab="help" data-mod="helpdesk" onclick="showTab('help',this)"><i class="ti ti-headset"></i> Help & Feedback</button> 934 </nav> 935 936 <section class="panel card" id="letters"> 937 <div class="sectitle"><i class="ti ti-file-certificate"></i> HR Letters</div> 938 <p style="color:var(--muted);font-size:13px;margin:-4px 0 14px">Letters & documents issued to you by HR. Download anytime.</p> 939 <div id="myLettersList"><div class="empty">Loadingâ¦</div></div> 940 </section> 941 942 <section class="panel card" id="docs"> 943 <div class="sectitle"><i class="ti ti-files"></i> My Documents</div> 944 <div id="docsList"></div> 945 </section> 946 947 <section class="panel card" id="dept"> 948 <div class="sectitle"><i class="ti ti-building"></i> Department & Role</div> 949 <div class="kv" id="deptKv"></div> 950 <div style="margin-top:18px;display:flex;gap:10px;flex-wrap:wrap"> 951 <button class="btn" onclick="openKRA()"><i class="ti ti-target"></i> View My KRA</button> 952 <a class="dl ghost" onclick="genKRA()" style="cursor:pointer"><i class="ti ti-download"></i> Download KRA</a> 953 </div> 954 </section> 955 956 <section class="panel card" id="salary"> 957 <div class="sectitle"><i class="ti ti-receipt-2"></i> Salary</div> 958 <p style="color:var(--muted);font-size:13px;margin-bottom:14px">Review each month's slip. Any discrepancy? Use "Report issue".</p> 959 <div id="salAnnex"></div> 960 <div id="slipList"></div> 961 <div id="issueBox" style="display:none;margin-top:6px"> 962 <label>Describe the issue for <b id="issueMonth"></b></label> 963 <textarea id="issueText" placeholder="Explain the salary issue..."></textarea> 964 <button class="btn" onclick="submitIssue()">Submit Issue</button> 965 </div> 966 <div id="myIssues" style="margin-top:16px"></div> 967 <div id="salRevisions"></div> 968 <div class="sectitle" style="margin-top:24px"><i class="ti ti-receipt"></i> Reimbursements</div> 969 <p style="color:var(--muted);font-size:13px;margin:-4px 0 12px">Claim work expenses. Once approved by Accounts, they're paid with your salary.</p> 970 <div class="lv-form"> 971 <div><label>Category</label><select class="inp" id="reimCat"><option value="travel">Travel</option><option value="food">
971Food</option><option value="hotel">Hotel</option><option value="petrol">Petrol</option><option value="internet">Internet</option><option value="medical">Medical</option><option value="other">Other</option></select></div> 972 <div><label>Amount (â¹)</label><input class="inp" type="number" id="reimAmt"></div> 973 <div><label>Date</label><input class="inp" type="date" id="reimDate"></div> 974 </div> 975 <label style="font-size:12px;color:var(--muted);display:block;margin-bottom:4px">Description</label> 976 <textarea id="reimDesc" placeholder="What is this expense for?"></textarea> 977 <label class="reim-file"><i class="ti ti-paperclip"></i> Attach bill (optional)<input type="file" id="reimFile" accept="image/*,application/pdf" style="display:none"></label> 978 <button class="btn" style="margin-top:10px" onclick="submitReim()"><i class="ti ti-send"></i> Submit claim</button> 979 <div id="myReims" style="margin-top:16px"></div> 980 </section> 981 982 <section class="panel card" id="attendance"> 983 <div class="sectitle"><i class="ti ti-clock-check"></i> Attendance</div> 984 <div class="acal-head"><button class="sal-nav" onclick="attMonth(-1)"><i class="ti ti-chevron-left"></i></button><span id="acalLabel" class="sal-yrlbl"></span><button class="sal-nav" onclick="attMonth(1)"><i class="ti ti-chevron-right"></i></button></div> 985 <div id="attSummary"></div> 986 <div id="attCalendar"></div> 987 <div class="acal-legend"><span><i class="acal-dot present"></i> Present</span><span><i class="acal-dot half"></i> Half</span><span><i class="acal-dot leave"></i> Leave</span><span><i class="acal-dot holiday"></i> Holiday</span><span><i class="acal-dot absent"></i> Absent</span><span><i class="acal-dot warn"></i> Missing punch-out</span></div> 988 </section> 989 990 <section class="panel card" id="leave"> 991 <div class="sectitle"><i class="ti ti-calendar-off"></i> Leave</div> 992 <div id="leaveBalances"></div> 993 <div id="leaveHolidays"></div> 994 <div class="accwrap-head" style="margin-top:14px">Apply for leave</div> 995 <div class="lv-form"> 996 <div><label>Type</label><select class="inp" id="lvType"></select></div> 997 <div><label>From</label><input class="inp" type="date" id="lvFrom"></div> 998 <div><label>To</label><input class="inp" type="date" id="lvTo"></div> 999 </div> 1000 <label class="lv-half"><input type="checkbox" id="lvHalf"> Half day</label> 1001 <textarea id="lvReason" placeholder="Reason for leaveâ¦"></textarea> 1002 <button class="btn" onclick="applyLeave()"><i class="ti ti-send"></i> Apply for Leave</button> 1003 <div id="myLeaves" style="margin-top:16px"></div> 1004 </section> 1005 1006 <section class="panel card" id="goals"> 1007 <div class="sectitle"><i class="ti ti-target"></i> Goals</div> 1008 <div id="goalsWrap"><div class="empty">Loadingâ¦</div></div> 1009 </section> 1010 1011 <section class="panel card" id="tasks"> 1012 <div class="sectitle"><i class="ti ti-checkbox"></i> My Tasks</div> 1013 <div id="tasksWrap"><div class="empty">Loadingâ¦</div></div> 1014 </section> 1015 1016 <section class="panel card" id="performance"> 1017 <div class="sectitle"><i class="ti ti-chart-line"></i> My Performance</div> 1018 <div id="perfWrap"><div class="empty">Loadingâ¦</div></div> 1019 </section> 1020 1021 <section class="panel card" id="rewards"> 1022 <div class="sectitle"><i class="ti ti-award"></i> Rewards & Recognition</div> 1023 <div id="rewardsWrap"><div class="empty">Loadingâ¦</div></div> 1024 </section> 1025 1026 <section class="panel card" id="learning"> 1027 <div class="sectitle"><i class="ti ti-school"></i> Learning & Training</div> 1028 <div id="coursesWrap"><div class="empty">Loadingâ¦</div></div> 1029 </section> 1030 1031 <section class="panel card" id="engagement"> 1032 <div class="sectitle"><i class="ti ti-message-2-heart"></i> Engagement</div> 1033 <div id="engageWrap"><div class="empty">Loadingâ¦</div></div> 1034 </section> 1035 1036 <section class="panel card" id="directory"> 1037 <div class="sectitle"><i class="ti ti-address-book"></i> Team Directory</div> 1038 <input class="inp" id="dirSearch" placeholder="Search by name, department, designationâ¦" oninput="renderDirectory()" style="margin-bottom:14px"> 1039 <div id="dirList"><div class="empty">Loadingâ¦</div></div> 1040 </section> 1041 1042 <section class="panel card" id="orgchart"> 1043 <div class="sectitle"><i class="ti ti-sitemap"></i> Organization Chart</div> 1044 <p style="color:var(--muted);font-size:13px;margin-bottom:14px">Reporting structure across the team.</p> 1045 <div id="orgWrap"><div class="empty">Loadingâ¦</div></div> 1046 </section> 1047 1048 <section class="panel card" id="timeline"> 1049 <div class="sectitle"><i class="ti ti-timeline"></i> Company Timeline</div> 1050 <p style="color:var(--muted);font-size:13px;margin-bottom:14px">Milestones & moments at MVS Foundation.</p> 1051 <div id="tlWrap"><div class="empty">Loadingâ¦</div></div> 1052 </section> 1053 1054 <section class="panel card" id="kb"> 1055 <div class="sectitle"><i class="ti ti-book-2"></i> Knowledge Base</div> 1056 <input class="inp" id="kbSearch" placeholder="Search articles, FAQsâ¦" oninput="renderKB()" style="margin-bottom:10px"> 1057 <div class="kb-cats" id="kbCats"></div> 1058 <div id="kbList"><div class="empty">Loadingâ¦</div></div> 1059 </section> 1060 1061 <section class="panel card" id="meetings"> 1062 <div class="sectitle"><i class="ti ti-calendar-event"></i> Meetings & Events</div> 1063 <div class="kb-cats" id="mtgTabs"><button class="kb-cat on" onclick="mtgView('upcoming')">Upcoming</button><button class="kb-cat" onclick="mtgView('past')">Past</button></div> 1064 <div id="mtgList"><div class="empty">Loadingâ¦</div></div> 1065 </section> 1066 1067 <section class="panel card" id="forms"> 1068 <div class="sectitle"><i class="ti ti-forms"></i> Forms</div> 1069 <div id="formsList"><div class="empty">Loadingâ¦</div></div> 1070 </section> 1071 1072 <section class="panel card" id="messages"> 1073 <div class="sectitle"><i class="ti ti-messages"></i> Messages with HR</div> 1074 <div class="msg-msgs" id="msgMsgs"><div class="empty">Loadingâ¦</div></div> 1075 <div class="msg-in"><input class="inp" id="msgInput" placeholder="Type a message to HRâ¦" onkeydown="if(event.key==='Enter')sendMsg()"><button class="btn" onclick="sendMsg()"><i class="ti ti-send"></i></button></div> 1076 </section> 1077 1078 <section class="panel card" id="notice"> 1079 <div class="sectitle"><i class="ti ti-speakerphone"></i> Notices</div> 1080 <div id="noticeList"></div> 1081 </section> 1082 1083 <section class="panel card" id="policies"> 1084 <div class="sectitle"><i class="ti ti-book"></i> Company Policies</div> 1085 <div id="policyList"></div> 1086 </section> 1087 1088 <section class="panel card" id="help"> 1089 <div class="sectitle"><i class="ti ti-headset"></i> Need Help? Raise a Request</div> 1090 <textarea id="hlpText" placeholder="Describe your query or problem..."></textarea> 1091 <button class="btn" onclick="submitComplaint()">Submit Request</button> 1092 <div id="myComplaints" style="margin-top:16px"></div> 1093 <div id="myApprovals"></div> 1094 <div class="sectitle" style="margin-top:24px">
1094<i class="ti ti-door-exit"></i> Resignation / Exit</div> 1095 <div id="resignBox"></div> 1096 </section> 1097 </div> 1098 </div> 1099 <div id="kraModal" onclick="if(event.target===this)this.style.display='none'" style="display:none;position:fixed;inset:0;z-index:80;background:rgba(5,10,18,.72);backdrop-filter:blur(4px);align-items:center;justify-content:center;padding:20px"> 1100 <div style="width:min(560px,100%);background:#141418;border:1px solid var(--line);border-radius:18px;padding:24px;max-height:80vh;overflow:auto;box-shadow:0 30px 80px -20px rgba(0,0,0,.8)"> 1101 <div style="display:flex;align-items:center;gap:10px;margin-bottom:14px"><i class="ti ti-target" style="color:var(--gold-soft);font-size:22px"></i><b style="font-size:18px;flex:1">Key Responsibility Areas (KRA)</b><button onclick="document.getElementById('kraModal').style.display='none'" style="background:none;border:none;color:var(--muted);font-size:24px;cursor:pointer;line-height:1">Ã</button></div> 1102 <div id="kraBody" style="font-size:14px;line-height:1.8;color:var(--txt);white-space:pre-wrap"></div> 1103 </div> 1104 </div> 1105 <div id="toast"></div> 1106 <div class="pdfstage" id="pdfstage"></div> 1107
1108<script> 1109 const API_URL = "https://qqbpgfbyyvjuqogardbv.supabase.co/functions/v1/api"; 1110 try{ fetch(API_URL,{method:"POST",body:new URLSearchParams({action:"warmup"}),keepalive:true}).catch(function(){}); }catch(_e){} // warm the edge before login 1111 let BUNDLE=null, currentId='', rateVal=0, cmpAll=false, _myLettersLoaded=false; 1112 1113 /* ---------- helpers ---------- */ 1114 /* Browser = session-only login (clears on close); installed MVS app = persists across app close 1115 (WebView clears sessionStorage on process death). Detected via the app's UA suffix. */ 1116 var _INAPP = / MVSPortalApp\//.test(navigator.userAgent); 1117 var _SS = window.localStorage; // persist login in BOTH browser + app (staff enter password once, stay logged in) 1118 function ssGet(k){try{return _SS.getItem(k)||''}catch(e){return''}} 1119 function ssSet(k,v){try{_SS.setItem(k,v)}catch(e){}} 1120 function ssDel(k){try{_SS.removeItem(k); localStorage.removeItem(k);}catch(e){}} 1121 /* Bridge to the installed Android app for the background red badge (in-app + launcher icon). No-op in a browser. */ 1122 function _mvsSaveSession(id,tok,name){try{ if(window.MvsNative && MvsNative.saveSession) MvsNative.saveSession(String(id||''),String(tok||''),String(name||'')); }catch(e){}} 1123 function _mvsClearSession(){try{ if(window.MvsNative && MvsNative.clearSession) MvsNative.clearSession(); }catch(e){}} 1124 function lsGet(k){try{return localStorage.getItem(k)||''}catch(e){return''}} 1125 function lsSet(k,v){try{localStorage.setItem(k,v)}catch(e){}} 1126 function applyTheme(t){ document.body.classList.toggle('night', t==='night'); const b=document.getElementById('themeBtn'); if(b) b.innerHTML='<i class="ti '+(t==='night'?'ti-moon-stars':'ti-moon')+'"></i>'; } 1127 function toggleTheme(){ const t=document.body.classList.contains('night')?'dark':'night'; lsSet('ems_theme',t); applyTheme(t); } 1128 applyTheme(lsGet('ems_theme')||'dark'); 1129 document.addEventListener('click',function(ev){var t=ev.target; if(t&&t.tagName==='INPUT'&&t.type==='date'){try{t.showPicker();}catch(e){}}}); 1130 function toggleAcc(h){ h.parentElement.classList.toggle('open'); } 1131 function eSideOpen(){ var d=document.getElementById('eSide'),v=document.getElementById('eVeil'); if(d)d.classList.add('on'); if(v)v.classList.add('on'); document.body.style.overflow='hidden'; } 1132 function eSideClose(){ var d=document.getElementById('eSide'),v=document.getElementById('eVeil'); if(d)d.classList.remove('on'); if(v)v.classList.remove('on'); document.body.style.overflow=''; } 1133 function eGrp(head){ head.parentElement.classList.toggle('open'); } 1134 function eGo(id,el){ var btn=document.querySelector('.tab[data-tab="'+id+'"]'); if(btn) showTab(id,btn); document.querySelectorAll('#eSide .snav-item').forEach(function(n){n.classList.remove('active');}); if(el){ el.classList.add('active'); var g=el.closest('.snav-grp'); if(g&&!g.classList.contains('open')) g.classList.add('open'); } eSideClose(); } 1135 // Show/hide the whole attendance system (punch card + Attendance & Leave nav items, tabs and sections) based 1136 // on whether attendance is enabled for this employee. Docs/letters-only staff get everything else as normal. 1137 function _applyAttEnabled(){ 1138 var on = window._attEnabled !== false; 1139 var HIDE=['attendance','leave']; 1140 try{ 1141 var _pc=document.getElementById('punchCard'); if(_pc){ _pc.style.display=on?'':'none'; if(!on) _pc.innerHTML=''; } 1142 HIDE.forEach(function(tab){ document.querySelectorAll('[data-tab="'+tab+'"]').forEach(function(x){ x.style.display=on?'':'none'; }); }); 1143 if(!on){ 1144 // If the person is currently on a now-hidden section, send them back to the dashboard. 1145 var cur=window._curTab||''; 1146 if(HIDE.indexOf(cur)>=0){ var db=document.querySelector('.snav-item[data-tab="dashboard"]'); if(db){ try{ eGo('dashboard',db); }catch(_){} } } 1147 HIDE.forEach(function(id){ var pn=document.getElementById(id); if(pn) pn.classList.remove('show'); }); 1148 } 1149 }catch(_){} 1150 } 1151 function eSyncActive(){ var sh=document.querySelector('.panel.show'); var id=sh?sh.id:'dashboard'; var it=document.querySelector('#eSide .snav-item[data-tab="'+id+'"]'); document.querySelectorAll('#eSide .snav-item').forEach(function(n){n.classList.remove('active');}); if(it){ it.classList.add('active'); var g=it.closest('.snav-grp'); if(g) g.classList.add('open'); } } 1152 /* ---- Global Search ---- */ 1153 var _gsT; 1154 function gsOpen(){ var o=document.getElementById('gsOverlay'); if(o){ o.classList.add('on'); setTimeout(function(){var i=document.getElementById('gsInput'); if(i)i.focus();},60); } } 1155 function gsClose(){ var o=document.getElementById('gsOverlay'); if(o) o.classList.remove('on'); } 1156 function gsSearch(){ clearTimeout(_gsT); _gsT=setTimeout(gsDo,250); } 1157 async function gsDo(){ 1158 const el=document.getElementById('gsResults'); if(!el) return; 1159 const q=((document.getElementById('gsInput')||{}).value||''); 1160 if(q.trim().length<2){ el.innerHTML='<div class="gs-hint">Type at least 2 charactersâ¦</div>'; return; } 1161 el.innerHTML='<div class="gs-hint">Searchingâ¦</div>'; 1162 const id=currentId||ssGet('ems_id'); 1163 const d=await api({action:'emsGlobalSearch', employeeId:id, q:q}); 1164 const rows=(d&&d.rows)||[]; 1165 if(!rows.length){ el.innerHTML='<div class="gs-hint">No results for "'+esc(q)+'"</div>'; return; } 1166 const groups={}; rows.forEach(function(r){ (groups[r.type]=groups[r.type]||[]).push(r); }); 1167 const ico={'Knowledge Base':'ti-book-2','Meeting':'ti-calendar-event','Timeline':'ti-timeline','Colleague':'ti-user','Notice':'ti-speakerphone','HR Letter':'ti-file-certificate'}; 1168 let h='';
1168 Object.keys(groups).forEach(function(t){ h+='<div class="gs-cat">'+esc(t)+'</div>'; h+=groups[t].map(function(r){ return '<div class="gs-item" onclick="gsGo(\''+esc(r.tab)+'\')"><i class="ti '+(ico[t]||'ti-point')+'"></i><div class="gs-txt"><b>'+esc(r.title||'')+'</b>'+(r.subtitle?'<span>'+esc(r.subtitle)+'</span>':'')+'</div><i class="ti ti-arrow-right gs-go"></i></div>'; }).join(''); }); 1169 el.innerHTML=h; 1170 } 1171 function gsGo(tab){ gsClose(); var it=document.querySelector('#eSide .snav-item[data-tab="'+tab+'"]'); if(it){ eGo(tab,it); } else { var btn=document.querySelector('.tab[data-tab="'+tab+'"]'); if(btn) showTab(tab,btn); } } 1172 function accItem(icon, title, sub, bodyHtml, urgent){ 1173 return '<div class="acc"><div class="acc-h" onclick="toggleAcc(this)"><div class="ai"'+(urgent?' style="background:rgba(224,83,63,.16);color:#ff9c8b"':'')+'><i class="ti '+icon+'"></i></div><div class="at"><b>'+title+'</b>'+(sub?'<span>'+sub+'</span>':'')+'</div><i class="ti ti-chevron-down chev"></i></div><div class="acc-b"><div class="acc-bi">'+bodyHtml+'</div></div></div>'; 1174 } 1175 function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]))} 1176 function rupee(v){const n=Number(String(v==null?'':v).replace(/[^0-9.]/g,''))||0;return '\u20B9'+n.toLocaleString('en-IN')} 1177 function numv(v){return Number(String(v==null?'':v).replace(/[^0-9.]/g,''))||0} 1178 function toast(m){const t=document.getElementById('toast');t.textContent=m;t.style.display='block';clearTimeout(t._t);t._t=setTimeout(()=>t.style.display='none',2600)} 1179 function fmt12(t){ t=String(t||""); if(t.indexOf(":")<0) return t; var pp=t.split(":"); var h=parseInt(pp[0],10), m=pp[1]; if(isNaN(h)) return t; var ap=h>=12?"PM":"AM"; var h12=(h%12)||12; return h12+":"+m+" "+ap; } 1180 function fmtDate(v){if(!v)return'';let d=new Date(String(v).indexOf('T')<0?String(v).replace(' ','T'):String(v));if(isNaN(d))return String(v);try{return d.toLocaleDateString('en-IN',{day:'numeric',month:'long',year:'numeric',timeZone:'Asia/Kolkata'})}catch(e){return String(v)}} 1181 function fmtDT(v){if(!v)return'';let d=new Date(String(v).indexOf('T')<0?String(v).replace(' ','T'):String(v));if(isNaN(d))return String(v);try{return d.toLocaleString('en-IN',{day:'numeric',month:'short',year:'numeric',hour:'numeric',minute:'2-digit',hour12:true,timeZone:'Asia/Kolkata'}).replace(/\u202f/g,' ')}catch(e){return String(v)}} 1182 function tgl(){const p=document.getElementById('liPw');p.type=p.type==='password'?'text':'password'} 1183 async function api(params){const fd=new URLSearchParams();Object.keys(params).forEach(k=>fd.append(k,params[k]));const r=await fetch(API_URL,{method:'POST',body:fd});const _t=await r.text();return JSON.parse(_t.replace(/https?:\/\/[a-z0-9-]+\.supabase\.co\/storage\/v1\/object\/public\/(uploads|media)\//gi,"https://files.mediamvs.com/$1/"));} 1184 1185 /* ---------- login ---------- */ 1186 async function doLogin(){ 1187 const id=document.getElementById('liId').value.trim(), pw=document.getElementById('liPw').value.trim(); 1188 const msg=document.getElementById('liMsg'); msg.textContent=''; 1189 if(!id||!pw){msg.textContent='Enter Employee ID and password.';return} 1190 const b=document.getElementById('liBtn'); b.disabled=true; b.textContent='Signing inâ¦'; 1191 try{ const d=await api({action:'emsLogin', employeeId:id, password:pw}); 1192 if(d.status==='success'){ currentId=id; ssSet('ems_id',id); ssSet('ems_tok',d.token||''); _mvsSaveSession(id, d.token||'', (d.name||d.employeeName||id)); load(d); document.getElementById('login').classList.add('hidden'); document.getElementById('app').style.display='block'; try{ window._curTab='dashboard'; ping('dashboard'); }catch(_){} } 1193 else msg.textContent=d.message||'Login failed.'; 1194 }catch(e){ msg.textContent='Could not connect to server.'; } 1195 b.disabled=false; b.textContent='Login'; 1196 } 1197 function logout(){ _mvsClearSession(); ssDel('ems_id'); ssDel('ems_tok'); currentId=''; location.reload(); } 1198 1199 /* ---------- load + render ---------- */ 1200 function load(d){
1201 // A lite background refresh omits the rarely-changing global arrays to save data â keep the copies the 1202 // app already has so the Policies / Company Docs / Performers tabs never go blank between full loads. 1203 if(BUNDLE){ ['policies','companyDocs','performers'].forEach(function(k){ if(d[k]===undefined && BUNDLE[k]!==undefined) d[k]=BUNDLE[k]; }); } 1204 BUNDLE=d; const e=d.employee||{}; 1205 // Attendance system shows only when it's enabled for this employee. Docs/letters-only staff never see 1206 // punch in/out, the attendance calendar, or Leave â everything attendance-related is hidden for them. 1207 window._attEnabled = (d.attendanceEnabled !== false); 1208 try{ _applyAttEnabled(); }catch(_ae){} 1209 try{ var _tn=document.getElementById('topName'); if(_tn)_tn.textContent=e.name||'Employee'; var _ts=document.getElementById('topSub'); if(_ts)_ts.textContent=e.designation||e.department||'MVS Foundation'; var _tl=document.getElementById('topLogo'); if(_tl && e.photo){ _tl.src=e.photo; _tl.classList.add('has-photo'); } var _tn2=document.getElementById('topName2'); if(_tn2)_tn2.textContent=e.name||'Employee'; var _ts2=document.getElementById('topSub2'); if(_ts2)_ts2.textContent=e.designation||e.department||'MVS Foundation'; var _ea=document.getElementById('esideAv'); if(_ea){ if(e.photo){ _ea.style.backgroundImage='url('+e.photo+')'; _ea.style.backgroundSize='cover'; _ea.style.backgroundPosition='center'; _ea.textContent=''; _ea.style.borderRadius='50%'; } else { _ea.textContent=(String(e.name||'?').trim()[0]||'M').toUpperCase(); } } }catch(_th){} 1210 window._empName=e.name||'Employee'; typeName(window._empName); 1211 try{ var _av=document.getElementById('eAvatar'); if(_av){ _av.style.backgroundImage=''; _av.textContent=(String(e.name||'?').trim()[0]||'?').toUpperCase(); } }catch(_a){} 1212 try{ var _fk='ems_greeted_'+(e.employeeId||'x'), _first=!localStorage.getItem(_fk); if(_first) localStorage.setItem(_fk,'1'); var _hr=new Date().getHours(); var _gp=(_hr<12?'Good morning':(_hr<17?'Good afternoon':(_hr<21?'Good evening':'Good night'))); var _ge=document.getElementById('greetPrefix'); if(_ge) _ge.textContent=_gp; }catch(_g){} 1213 document.getElementById('eId').textContent=e.employeeId||'â'; 1214 var mgr=d.reportingManager||e.reportingManager||''; var mc=document.getElementById('mgrChip'); if(mc){ if(mgr){ document.getElementById('eMgr').textContent=mgr; mc.style.display='inline-flex'; } else mc.style.display='none'; } 1215 const hb=(d.config||{}).handbookLink; const hbBtn=document.getElementById('hbBtn'); if(hbBtn){ hbBtn.style.display='inline-flex'; if(hb){ hbBtn.href=hb; hbBtn.onclick=null; } else { hbBtn.removeAttribute('href'); hbBtn.onclick=function(ev){ev.preventDefault(); toast('Employee handbook will be available soon.');}; } } 1216 renderCrm(d); renderProgress(d); renderDocs(d); renderDept(d); 1217 renderSalary(d); renderNotices(d); renderComplaints(d.complaints||[]); renderBell(d); 1218 renderPolicies(d); renderWarnings(d); renderResignation(d); 1219 requestAnimationFrame(function(){ emsFoundationLoad(); renderPunchCard(); renderOnboardCard(); renderAssetsCard(); setTimeout(eSyncActive,60); setTimeout(loadDashboard,120); }); 1220 } 1221 function svcBreak(doj){ 1222 const d=new Date(String(doj||'').replace(' ','T')); if(isNaN(d)) return null; 1223 const now=new Date(); let y=now.getFullYear()-d.getFullYear(), mo=now.getMonth()-d.getMonth(), day=now.getDate()-d.getDate(); 1224 if(day<0){ mo--; day+=new Date(now.getFullYear(),now.getMonth(),0).getDate(); } 1225 if(mo<0){ y--; mo+=12; } 1226 return {y:y,mo:mo,d:day}; 1227 } 1228 function svcVal(doj, totalDays){ 1229 const dd=new Date(String(doj||'').replace(' ','T')); 1230 if(isNaN(dd)) return '<span>'+(totalDays||0)+'</span> days'; 1231 const now=new Date(); 1232 if(dd.getTime() > now.getTime()){ 1233 const inDays=Math.max(0, Math.ceil((dd.getTime()-now.getTime())/86400000)); 1234 const dLabel=dd.toLocaleDateString('en-IN',{day:'numeric',month:'short',year:'numeric'}); 1235 const sub='Joins '+dLabel+(inDays?(' \u00b7 in '+inDays+' day'+(inDays>1?'s':'')):''); 1236 return '<span>Not started</span><small style="display:block;margin-top:5px;color:var(--faint);font-weight:500">'+sub+'</small>'; 1237 } 1238 /* LIVE tenure: whole calendar days since joining â joining day = 0, next day = 1, and so on (independent of time of day). */ 1239 const a0=new Date(dd.getFullYear(),dd.getMonth(),dd.getDate()); 1240 const b0=new Date(now.getFullYear(),now.getMonth(),now.getDate()); 1241 const total=Math.max(0, Math.floor((b0-a0)/86400000)); 1242 if(total<=0) return '<span>Today</span><small style="display:block;margin-top:5px;color:var(--faint);font-weight:500">Joined today</small>'; 1243 const b=svcBreak(doj); 1244 const parts=[]; if(b&&b.y>0) parts.push(b.y+'y'); if(b&&b.mo>0) parts.push(b.mo+'mo'); parts.push(Math.max(0,(b?b.d:total))+'d'); 1245 const main=parts.join(' '); 1246 return '<span>'+main+'</span><small style="display:block;margin-top:5px;color:var(--faint);font-weight:500">'+total+' day'+(total===1?'':'s')+' total</small>'; 1247 } 1248 function renderCrm(d){ 1249 const e=d.employee||{}, m=d.metrics||{}; 1250 const rating=Math.round(numv(e.rating)); 1251 const stars=[1,2,3,4,5].map(i=>'<i class="ti ti-star-filled'+(i<=rating?' on':'')+'"></i>').join(''); 1252 const drate=Math.round(numv(e.disciplineRating)); 1253 const dstars=[1,2,3,4,5].map(i=>'<i class="ti ti-star-filled'+(i<=drate?' on':'')+'"></i>').join(''); 1254 const cards=[ 1255 ['ti-calendar','Days in Company', svcVal(e.doj, m.daysInCompany)], 1256 ['ti-building','Department', esc(e.department||'â')], 1257 ['ti-briefcase','Designation', esc(e.designation||'â')], 1258 ['ti-star','Performance Rating','<span class="stars">'+stars+'</span>'], 1259 ['ti-shield-check','Discipline Rating','<span class="stars">'+dstars+'</span>'] 1260 ]; 1261 document.getElementById('crm').innerHTML=cards.map(c=>'<div class="ccard"><div class="cl"><i class="ti '+c[0]+'"></i> '+c[1]+'</div><div class="cv">'+c[2]+'</div></div>').join(''); 1262 } 1263 function probationEnd(e){ const d=new Date(String(e.doj||'').replace(' ','T')); if(isNaN(d))return null; const m=parseInt(e.probationMonths||'6',10)||6; d.setMonth(d.getMonth()+m); return d; } 1264 function isConfirmed(e){ if(String(e.status).toLowerCase()==='confirmed')return true; const pe=probationEnd(e); return pe? Date.now()>=pe.getTime() : false; } 1265 function docStats(d){ 1266 const uploaded=d.documents||[]; const cfg=d.config||{}; 1267 const mand=String(cfg.mandatoryDocs||'').split(',').map(s=>
1267s.trim()).filter(Boolean); 1268 const byType={}; uploaded.forEach(u=>{ if(u.link) byType[String(u.docType).trim().toLowerCase()]=u; }); 1269 const done=mand.filter(n=>byType[n.toLowerCase()]).length; 1270 return {mand:mand, done:done, total:mand.length, all: mand.length>0 && done>=mand.length, byType:byType}; 1271 } 1272 function renderProgress(d){ 1273 const e=d.employee||{}; const ds=docStats(d); 1274 const steps=[ 1275 {t:'Offer Accepted & Joined', d:true}, 1276 {t:'Documents Submitted ('+ds.done+'/'+ds.total+')', d:ds.all}, 1277 {t:'Probation Cleared / Confirmed', d:isConfirmed(e)}, 1278 {t:'Profile Complete', d: !!(e.photo && e.pan && e.bankAccount)} 1279 ]; 1280 const done=steps.filter(s=>s.d).length, pct=Math.round(done/steps.length*100); 1281 document.getElementById('progFill').style.width=pct+'%'; 1282 const all=done===steps.length; 1283 var _ps=document.getElementById('progSub'); if(_ps) _ps.textContent = all ? 'All steps complete' : (done+' of '+steps.length+' steps complete'); 1284 document.getElementById('progMsg').innerHTML= all 1285 ? '<span style="color:var(--ok);font-weight:600"><i class="ti ti-circle-check"></i> All set â your profile is complete! ð</span>' 1286 : '<span style="color:var(--warn);font-weight:600"><i class="ti ti-alert-circle"></i> Setup incomplete â complete the highlighted steps.</span> '+done+'/'+steps.length; 1287 document.getElementById('progSteps').innerHTML=steps.map(s=>'<div class="pstep'+(s.d?' done':' todo')+'"><span class="pi"><i class="ti '+(s.d?'ti-check':'ti-alert-triangle')+'"></i></span>'+s.t+'</div>').join(''); 1288 } 1289 /* ---- Documents ---- */ 1290 function renderDocs(d){ 1291 const ds=docStats(d); const list=ds.mand.map(name=>{ const u=ds.byType[name.toLowerCase()]; return {name:name, link:u?u.link:''}; }); 1292 (d.documents||[]).forEach(u=>{ if(u.link && ds.mand.findIndex(m=>m.toLowerCase()===String(u.docType).trim().toLowerCase())<0) list.push({name:u.docType, link:u.link}); }); 1293 window.__docList=list; 1294 if(!list.length){ document.getElementById('docsList').innerHTML='<div class="empty">No document list configured.</div>'; return; } 1295 const warn = ds.all ? '<div class="docok-pro"><i class="ti ti-rosette-discount-check"></i><div><b>All documents submitted</b><span>Your document file is complete and up to date â thank you.</span></div></div>' 1296 : '<div class="docwarn"><i class="ti ti-alert-triangle"></i><div><b>'+(ds.total-ds.done)+' document(s) still pending.</b> Please upload all documents â incomplete files may delay your salary credit.</div></div>'; 1297 const body=list.map((x,i)=>{ const done=!!x.link; 1298 return '<div class="line"><span>'+(done?'<i class="ti ti-circle-check" style="color:var(--ok)"></i> ':'<i class="ti ti-circle" style="color:var(--faint)"></i> ')+esc(x.name)+'</span>'+(done?'<a class="dl ghost" href="'+esc(x.link)+'" target="_blank" style="padding:5px 11px"><i class="ti ti-eye"></i> View</a>':'<button class="dl" style="padding:6px 12px" onclick="uploadMyDoc('+i+')"><i class="ti ti-upload"></i> Upload</button>')+'</div>'; 1299 }).join(''); 1300 document.getElementById('docsList').innerHTML = warn + accItem('ti-files','My Documents', ds.done+' of '+ds.total+' uploaded', body); 1301 } 1302 function uploadMyDoc(i){ 1303 const item=(window.__docList||[])[i]; if(!item)return; const docType=item.name; 1304 const inp=document.createElement('input'); inp.type='file'; inp.accept='image/*,application/pdf'; 1305 inp.onchange=async()=>{ const f=inp.files[0]; if(!f)return; toast('Uploading '+docType+'â¦'); 1306 let data=await new Promise(r=>{const rd=new FileReader();rd.onload=e=>r(e.target.result);rd.readAsDataURL(f);}); 1307 if(f.type.indexOf('image')===0){ data=await new Promise(res=>{const img=new Image();img.onload=()=>{let w=img.width,h=img.height;const mx=1500;if(w>mx||h>mx){const s=Math.min(mx/w,mx/h);w=Math.round(w*s);h=Math.round(h*s);}const c=document.createElement('canvas');c.width=w;c.height=h;c.getContext('2d').drawImage(img,0,0,w,h);res(c.toDataURL('image/jpeg',0.78));};img.onerror=()=>res(data);img.src=data;}); } 1308 const dd=await api({action:'emsUploadDoc', employeeId:currentId, docType:docType, file:data}); 1309 if(dd.status==='success'){ toast(docType+' uploaded â'); refresh(true); } else toast(dd.message||'Upload failed'); 1310 }; 1311 inp.click(); 1312 } 1313 /* ---- Department ---- */ 1314 function ministars(v){ const r=Math.round(numv(v)); return v?('<span class="stars">'+[1,2,3,4,5].map(i=>'<i class="ti ti-star-filled'+(i<=r?' on':'')+'"></i>').join('')+'</span>'):'â'; } 1315 function renderDept(d){ 1316 const e=d.employee||{}, cfg=d.config||{}; 1317 const sr=salaryRows(e); const hasSal=sr.gross>0; 1318 const monthly = hasSal ? rupee(sr.gross)+' / month' : 'â'; 1319 const annual = e.ctc ? rupee(e.ctc) : (hasSal ? rupee(sr.gross*12) : 'As per salary structure'); 1320 const pm = parseInt(e.probationMonths||'',10); const prob = pm ? (pm+' Month'+(pm>1?'s':'')) : 'â'; 1321 const row=function(icon,label,val,hi){ return '<div class="dq"><span class="dq-k"><i class="ti '+icon+'"></i>'+label+'</span><span class="dq-v'+(hi?' hi':'')+'">'+(val==null||val===''?'â':val)+'</span></div>'; }; 1322 const longrow=function(icon,label,val){ return '<div class="dq-long"><div class="dq-k"><i class="ti '+icon+'"></i>'+label+'</div><div class="dq-longv">'+(val==null||val===''?'â':val)+'</div></div>'; }; 1323 const block=function(icon,title,rowsHtml){ return '<div class="dept-block"><div class="dept-bh"><i class="ti '+icon+'"></i>'+title+'</div><div class="dept-brows">'+rowsHtml+'</div></div>'; }; 1324 let h=''; 1325 h+=block('ti-briefcase','Role & Joining Details', 1326 row('ti-user-star','Designation / Role', esc(e.designation), true)+ 1327 row('ti-building','Department', esc(e.department))+ 1328 row('ti-user-check','Reporting To', esc(d.reportingManager||e.reportingManager))+ 1329 row('ti-map-pin','Work Location', esc(e.workLocation))+ 1330 row('ti-calendar-week','Working Days', esc(e.workingDays||cfg.workingDays||'Monday to Saturday'))+ 1331 row('ti-clock-hour-9','Office Timing', esc(e.officeTiming||cfg.officeTiming||'9:30 AM to 6:30 PM'))+ 1332 row('ti-id-badge-2','Employment Type', esc(e.employmentType||'Full-time'))+ 1333 row('ti-hourglass-high','Probation Period', esc(prob))+ 1334 row('ti-calendar-event','Joining Date', esc(fmtDate(e.doj))) 1335 ); 1336 h+=block('ti-cash-banknote','Compensation', 1337 row('ti-coin-rupee','Monthly Salary', monthly, hasSal)+ 1338 row('ti-wallet','Annual CTC', annual, !!e.ctc||hasSal) 1339 ); 1340 if(e.assignedWork){ h+=block('ti-clipboard-list','Assigned Work', longrow('ti-checklist','Re
1340sponsibilities', esc(e.assignedWork))); } 1341 const anyRating=numv(e.rating)+numv(e.disciplineRating)+numv(e.professionalism)+numv(e.behaviour)+numv(e.punctuality)+numv(e.teamwork)>0; 1342 if(anyRating||e.conductNote){ 1343 let rr=''; 1344 if(anyRating){ rr+=row('ti-chart-line','Performance', ministars(e.rating))+ 1345 row('ti-shield-check','Discipline', ministars(e.disciplineRating))+ 1346 row('ti-briefcase','Professionalism', ministars(e.professionalism))+ 1347 row('ti-mood-smile','Behaviour', ministars(e.behaviour))+ 1348 row('ti-clock-check','Punctuality', ministars(e.punctuality))+ 1349 row('ti-users-group','Teamwork', ministars(e.teamwork)); } 1350 if(e.conductNote) rr+=longrow('ti-note','HR Note', esc(e.conductNote)); 1351 h+=block('ti-award','Performance & Conduct', rr); 1352 } 1353 document.getElementById('deptKv').innerHTML=h; 1354 } 1355 async function submitWork(){ 1356 const t=document.getElementById('workTask').value.trim(); if(!t){toast('Enter your work update');return} 1357 await api({action:'emsAddWork', employeeId:currentId, name:(BUNDLE.employee||{}).name||'', task:t}); 1358 document.getElementById('workTask').value=''; toast('Work update submitted â'); 1359 } 1360 /* ---- Salary slips (last 6 months) ---- */ 1361 function lastMonths(n){ const out=[]; const d=new Date(); for(let i=0;i<n;i++){ const m=new Date(d.getFullYear(), d.getMonth()-i, 1); out.push({key:m.toLocaleString('en-IN',{month:'long',year:'numeric'}), date:m}); } return out; } 1362 var _salYear=0; 1363 function fmtDay(d){ return ('0'+d.getDate()).slice(-2)+' '+d.toLocaleString('en-IN',{month:'short'})+' '+d.getFullYear(); }
1364 function renderSalAnnex(e){ 1365 const box=document.getElementById('salAnnex'); if(!box) return; 1366 const s=salaryRows(e); if(s.gross<=0){ box.innerHTML=''; return; } 1367 const rrow=function(n,v){ return '<div class="sa-row"><span>'+esc(n)+'</span><b>'+rupee(v)+'</b></div>'; }; 1368 const earn=s.earn.filter(function(r){return r[1]>0;}); const ded=s.ded.filter(function(r){return r[1]>0;}); 1369 let words=''; try{ words=inWords(s.net); }catch(_){ words=''; } 1370 let h='<div class="acc"><div class="acc-h" onclick="toggleAcc(this)"><div class="ai"><i class="ti ti-file-invoice"></i></div><div class="at"><b>Salary Annexure</b><span>Net '+rupee(s.net)+'/mo \u00b7 tap for full breakdown</span></div><i class="ti ti-chevron-down chev"></i></div><div class="acc-b"><div class="acc-bi"><div class="sa-grid">'; 1371 h+='<div class="sa-col"><div class="sa-ctitle earn">Earnings</div>'+(earn.length?earn.map(function(r){return rrow(r[0],r[1]);}).join(''):'<div class="sa-row"><span>â</span><b>'+rupee(0)+'</b></div>')+'<div class="sa-total earn"><span>Total Earnings (Gross)</span><b>'+rupee(s.gross)+'</b></div></div>'; 1372 h+='<div class="sa-col"><div class="sa-ctitle ded">Deductions</div>'+(ded.length?ded.map(function(r){return rrow(r[0],r[1]);}).join(''):'<div class="sa-row"><span>No deductions</span><b>'+rupee(0)+'</b></div>')+'<div class="sa-total ded"><span>Total Deductions</span><b>'+rupee(s.totded)+'</b></div></div>'; 1373 h+='</div><div class="sa-net"><span>Net Take-Home (Monthly)</span><b>'+rupee(s.net)+'</b></div>'; 1374 h+='<div class="sa-sub">Annual CTC: <b>'+rupee(e.ctc?numv(e.ctc):s.gross*12)+'</b>'+(words?(' · In words: '+esc(words)+' only.'):'')+'</div></div></div></div>'; 1375 box.innerHTML=h; 1376 }
1377 async function renderSalary(d){ 1378 const e=d.employee||{}; const has=salaryRows(e).gross>0; 1379 renderSalAnnex(e); 1380 try{ const pd=await api({action:'emsMyPayslips', employeeId:currentId||ssGet('ems_id')}); if(pd&&pd.status==='success'){ window.__myPayslips=pd.rows||[]; window.__myRevisions=pd.revisions||[]; } }catch(_){} 1381 renderRevisions(window.__myRevisions||[]); loadReimbursements(); 1382 if(!has){ document.getElementById('slipList').innerHTML='<div class="empty">Salary details not set yet. HR will update them.</div>'; renderMyIssues(d.salaryIssues||[]); return; } 1383 if(!_salYear) _salYear=new Date().getFullYear(); 1384 salRenderMonths(); renderMyIssues(d.salaryIssues||[]); 1385 } 1386 function salYear(dir){ _salYear+=dir; salRenderMonths(); } 1387 function fileToDataURL(f){ return new Promise(function(res){ const r=new FileReader(); r.onload=function(e){res(e.target.result);}; r.onerror=function(){res('');}; r.readAsDataURL(f); }); } 1388 function renderRevisions(rows){ 1389 const el=document.getElementById('salRevisions'); if(!el) return; 1390 if(!rows||!rows.length){ el.innerHTML=''; return; } 1391 el.innerHTML='<div class="sectitle" style="margin-top:24px"><i class="ti ti-history"></i> Salary Revision History</div>'+rows.map(function(r){ 1392 const g=Number(r.basic||0)+Number(r.hra||0)+Number(r.allowance||0); 1393 return '<div class="cmp-item"><div class="ctop"><b>Effective '+esc(fmtDate(r.effective_from)||r.effective_from||'')+'</b><span class="cs">'+rupee(g)+' / mo</span></div>'+(r.reason?'<div style="margin-top:4px;color:var(--muted)">'+esc(r.reason)+'</div>':'')+'</div>'; 1394 }).join(''); 1395 } 1396 async function loadReimbursements(){ 1397 const id=currentId||ssGet('ems_id'); if(!id) return; 1398 const dt=document.getElementById('reimDate'); if(dt&&!dt.value) dt.value=_todayISO(); 1399 try{ const d=await api({action:'emsExpenseMine', employeeId:id}); if(d&&d.status==='success') renderReims(d.rows||[]); }catch(_){} 1400 } 1401 async function submitReim(){ 1402 const id=currentId||ssGet('ems_id'); 1403 const cat=document.getElementById('reimCat').value, amt=document.getElementById('reimAmt').value, date=document.getElementById('reimDate').value, desc=document.getElementById('reimDesc').value.trim(); 1404 if(!(Number(amt)>0)){ toast('Enter a valid amount'); return; } 1405 const params={action:'emsExpenseSubmit', employeeId:id, token:ssGet('ems_tok'), category:cat, amount:amt, claimDate:date, description:desc}; 1406 const f=document.getElementById('reimFile'); if(f&&f.files&&f.files[0]){ toast('Uploadingâ¦'); params.file=await fileToDataURL(f.files[0]); } 1407 try{ 1408 const d=await api(params); 1409 if(d&&d.status==='success'){ toast('Claim submitted â'); document.getElementById('reimAmt').value=''; document.getElementById('reimDesc').value=''; if(f) f.value=''; loadReimbursements(); } 1410 else toast((d&&d.message)||'Submit failed'); 1411 }catch(e){ toast('Network issue â please try again.'); } 1412 } 1413 async function cancelReim(cid){ 1414 if(!confirm('Cancel this claim?')) return; 1415 const id=currentId||ssGet('ems_id'); 1416 const d=await api({action:'emsExpenseCancel', employeeId:id, id:cid}); 1417 if(d&&d.status==='success'){ toast('Cancelled'); loadReimbursements(); } else toast((d&&d.message)||'Failed'); 1418 } 1419 function renderReims(rows){ 1420 const el=document.getElementById('myReims'); if(!el) return; 1421 if(!rows.length){ el.innerHTML='<div class="accwrap-head">My claims</div><div class="empty">No claims yet.</div>'; return; } 1422 el.innerHTML='<div class="accwrap-head">My claims</div>'+rows.map(function(r){ const s=String(r.status||'pending'); const badge=(s==='paid'||s==='approved')?'<span class="cs resolved">'+(s==='paid'?'Paid':'Approved')+'</span>':(s==='rejected'?'<span class="cs" style="background:rgba(224,83,63,.15);color:#ff9c8b">Rejected</span>':(s==='cancelled'?'<span class="cs">Cancelled</span>':'<span class="cs open">Pending</span>')); const cancel=(s==='pending')?'<button class="dl ghost" style="margin-top:8px;padding:6px 12px" onclick="cancelReim(\''+esc(r.id)+'\')"><i class="ti ti-x"></i> Cancel</button>':'';
1422 const bill=r.bill_link?' \u00b7 <a href="'+esc(r.bill_link)+'" target="_blank" style="color:var(--gold-soft)">bill</a>':''; const dn=r.decision_note?'<div class="creply"><b>Accounts:</b> '+esc(r.decision_note)+'</div>':''; const cat=(r.category||'expense'); return '<div class="cmp-item"><div class="ctop"><b>'+esc(cat.charAt(0).toUpperCase()+cat.slice(1))+' \u00b7 '+rupee(r.amount)+'</b>'+badge+'</div><div style="font-size:12.5px;color:var(--muted)">'+esc(r.claim_date||'')+bill+'</div>'+(r.description?'<div style="margin-top:4px">'+esc(r.description)+'</div>':'')+dn+cancel+'</div>'; }).join(''); 1423 } 1424 function salFinalizedSlips(){ return (window.__myPayslips||[]).filter(function(ps){ return String(ps.status)==='finalized'||String(ps.status)==='paid'; }); } 1425 function salQuickBar(){ 1426 const mn=['January','February','March','April','May','June','July','August','September','October','November','December']; 1427 const slips=salFinalizedSlips().slice().sort(function(a,b){ return String(b.month).localeCompare(String(a.month)); }); 1428 if(!slips.length){ return '<div class="sal-quick"><i class="ti ti-file-download"></i><span class="sal-qlbl">Quick payslip</span><span class="sal-qnote">Available once HR finalizes a monthâs payroll.</span></div>'; } 1429 let opts=slips.map(function(ps){ const y=String(ps.month).slice(0,4), mi=parseInt(String(ps.month).slice(5,7),10)-1; return '<option value="'+esc(ps.month)+'">'+mn[mi]+' '+y+'</option>'; }).join(''); 1430 return '<div class="sal-quick"><i class="ti ti-file-download"></i><span class="sal-qlbl">Quick payslip</span><select class="inp sm" id="salQM">'+opts+'</select><button class="sal-dl" onclick="salQuickDL()"><i class="ti ti-download"></i> Download</button></div>'; 1431 } 1432 function salQuickDL(){ 1433 const el=document.getElementById('salQM'); const v=el?el.value:''; if(!v){ toast('No finalized payslip yet'); return; } 1434 genSlip(parseInt(v.slice(0,4),10), parseInt(v.slice(5,7),10)-1); 1435 } 1436 function salRenderMonths(){ 1437 const now=new Date(), cy=now.getFullYear(); 1438 const doj=new Date(String((BUNDLE.employee||{}).doj||'').replace(' ','T')); 1439 const dojY=isNaN(doj)?2000:doj.getFullYear(); 1440 const canPrev=_salYear>dojY, canNext=_salYear<cy; 1441 let h=salQuickBar()+'<div class="sal-yr"><button class="sal-nav"'+(canPrev?'':' disabled')+' onclick="salYear(-1)"><i class="ti ti-chevron-left"></i></button><span class="sal-yrlbl">'+_salYear+'</span><button class="sal-nav"'+(canNext?'':' disabled')+' onclick="salYear(1)"><i class="ti ti-chevron-right"></i></button></div>'; 1442 // Premium history: only months whose payroll HR has finalized appear here. 1443 const yearSlips=salFinalizedSlips().filter(function(ps){ return String(ps.month||'').slice(0,4)===String(_salYear); }).sort(function(a,b){ return String(b.month).localeCompare(String(a.month)); }); 1444 if(!yearSlips.length){ document.getElementById('slipList').innerHTML=h+'<div class="sal-empty"><i class="ti ti-file-invoice"></i><div class="sal-empty-t">No payslips for '+_salYear+' yet</div><div class="sal-empty-s">Your payslip for a month appears here once HR finalizes that monthâs payroll.</div></div>'; return; } 1445 h+='<div class="sal-months">'+yearSlips.map(function(ps){ 1446 const mIdx=parseInt(String(ps.month).slice(5,7),10)-1; 1447 const first=new Date(_salYear,mIdx,1), last=new Date(_salYear,mIdx+1,0); 1448 const mName=first.toLocaleString('en-IN',{month:'short'}); 1449 const range=fmtDay(first)+' â '+fmtDay(last); 1450 const paid=String(ps.status)==='paid'; 1451 const badge='<span class="sal-net-badge '+(paid?'paid':'fin')+'">'+(paid?'Paid':'Ready')+' · '+rupee(ps.net)+'</span>'; 1452 return '<div class="sal-card"><div class="sal-mbadge"><b>01</b><span>'+mName+'</span></div><div class="sal-mmain"><b>Payslip</b><span>'+range+'</span>'+badge+'</div><div class="sal-mbtns"><button class="sal-dl" onclick="genSlip('+_salYear+','+mIdx+')"><i class="ti ti-download"></i> Download</button><button class="sal-rep" title="Report issue" onclick="openIssue(\''+mName+' '+_salYear+'\')"><i class="ti ti-alert-triangle"></i></button></div></div>'; 1453 }).join('')+'</div>'; 1454 document.getElementById('slipList').innerHTML=h; 1455 } 1456 function openIssue(m){ document.getElementById('issueBox').style.display='block'; document.getElementById('issueMonth').textContent=m; document.getElementById('issueText').focus(); document.getElementById('issueBox').dataset.m=m; } 1457 async function submitIssue(){
1458 const m=document.getElementById('issueBox').dataset.m||''; const t=document.getElementById('issueText').value.trim(); 1459 if(!t){toast('Describe the issue');return} 1460 try{ 1461 const d=await api({action:'emsSalaryIssue', employeeId:currentId, name:(BUNDLE.employee||{}).name||'', month:m, message:t}); 1462 if(d&&d.status==='success'){ document.getElementById('issueText').value=''; document.getElementById('issueBox').style.display='none'; toast('Issue submitted â'); refresh(true); } 1463 else toast((d&&d.message)||'Could not submit â please try again'); 1464 }catch(e){ toast('Network issue â please try again.'); } 1465 } 1466 function renderMyIssues(list){ 1467 if(!list||!list.length){document.getElementById('myIssues').innerHTML='';return} 1468 document.getElementById('myIssues').innerHTML='<div class="accwrap-head">Your reported issues</div>'+list.slice().reverse().map(c=>{ 1469 const r=String(c.status).toLowerCase()==='resolved'||String(c.status).toLowerCase()==='closed'; 1470 const badge=r?'<span class="cs resolved">Resolved</span>':'<span class="cs open">Open</span>'; 1471 const body='<div style="margin-top:4px">'+esc(c.message)+'</div>'+(c.reply?'<div class="creply"><b>HR:</b> '+esc(c.reply)+'</div>':''); 1472 return accItem('ti-cash', esc(c.month||'Salary issue')+' '+badge, esc(fmtDT(c.time)), body); 1473 }).join(''); 1474 } 1475 /* ---- Notices ---- */ 1476 function _noticeSeen(){ try{ return JSON.parse(localStorage.getItem('ems_notice_seen')||'[]'); }catch(_){ return []; } } 1477 function _noticeMarkSeen(){ try{ var s=_noticeSeen(); (window._noticeIds||[]).forEach(function(id){ if(s.indexOf(id)<0)s.push(id); }); if(s.length>300)s=s.slice(-300); localStorage.setItem('ems_notice_seen',JSON.stringify(s)); }catch(_){} } 1478 function _noticeId(n){ return String((n&&n.id)||(((n&&n.subject)||'')+'|'+((n&&n.time)||''))); } 1479 function renderNotices(d){ 1480 const list=d.notices||[]; 1481 if(!list.length){document.getElementById('noticeList').innerHTML='<div class="empty">No notices yet.</div>';return} 1482 var _seen=_noticeSeen(); var _rev=list.slice().reverse(); window._noticeIds=_rev.map(_noticeId); 1483 document.getElementById('noticeList').innerHTML=_rev.map(n=>{ 1484 const urgent=String(n.type).toLowerCase()==='urgent'; 1485 const isNew=_seen.indexOf(_noticeId(n))<0; 1486 const btn=n.pdfLink?'<a class="dl" href="'+esc(n.pdfLink)+'" target="_blank"><i class="ti ti-download"></i> Download PDF</a>':'<button class="dl ghost" onclick=\'genNotice('+JSON.stringify(JSON.stringify({s:n.subject,m:n.message,t:n.time}))+')\'><i class="ti ti-download"></i> Download PDF</button>'; 1487 const cfg=BUNDLE.config||{}; const stK='stamp'+(n.stampType||''); const stamp=(n.stampType&&cfg[stK])?'<div style="margin-top:12px"><img src="'+esc(cfg[stK])+'" style="max-height:72px"><div style="font-size:11px;color:var(--faint)">'+esc(n.stampType)+' â Authorised</div></div>':''; 1488 const body='<div class="ptext">'+esc(n.message||'')+'</div>'+stamp+'<div style="margin-top:10px">'+btn+'</div>'; 1489 return accItem('ti-speakerphone', esc(n.subject||'Notice')+(isNew?'<span class="ntag-new">NEW</span>':'')+(urgent?'<span class="btag">URGENT</span>':''), esc(fmtDT(n.time)), body, (urgent||isNew)); 1490 }).join(''); 1491 } 1492 /* ---- Complaints ---- */ 1493 function renderComplaints(list){ 1494 const el=document.getElementById('myComplaints'); 1495 if(!list.length){el.innerHTML='';return} 1496 const shown=cmpAll?list:list.slice(0,3); 1497 el.innerHTML='<div class="accwrap-head">Your Requests</div>'+shown.map(c=>{ 1498 const r=String(c.status).toLowerCase()==='resolved'||String(c.status).toLowerCase()==='closed'; 1499 const badge=r?'<span class="cs resolved">Resolved</span>':'<span class="cs open">Open</span>'; 1500 const body='<div style="margin-top:4px">'+esc(c.message)+'</div>'+(c.reply?'<div class="creply"><b>HR:</b> '+esc(c.reply)+'</div>':''); 1501 return accItem('ti-headset', 'Request '+badge, esc(fmtDT(c.time)), body); 1502 }).join('')+(list.length>3?'<button class="reqmore" onclick="cmpAll=!cmpAll;renderComplaints(window.__c||[])">'+(cmpAll?'Show less':'Show all ('+list.length+')')+'</button>':''); 1503 window.__c=list; 1504 } 1505 async function submitComplaint(){ 1506 const t=document.getElementById('hlpText').value.trim(); if(!t){toast('Describe your request');return} 1507 try{ 1508 const d=await api({action:'emsAddComplaint', employeeId:currentId, name:(BUNDLE.employee||{}).name||'', message:t}); 1509 if(d&&d.status==='success'){ document.getElementById('hlpText').value=''; toast('Request submitted â'); refresh(true); } 1510 else toast((d&&d.message)||'Could not submit â please try again'); 1511 }catch(e){ toast('Network issue â please try again.'); } 1512 } 1513 var _rsEl=document.getElementById('rateStars'); if(_rsEl) _rsEl.addEventListener('click',e=>{ const v=e.target.dataset.v; if(!v)return; rateVal=+v;
1513 [...document.querySelectorAll('#rateStars i')].forEach(i=>i.classList.toggle('on', +i.dataset.v<=rateVal)); }); 1514 async function submitFeedback(){ 1515 if(!rateVal){toast('Tap stars to rate');return} 1516 try{ 1517 const d=await api({action:'emsAddFeedback', employeeId:currentId, name:(BUNDLE.employee||{}).name||'', rating:rateVal, message:document.getElementById('fbText').value.trim()}); 1518 if(!(d&&d.status==='success')){ toast((d&&d.message)||'Could not submit â please try again'); return; } 1519 }catch(e){ toast('Network issue â please try again.'); return; } 1520 document.getElementById('fbText').value=''; toast('Thanks for your feedback â'); 1521 } 1522 /* ---- Bell ---- */ 1523 function renderBell(d){ 1524 d=d||BUNDLE||{}; 1525 const notices=(d.notices||[]).map(n=>({id:'n'+n.id, subject:n.subject||'Notice', message:n.message||'', time:n.time, type:(String(n.type).toLowerCase()==='urgent'?'urgent':'')})); 1526 const compReplies=(d.complaints||[]).filter(c=>c.reply).map(c=>({id:'c'+(c.id||c.time)+String(c.reply).length, subject:'HR replied to your request', message:c.reply, time:c.replyTime||c.time, type:''})); 1527 const salReplies=(d.salaryIssues||[]).filter(c=>c.reply).map(c=>({id:'si'+(c.id||c.time)+String(c.reply).length, subject:'HR replied · '+(c.month||'Salary issue'), message:c.reply, time:c.replyTime||c.time, type:''})); 1528 const resigns=(d.resignations||[]).filter(r=>String(r.status||'').toLowerCase()!=='pending' || r.settlement).map(r=>({id:'r'+(r.id||r.time)+String(r.status||'')+String(r.settlement||'').length, subject:'Resignation update: '+(r.status||'Updated'), message:(r.settlement?r.settlement:('Your resignation status is now â'+(r.status||'updated')+'â.')), time:r.updatedAt||r.time, type:''})); 1529 const unified=(window.__unifiedNotifs||[]).map(n=>({id:'u'+n.id, subject:n.title||'Notification', message:n.message||'', time:n.time, type:(String(n.type).toLowerCase()==='urgent'?'urgent':'')})); 1530 let list=notices.concat(compReplies, salReplies, resigns, unified); 1531 list.sort((a,b)=> new Date(String(b.time||'').replace(' ','T')) - new Date(String(a.time||'').replace(' ','T')) ); 1532 const unreadKey='ems_read_'+currentId; const read=lsGet(unreadKey).split(','); 1533 const unread=list.filter(n=>read.indexOf(String(n.id))<0).length; 1534 const dot=document.getElementById('bdot'); if(unread>0){dot.style.display='grid';dot.textContent=unread}else dot.style.display='none'; 1535 window.__bellIds=list.map(n=>String(n.id)); 1536 document.getElementById('bpanel').innerHTML = list.length? list.map(n=>'<div class="bitem'+(n.type==='urgent'?' urgent':'')+'"><b>'+esc(n.subject)+(n.type==='urgent'?'<span class="btag">URGENT</span>':'')+'</b>'+esc(n.message)+'<div class="bt">'+esc(fmtDT(n.time))+'</div></div>').join('') : '<div class="empty">No notifications</div>'; 1537 } 1538 function toggleBell(){ 1539 const p=document.getElementById('bpanel'); p.style.display=p.style.display==='block'?'none':'block'; 1540 if(p.style.display==='block'){ const ids=(window.__bellIds||[]); lsSet('ems_read_'+currentId, ids.join(',')); document.getElementById('bdot').style.display='none'; } 1541 } 1542 document.addEventListener('click',e=>{ if(!e.target.closest('#bpanel')&&!e.target.closest('#bellBtn')) document.getElementById('bpanel').style.display='none'; }); 1543 1544 /* ---- Phase 0 foundation: role-aware UI + unified notifications + my approvals ---- */ 1545 async function emsFoundationLoad(){ 1546 const id=currentId||ssGet('ems_id'); if(!id) return; 1547 try{ const pm=await api({action:'emsMyPerms', employeeId:id}); 1548 if(pm&&pm.status==='success'){ window.__role=pm.role||'Employee'; window.__mods=pm.modules||[]; applyRoleVisibility(); 1549 const rt=document.getElementById('eRoleTxt'); if(rt){ rt.textContent=window.__role; const rb=document.getElementById('eRoleBadge'); if(rb) rb.style.display='inline-flex'; } 1550 } 1551 }catch(e){} 1552 try{ const nl=await api({action:'emsNotifyList', employeeId:id}); 1553 if(nl&&nl.status==='success'){ window.__unifiedNotifs=nl.rows||[]; if(BUNDLE) renderBell(BUNDLE); try{ if(typeof dwUpdates==='function') dwUpdates(window.__unifiedNotifs); }catch(_u){} } 1554 }catch(e){} 1555 try{ const ma=await api({action:'emsApprMine', employeeId:id}); 1556 if(ma&&ma.status==='success'){ renderMyApprovals(ma.rows||[]); } 1557 }catch(e){} 1558 } 1559 function applyRoleVisibility(){ 1560 const mods=window.__mods; if(!mods||!mods.length) return;
1561 let firstVisible=null, activeHidden=false; 1562 document.querySelectorAll('.tab[data-mod]').forEach(function(t){ 1563 const ok=mods.indexOf(t.getAttribute('data-mod'))>=0; 1564 t.style.display=ok?'':'none'; 1565 if(ok&&!firstVisible) firstVisible=t; 1566 if(!ok&&t.classList.contains('active')) activeHidden=true; 1567 }); 1568 document.querySelectorAll('#eSide .snav-item[data-mod]').forEach(function(n){ n.style.display=mods.indexOf(n.getAttribute('data-mod'))>=0?'':'none'; }); 1569 document.querySelectorAll('#eSide .snav-grp').forEach(function(g){ var any=false; g.querySelectorAll('.snav-item').forEach(function(n){ if(n.style.display!=='none') any=true; }); g.style.display=any?'':'none'; }); 1570 if(activeHidden&&firstVisible) firstVisible.click(); 1571 eSyncActive(); 1572 } 1573 function renderMyApprovals(rows){ 1574 const el=document.getElementById('myApprovals'); if(!el) return; 1575 if(!rows||!rows.length){ el.innerHTML=''; return; } 1576 el.innerHTML='<div class="sectitle" style="margin-top:24px"><i class="ti ti-checklist"></i> My Requests</div>'+rows.map(function(r){ 1577 const s=String(r.status||'pending').toLowerCase(); 1578 const badge=s==='approved'?'<span class="cs resolved">Approved</span>':(s==='rejected'?'<span class="cs" style="background:rgba(224,83,63,.15);color:#ff9c8b">Rejected</span>':'<span class="cs open">Pending</span>'); 1579 const et=String(r.entity_type||'request'); const etT=et.charAt(0).toUpperCase()+et.slice(1); 1580 const dn=r.decision_note?'<div class="creply"><b>HR:</b> '+esc(r.decision_note)+'</div>':''; 1581 return '<div class="cmp-item"><div class="ctop"><b>'+esc(etT)+'</b>'+badge+'</div>'+(r.title?'<div style="margin-top:4px">'+esc(r.title)+'</div>':'')+'<div style="font-size:11px;color:var(--faint);margin-top:4px">'+esc(fmtDT(r.time))+'</div>'+dn+'</div>'; 1582 }).join(''); 1583 } 1584 1585 /* ---- Phase 3: Goals / Tasks / Performance / Rewards (employee) ---- */ 1586 async function loadGoals(){ 1587 const id=currentId||ssGet('ems_id'); if(!id) return; 1588 const d=await api({action:'emsMyGoals', employeeId:id}); 1589 const el=document.getElementById('goalsWrap'); if(!el) return; 1590 const rows=(d&&d.rows)||[]; 1591 if(!rows.length){ el.innerHTML='<div class="empty">No goals assigned yet.</div>'; return; } 1592 const groups={company:[],department:[],personal:[]}; 1593 rows.forEach(function(g){ (groups[g.scope]||groups.company).push(g); }); 1594 let h=''; 1595 [['personal','My Goals'],['department','Department Goals'],['company','Company Goals']].forEach(function(gp){ 1596 const list=groups[gp[0]]; if(!list||!list.length) return; 1597 h+='<div class="accwrap-head" style="margin-top:14px">'+gp[1]+'</div>'+list.map(function(g){ 1598 const pct=Math.max(0,Math.min(100,Number(g.progress)||0)); const editable=(gp[0]==='personal'); 1599 const edit=editable?'<div class="goal-edit"><input type="range" min="0" max="100" value="'+pct+'" id="gs_'+esc(g.id)+'" oninput="document.getElementById(\'gv_'+esc(g.id)+'\').textContent=this.value+\'%\'"><span id="gv_'+esc(g.id)+'">'+pct+'%</span><button class="dl ghost" style="padding:6px 12px" onclick="updateGoalProgress(\''+esc(g.id)+'\')">Save</button></div>':('<div style="font-size:12px;color:var(--muted);margin-top:6px">'+pct+'%'+(g.due_date?(' · due '+esc(g.due_date)):'')+'</div>'); 1600 return '<div class="goal-card"><div class="ctop"><b>'+esc(g.title||'')+'</b><span class="cs '+(String(g.status)==='done'?'resolved':'open')+'">'+esc(String(g.status||'open').replace('_',' '))+'</span></div>'+(g.description?'<div style="color:var(--muted);font-size:13px;margin:4px 0">'+esc(g.description)+'</div>':'')+(g.target?'<div style="font-size:12px;color:var(--faint)">Target: '+esc(g.target)+'</div>':'')+'<div class="goal-bar"><span style="width:'+pct+'%"></span></div>'+edit+'</div>'; 1601 }).join(''); 1602 }); 1603 el.innerHTML=h; 1604 } 1605 /* ---- Org Chart ---- */ 1606 async function loadOrgChart(){ 1607 const id=currentId||ssGet('ems_id'); if(!id) return; 1608 if(!_dir.length){ const d=await api({action:'emsDirectory', employeeId:id}); _dir=(d&&d.rows)||[]; } 1609 renderOrgChart(); 1610 } 1611 function renderOrgChart(){ 1612 const el=document.getElementById('orgWrap'); if(!el) return; 1613 const emps=_dir||[]; if(!emps.length){ el.innerHTML='<div class="empty">No data.</div>'; return; } 1614 const byName={}; emps.forEach(function(e){ byName[String(e.name||'').trim().toLowerCase()]=e; });
1615 const kids={}, roots=[]; 1616 emps.forEach(function(e){ const m=String(e.reportingManager||'').trim().toLowerCase(); if(m && byName[m] && byName[m]!==e){ (kids[m]=kids[m]||[]).push(e); } else roots.push(e); }); 1617 const seen={}; 1618 function node(e){ 1619 const nm=String(e.name||'').trim().toLowerCase(); if(seen[nm]) return ''; seen[nm]=1; 1620 const ini=(String(e.name||'?').trim()[0]||'?').toUpperCase(); const reports=kids[nm]||[]; 1621 let h='<div class="org-node"><div class="org-card"><div class="org-av">'+esc(ini)+'</div><div class="org-info"><b>'+esc(e.name||'')+'</b><span>'+esc(e.designation||'\u2014')+(e.department?(' \u00b7 '+esc(e.department)):'')+'</span></div>'+(reports.length?'<span class="org-count">'+reports.length+'</span>':'')+'</div>'; 1622 if(reports.length){ h+='<div class="org-children">'+reports.map(node).join('')+'</div>'; } 1623 return h+'</div>'; 1624 } 1625 roots.sort(function(a,b){ return (kids[String(b.name||'').trim().toLowerCase()]||[]).length-(kids[String(a.name||'').trim().toLowerCase()]||[]).length; }); 1626 el.innerHTML=roots.map(node).join('') || '<div class="empty">No reporting structure set.</div>'; 1627 } 1628 /* ---- Company Timeline ---- */ 1629 async function loadTimeline(){ 1630 const id=currentId||ssGet('ems_id'); if(!id) return; 1631 const d=await api({action:'emsTimeline', employeeId:id}); const rows=(d&&d.rows)||[]; 1632 const el=document.getElementById('tlWrap'); if(!el) return; 1633 if(!rows.length){ el.innerHTML='<div class="empty">No milestones yet.</div>'; return; } 1634 const ico={milestone:'ti-flag',achievement:'ti-trophy',announcement:'ti-speakerphone',celebration:'ti-confetti',event:'ti-calendar-event',other:'ti-point'}; 1635 const col={milestone:'#e8c977',achievement:'#7fe0a8',announcement:'#8fb6f0',celebration:'#c6a3ff',event:'#f3d27a',other:'#a9b6c9'}; 1636 el.innerHTML='<div class="tl">'+rows.map(function(t){ const c=String(t.category||'milestone'); const pin=(t.pinned===true||String(t.pinned)==='true'); const dt=fmtDate(t.event_date)||t.event_date||''; return '<div class="tl-item"><div class="tl-dot" style="background:'+(col[c]||'#a9b6c9')+'"><i class="ti '+(ico[c]||'ti-point')+'"></i></div><div class="tl-body"><div class="tl-top"><b>'+esc(t.title||'')+'</b>'+(pin?' <span class="tl-pin"><i class="ti ti-pin"></i></span>':'')+'</div><div class="tl-date">'+esc(dt)+' · '+esc(c.charAt(0).toUpperCase()+c.slice(1))+'</div>'+(t.description?'<div class="tl-desc">'+esc(t.description)+'</div>':'')+'</div></div>'; }).join('')+'</div>'; 1637 } 1638 /* ---- Knowledge Base ---- */ 1639 var _kb=[], _kbCat='all'; 1640 async function loadKB(){ 1641 const id=currentId||ssGet('ems_id'); if(!id) return; 1642 const d=await api({action:'emsKB', employeeId:id}); _kb=(d&&d.rows)||[]; _kbCat='all'; renderKBCats(); renderKB(); 1643 } 1644 function renderKBCats(){ 1645 const el=document.getElementById('kbCats'); if(!el) return; 1646 const cats=['all'].concat(Array.from(new Set(_kb.map(function(k){return String(k.category||'general');}))).sort()); 1647 el.innerHTML=cats.map(function(c){ return '<button class="kb-cat'+(c===_kbCat?' on':'')+'" onclick="kbPick(\''+c+'\')">'+esc(c==='all'?'All':(c.charAt(0).toUpperCase()+c.slice(1)))+'</button>'; }).join(''); 1648 } 1649 function kbPick(c){ _kbCat=c; renderKBCats(); renderKB(); } 1650 function renderKB(){ 1651 const el=document.getElementById('kbList'); if(!el) return; 1652 const ql=((document.getElementById('kbSearch')||{}).value||'').trim().toLowerCase(); 1653 let rows=_kb.filter(function(k){ return _kbCat==='all'||String(k.category)===_kbCat; }); 1654 if(ql) rows=rows.filter(function(k){ return (String(k.title)+' '+String(k.content)+' '+String(k.tags)).toLowerCase().indexOf(ql)>=0; }); 1655 if(!rows.length){ el.innerHTML='<div class="empty">No articles found.</div>'; return; } 1656 el.innerHTML=rows.map(function(k){ const pin=(k.pinned===true||String(k.pinned)==='true'); return '<div class="acc kb-acc"><div class="acc-h" onclick="toggleAcc(this)"><div class="ai"><i class="ti ti-help-circle"></i></div><div class="at"><b>'+esc(k.title||'')+(pin?' <i class="ti ti-pin" style="color:var(--gold-soft);font-size:12px"></i>':'')+'</b><span>'+esc((k.category||'general').charAt(0).toUpperCase()+(k.category||'general').slice(1))+'</span></div><i class="ti ti-chevron-down chev"></i></div><div class="acc-b"><div class="acc-bi"><div class="kb-content">'+esc(k.content||'').replace(/\n/g,'<br>')+'</div>'+(k.tags?'<div class="kb-tags">#'+esc(String(k.tags).split(',').map(function(x){return x.trim();}
1656).filter(Boolean).join(' #'))+'</div>':'')+'</div></div></div>'; }).join(''); 1657 } 1658 /* ---- Meetings & Calendar ---- */ 1659 var _mtg=[], _mtgView='upcoming'; 1660 async function loadMeetings(){ 1661 const id=currentId||ssGet('ems_id'); if(!id) return; 1662 const d=await api({action:'emsMyMeetings', employeeId:id}); _mtg=(d&&d.rows)||[]; renderMeetings(); 1663 if(!window._mtgTimer){ window._mtgTimer=setInterval(mtgTick,1000); } mtgTick(); 1664 } 1665 function mtgTargetTs(m){ const d=String(m.meeting_date||''); if(!d) return 0; let t=String(m.start_time||'00:00'); if(t.length<5) t='00:00'; const v=new Date(d+'T'+t.slice(0,5)+':00').getTime(); return isNaN(v)?0:v; } 1666 function mtgTick(){ 1667 const mp=document.getElementById('meetings'); if(!mp||!mp.classList.contains('show')) return; 1668 const now=Date.now(); 1669 document.querySelectorAll('.mtg-cd').forEach(function(el){ 1670 const ts=Number(el.getAttribute('data-ts'))||0; let diff=ts-now; 1671 if(!ts){ el.innerHTML=''; return; } 1672 if(diff<=0){ el.innerHTML='<span class="mtg-live"><span class="mtg-livedot"></span> Happening now</span>'; return; } 1673 const dd=Math.floor(diff/86400000); diff-=dd*86400000; 1674 const hh=Math.floor(diff/3600000); diff-=hh*3600000; 1675 const mm=Math.floor(diff/60000); diff-=mm*60000; 1676 const ss=Math.floor(diff/1000); const pad=function(n){return String(n).padStart(2,'0');}; 1677 el.innerHTML='<span class="cd-lbl">Starts in</span><div class="cd-b"><b>'+dd+'</b><span>days</span></div><div class="cd-s">:</div><div class="cd-b"><b>'+pad(hh)+'</b><span>hrs</span></div><div class="cd-s">:</div><div class="cd-b"><b>'+pad(mm)+'</b><span>min</span></div><div class="cd-s">:</div><div class="cd-b"><b>'+pad(ss)+'</b><span>sec</span></div>'; 1678 }); 1679 } 1680 /* ---- Dynamic Forms ---- */ 1681 var _forms=[]; 1682 async function loadForms(){ 1683 const id=currentId||ssGet('ems_id'); if(!id) return; 1684 const d=await api({action:'emsMyForms', employeeId:id}); _forms=(d&&d.rows)||[]; renderForms(); 1685 } 1686 function renderForms(){ 1687 const el=document.getElementById('formsList'); if(!el) return; 1688 if(!_forms.length){ el.innerHTML='<div class="empty">No forms to fill right now.</div>'; return; } 1689 el.innerHTML=_forms.map(function(f,fi){ 1690 if(f.responded) return '<div class="fm-card done"><div class="fm-h"><b>'+esc(f.title||'')+'</b><span class="fm-done"><i class="ti ti-circle-check-filled"></i> Submitted</span></div>'+(f.description?'<p class="fm-desc">'+esc(f.description)+'</p>':'')+'</div>'; 1691 var body=(f.fields||[]).map(function(fld,i){ return '<div class="fm-field"><label class="fm-lbl">'+esc(fld.label||'')+(fld.required?' <span style="color:#ff9c8b">*</span>':'')+'</label>'+fmFieldInput(fld,fi,i)+'</div>'; }).join(''); 1692 return '<div class="fm-card"><div class="fm-h"><b>'+esc(f.title||'')+'</b></div>'+(f.description?'<p class="fm-desc">'+esc(f.description)+'</p>':'')+body+'<button class="btn fm-submit" onclick="submitForm('+fi+')"><i class="ti ti-send"></i> Submit</button></div>'; 1693 }).join(''); 1694 } 1695 function fmFieldInput(fld,fi,i){ 1696 var n='fm_'+fi+'_'+i; var t=fld.type||'text'; 1697 if(t==='textarea') return '<textarea class="inp" id="'+n+'" rows="3"></textarea>'; 1698 if(t==='number') return '<input class="inp" type="number" id="'+n+'">'; 1699 if(t==='date') return '<input class="inp" type="date" id="'+n+'">'; 1700 if(t==='select'){ return '<select class="inp" id="'+n+'"><option value="">â Select â</option>'+(fld.options||[]).map(function(o){return '<option>'+esc(o)+'</option>';}).join('')+'</select>'; } 1701 if(t==='radio'){ return '<div class="fm-opts" id="'+n+'">'+(fld.options||[]).map(function(o,oi){return '<label class="fm-opt"><input type="radio" name="'+n+'" value="'+esc(o)+'"> '+esc(o)+'</label>';}).join('')+'</div>'; } 1702 if(t==='checkbox'){ return '<div class="fm-opts" id="'+n+'">'+(fld.options||[]).map(function(o){return '<label class="fm-opt"><input type="checkbox" value="'+esc(o)+'"> '+esc(o)+'</label>';}).join('')+'</div>'; } 1703 return '<input class="inp" id="'+n+'">'; 1704 } 1705 async function submitForm(fi){ 1706 const f=_forms[fi]; if(!f) return; const answers=[]; 1707 for(var i=0;i<(f.fields||[]).length;i++){ var fld=f.fields[i]; var n='fm_'+fi+'_'+i; var t=fld.type||'text'; var val; 1708 if(t==='radio'){ var c=document.querySelector('input[name="'+n+'"]:checked'); val=c?c.value:''; } 1709 else if(t==='checkbox'){ val=Array.prototype.map.call(document.querySelectorAll('#'+n+' input:checked'),function(x){return x.value;}); } 1710 else { var el=document.getElementById(n); val=el?el.value:''; } 1711 if(fld.required && (val===''||val==null||(Array.isArray(val)&&!val.length))){ toast('Please fill: '+fld.label); return; } 1712 answers.push(val); 1713 } 1714 const id=currentId||ssGet('ems_id'); 1715 const d=await api({action:'emsFormSubmit', employeeId:id, formId:f.id, answers:JSON.stringify(answers)}); 1716 if(d&&d.status==='success'){ toast('Submitted â'); loadForms(); } else toast((d&&d.message)||'Failed'); 1717 } 1718 /* ---- Messaging (employee <-> HR) ---- */ 1719 async function loadMessages(){ 1720 const id=currentId||ssGet('ems_id'); if(!id) return; 1721 const d=await api({action:'emsMyMessages', employeeId:id}); const rows=(d&&d.rows)||[]; 1722 const el=document.getElementById('msgMsgs'); if(!el) return; 1723 if(!rows.length){ el.innerHTML='<div class="empty">No messages yet. Say hello to HR ð</div>'; } 1724 else { 1725 var groups={}, order=[]; rows.forEach(function(m){ var dt=String(m.time||'').slice(0,10); if(!groups[dt]){groups[dt]=[];order.push(dt);} groups[dt].push(m); }); 1726 var todayStr=_todayISO(); 1727 el.innerHTML=order.map(function(dt,gi){ 1728 var last=(gi===order.length-1); 1729 var label=(dt===todayStr)?'Today':(fmtDate(dt)||dt); 1730 var msgs=groups[dt].map(function(m){ var mine=String(m.sender)==='employee'; return '<div class="msg-bub '+(mine?'me':'them')+'">'+(!mine?'<span class="msg-from">'+esc(m.sender_name||'HR')+'</span>':'')+'<div class="msg-b">'+esc(m.body||'')+'</div><div class="msg-t">'+esc(String(m.time||'').slice(11,16))+'</div></div>'; }).join(''); 1731 return '<div class="msg-day'+(last?' open':'')+'"><div class="msg-day-h" onclick="msgDayToggle(this)"><i class="ti ti-chevron-down msg-day-chev"></i> '+esc(label)+' <span class="msg-day-n">'+groups[dt].length+'</span></div><div class="msg-day-b"><div class="msg-day-bi">'+msgs+'</div></div></div>'; 1732 }).join(''); 1733 el.scrollTop=el.scrollHeight; 1734 } 1735 if(!window._msgTimer){ window._msgTimer=setInterval(function(){ var pn=document.getElementById('messages'); if(pn&&pn.classList.contains('show')) loadMessages(); },15000); } 1736 } 1737 async function sendMsg(){ 1738 const el=document.getElementById('msgInput'); const t=el?el.value:''; if(!t||!t.trim()) return; 1739 const id=currentId||ssGet('ems_id'); if(el) el.value='';
1740 const d=await api({action:'emsSendMessage', employeeId:id, body:t}); 1741 if(d&&d.status==='success'){ loadMessages(); } else { toast('Failed'); if(el) el.value=t; } 1742 } 1743 function msgDayToggle(h){ h.parentElement.classList.toggle('open'); } 1744 /* ================= DASHBOARD WORKSPACE ================= */ 1745 function dashGo(t){ var b=document.querySelector('.tab[data-tab="'+t+'"]'); if(b) b.click(); } 1746 function dwEmpty(msg,icon){ return '<div class="dw-empty"><i class="ti '+(icon||'ti-checkbox')+'"></i><span>'+esc(msg)+'</span></div>'; } 1747 function _todayISO(){ try{ return new Date().toLocaleDateString('en-CA',{timeZone:'Asia/Kolkata'}); }catch(_){ return new Date().toISOString().slice(0,10); } } 1748 async function loadDashboard(){ 1749 const id=currentId||ssGet('ems_id'); if(!id) return; 1750 try{ var e=(typeof BUNDLE!=='undefined'&&BUNDLE&&BUNDLE.employee)||{}; var fs=['name','email','phone','department','designation','doj','dob','address','reportingManager']; var fl=fs.filter(function(f){return e[f]&&String(e[f]).trim();}).length; var pe=document.getElementById('eProfilePct'); if(pe)pe.textContent=Math.round(fl/fs.length*100)+'%'; }catch(_p){} 1751 // Each widget renders from its own result â one failing endpoint never blanks the whole dashboard. 1752 var rr=await Promise.allSettled([ 1753 api({action:'emsMyTasks',employeeId:id}), 1754 api({action:'emsMyMeetings',employeeId:id}), 1755 api({action:'emsLeaveInit',employeeId:id}), 1756 api({action:'emsMyReviews',employeeId:id}) 1757 ]); 1758 var _v=function(x){ return (x&&x.status==='fulfilled'&&x.value)?x.value:null; }; 1759 var r0=_v(rr[0]),r1=_v(rr[1]),r2=_v(rr[2]),r3=_v(rr[3]); 1760 try{ dwTasks((r0&&r0.rows)||[]); }catch(_a){} 1761 try{ dwUpcoming((r1&&r1.rows)||[]); }catch(_b){} 1762 try{ dwLeave(r2||{}); }catch(_c){} 1763 try{ dwPerf((r3&&r3.rows)||[]); }catch(_e){} 1764 try{ dwUpdates(window.__unifiedNotifs||[]); }catch(_f){} 1765 } 1766 function dwTasks(rows){ var el=document.getElementById('dwTasks'); if(!el)return; var pend=(rows||[]).filter(function(t){return String(t.status)!=='done';}).slice(0,5); if(!pend.length){ el.innerHTML=dwEmpty('No pending tasks \u2014 you\u2019re all caught up!','ti-checks'); return; } var pc={high:'#ff9c8b',med:'var(--gold-soft)',low:'var(--faint)'}; el.innerHTML=pend.map(function(t){ var pr=String(t.priority||'low'); var st=String(t.status||'todo').replace('_',' '); return '<div class="dw-task"><span class="dw-pri" style="background:'+(pc[pr]||pc.low)+'"></span><div class="dw-task-b"><b>'+esc(t.title||'Untitled task')+'</b><span class="dw-task-m">'+esc(st)+(pr?' \u00b7 '+esc(pr)+' priority':'')+'</span></div><i class="ti ti-chevron-right dw-task-go"></i></div>'; }).join(''); } 1767 function dwUpcoming(rows){ var el=document.getElementById('dwUpcoming'); if(!el)return; var base=new Date(new Date().toDateString()); var up=(rows||[]).filter(function(m){ var d=new Date(String(m.meeting_date||'')+'T00:00:00'); return d>=base; }).sort(function(a,b){return String(a.meeting_date+((a.start_time)||'')).localeCompare(String(b.meeting_date+((b.start_time)||'')));}).slice(0,4); if(!up.length){ el.innerHTML=dwEmpty('No upcoming meetings or events.','ti-calendar'); return; } var td=_todayISO(); el.innerHTML=up.map(function(m){ var dt=String(m.meeting_date||''); var lbl=(dt===td)?'Today':(fmtDate(dt)||dt); return '<div class="dw-up"><div class="dw-up-d"><b>'+esc(lbl)+'</b><span>'+esc(String(m.start_time||'').slice(0,5)||'\u2014')+'</span></div><div class="dw-up-b"><b>'+esc(m.title||'Event')+'</b>'+(m.location?'<span><i class="ti ti-map-pin"></i> '+esc(m.location)+'</span>':(m.link?'<span><i class="ti ti-video"></i> Online</span>':''))+'</div></div>'; }).join(''); } 1768 function dwUpdates(rows){ var el=document.getElementById('dwUpdates'); if(!el)return; var today=_todayISO(); var seen={}, list=[]; (rows||[]).forEach(function(n){ var key=(String(n.title||'')+'|'+String(n.message||'')).toLowerCase().slice(0,120); if(seen[key])return; seen[key]=1; list.push(n); }); var r=list.slice(0,4); if(!r.length){ el.innerHTML=dwEmpty('No new updates right now.','ti-bell'); return; } el.innerHTML=r.map(function(n){ var ic=(n.type==='success'?'ti-circle-check':(n.type==='warn'?'ti-alert-triangle':'ti-info-circle')); var isNew=String(n.time||'').slice(0,10)===today; var dot=isNew?'<span class="dw-new" title="New today"></span>':''; return '<div class="dw-note"><div class="dw-note-i '+(n.type||'info')+'"><i class="ti '+ic+'"></i></div><div class="dw-note-b"><b>
1768'+esc(n.title||'Update')+dot+'</b>'+(n.message?'<span>'+esc(String(n.message).slice(0,90))+'</span>':'')+'<em>'+esc(String(n.time||'').slice(0,16))+'</em></div></div>'; }).join(''); } 1769 function dwLeave(d){ var el=document.getElementById('dwLeave'); if(!el)return; var types=(d&&d.types)||[], bals=(d&&d.balances)||[]; var by={}; bals.forEach(function(b){by[b.type_code]=b;}); if(!types.length){ el.innerHTML=dwEmpty('Leave balance not set yet.','ti-calendar-off'); return; } el.innerHTML=types.slice(0,4).map(function(t){ var b=by[t.code]||{}; var al=Number(b.allotted!=null?b.allotted:(t.annual_quota||0))||0; var us=Number(b.used||0)+Number(b.pending||0); var rem=Math.max(0,al-us); var pct=al>0?Math.min(100,Math.round(us/al*100)):0; var paid=(String(t.paid)!=='false'); var tag='<span class="lv-tag '+(paid?'paid':'unpaid')+'">'+(paid?'PAID':'UNPAID')+'</span>'; var val=(al>0)?(rem+'<small>/'+al+'</small>'):'â'; return '<div class="dw-lv"><div class="dw-lv-t"><span>'+esc(t.name||t.code)+tag+'</span><b>'+val+'</b></div><div class="dw-lv-bar"><span style="width:'+pct+'%"></span></div></div>'; }).join(''); } 1770 function dwPerf(rows){ var el=document.getElementById('dwPerf'); if(!el)return; if(!(rows&&rows.length)){ el.innerHTML=dwEmpty('No performance review shared yet.','ti-chart-line'); return; } var r=rows[0]; var ov=Number(r.overall||0); var stars=[1,2,3,4,5].map(function(i){return '<i class="ti ti-star-filled'+(i<=Math.round(ov)?' on':'')+'"></i>';}).join(''); var br=[['Attendance',r.attendance_score],['Quality',r.quality],['Discipline',r.discipline],['Tasks',r.task_score]].filter(function(x){return x[1]!=null&&x[1]!=='';}); el.innerHTML='<div class="dw-perf-top"><div class="dw-perf-n">'+(ov?ov.toFixed(1):'\u2014')+'<small>/5</small></div><div class="dw-perf-r"><div class="stars">'+stars+'</div><span>'+esc(r.period||'Latest review')+'</span></div></div>'+(br.length?'<div class="dw-perf-br">'+br.map(function(x){var v=Number(x[1])||0;return '<div class="dw-lv"><div class="dw-lv-t"><span>'+x[0]+'</span><b>'+v.toFixed(1)+'</b></div><div class="dw-lv-bar"><span style="width:'+(v/5*100)+'%"></span></div></div>';}).join('')+'</div>':''); } 1771 function mtgView(v){ _mtgView=v; document.querySelectorAll('#mtgTabs .kb-cat').forEach(function(b){ b.classList.toggle('on', b.textContent.toLowerCase()===v); }); renderMeetings(); } 1772 function renderMeetings(){ 1773 const el=document.getElementById('mtgList'); if(!el) return; 1774 const today=_todayISO(); 1775 let rows=_mtg.filter(function(m){ return _mtgView==='upcoming' ? String(m.meeting_date||'')>=today : String(m.meeting_date||'')<today; }); 1776 if(_mtgView==='past') rows=rows.reverse(); 1777 if(!rows.length){ el.innerHTML='<div class="empty">No '+_mtgView+' meetings.</div>'; return; } 1778 const ico={meeting:'ti-users',event:'ti-confetti',training:'ti-school',holiday:'ti-beach'}; 1779 const col={meeting:'#8fb6f0',event:'#e8c977',training:'#7fe0a8',holiday:'#c6a3ff'}; 1780 const groups={}; rows.forEach(function(m){ const d=m.meeting_date||''; (groups[d]=groups[d]||[]).push(m); }); 1781 let h=''; Object.keys(groups).forEach(function(dt){ 1782 h+='<div class="mtg-date">'+esc(fmtDate(dt)||dt)+'</div>'; 1783 h+=groups[dt].map(function(m){ const t=String(m.mtype||'meeting'); return '<div class="mtg-card"><div class="mtg-ic" style="background:'+(col[t]||'#8fb6f0')+'"><i class="ti '+(ico[t]||'ti-users')+'"></i></div><div class="mtg-body"><b>'+esc(m.title||'')+'</b><div class="mtg-meta">'+(m.start_time?esc(m.start_time)+(m.end_time?('â'+esc(m.end_time)):''):'All day')+(m.location?(' · '+esc(m.location)):'')+'</div>'+(_mtgView==='upcoming'?'<div class="mtg-cd" data-ts="'+mtgTargetTs(m)+'"></div>':'')+(m.description?'<div class="mtg-desc">'+esc(m.description)+'</div>':'')+(m.link?'<a class="mtg-link" href="'+esc(m.link)+'" target="_blank"><i class="ti ti-video"></i> Join</a>':'')+'</div></div>'; }).join(''); 1784 }); 1785 el.innerHTML=h; 1786 } 1787 async function updateGoalProgress(gid){ 1788 const id=currentId||ssGet('ems_id'); const el=document.getElementById('gs_'+gid); if(!el) return; 1789 const d=await api({action:'emsGoalProgress', employeeId:id, id:gid, progress:el.value}); 1790 if(d&&d.status==='success'){ toast('Progress updated â'); loadGoals(); } else toast((d&&d.message)||'Failed'); 1791 } 1792 var _myTasks=[]; 1793 async function loadTasks(){ 1794 const id=currentId||ssGet('ems_id'); if(!id) return; 1795 const d=await api({action:'emsMyTasks', employeeId:id}); _myTasks=(d&&d.rows)||[]; renderTasks(); 1796 } 1797 function renderTasks(){ 1798 const el=document.getElementById('tasksWrap'); if(!el) return; 1799 if(!_myTasks.length){ el.innerHTML='<div class="empty">No tasks assigned yet.</div>'; return; } 1800 const cols=[['todo','To-do'],['in_progress','In Progress'],['review','Review'],['done','Done']]; 1801 el.innerHTML='<div class="kb">'+cols.map(function(c){ const items=_myTasks.filter(function(t){return String(t.status)===c[0];}); return '<div class="kb-col"><div class="kb-h">'+c[1]+' <span>'+items.length+'</span></div>'+items.map(taskCard).join('')+'</div>'; }).join('')+'</div>'; 1802 } 1803 function taskCard(t){ 1804 const pri={high:'#ff9c8b',med:'var(--gold-soft)',low:'var(--faint)'}[String(t.priority)]||'var(--faint)'; 1805 const opts=['todo','in_progress','review','done'].map(function(s){ return '<option value="'+s+'"'+(String(t.status)===s?' selected':'')+'>'+s.replace('_',' ')+'</option>'; }).join(''); 1806 return '<div class="kb-card"><div style="border-left:3px solid '+pri+';padding-left:8px"><b>'+esc(t.title||'')+'</b>'+(t.description?'<div style="font-size:12px;color:var(--muted);margin-top:2px">'+esc(t.description)+'</div>':'')+(t.due_date?'<div style="font-size:11px;color:var(--faint);margin-top:2px">Due '+esc(t.due_date)+'</div>
1806':'')+'</div><select class="inp" style="margin-top:8px;margin-bottom:0" onchange="taskSetStatus(\''+esc(t.id)+'\',this.value)">'+opts+'</select></div>'; 1807 } 1808 async function taskSetStatus(tid,status){ 1809 const id=currentId||ssGet('ems_id'); 1810 const params={action:'emsTaskUpdate', employeeId:id, id:tid, status:status}; if(status==='done') params.progress=100; 1811 const d=await api(params); 1812 if(d&&d.status==='success'){ toast('Updated â'); loadTasks(); } else toast((d&&d.message)||'Failed'); 1813 } 1814 async function loadPerformance(){ 1815 const id=currentId||ssGet('ems_id'); if(!id) return; 1816 const d=await api({action:'emsMyReviews', employeeId:id}); 1817 const el=document.getElementById('perfWrap'); if(!el) return; 1818 const rows=(d&&d.rows)||[]; 1819 if(!rows.length){ el.innerHTML='<div class="empty">No performance reviews shared yet.</div>'; return; } 1820 el.innerHTML=rows.map(function(r){ 1821 const chips=[['Attendance',r.attendance_score,'%'],['Tasks',r.task_score,'%'],['Quality',r.quality,'/5'],['Communication',r.communication,'/5'],['Leadership',r.leadership,'/5'],['Innovation',r.innovation,'/5'],['Discipline',r.discipline,'/5']]; 1822 return '<div class="perf-card"><div class="perf-top"><div><div class="perf-period">'+esc(r.period||'')+'</div>'+(r.promotion_eligible?'<span class="cs resolved" style="margin-top:6px;display:inline-block">Promotion eligible</span>':'')+'</div><div class="perf-overall"><b>'+esc(String(r.overall||0))+'</b><span>/5</span></div></div><div class="perf-chips">'+chips.map(function(c){ return '<span class="perf-chip">'+c[0]+' <b>'+esc(String(c[1]||0))+c[2]+'</b></span>'; }).join('')+'</div>'+(r.manager_feedback?'<div class="perf-fb"><b>Manager Feedback</b><div>'+esc(r.manager_feedback)+'</div></div>':'')+(r.improvement?'<div class="perf-fb"><b>Areas to Improve</b><div>'+esc(r.improvement)+'</div></div>':'')+'</div>'; 1823 }).join(''); 1824 } 1825 async function loadRewards(){ 1826 const id=currentId||ssGet('ems_id'); if(!id) return; 1827 const res=await Promise.all([api({action:'emsMyRewards', employeeId:id}), api({action:'emsRewardsLeaderboard', employeeId:id})]); 1828 const mine=res[0]||{}, lb=res[1]||{}; const el=document.getElementById('rewardsWrap'); if(!el) return; 1829 const rows=mine.rows||[], points=mine.points||0; 1830 let h='<div class="rw-hero"><div><div class="rw-pts">'+points+'</div><div class="rw-lbl">Reward Points</div></div><i class="ti ti-trophy"></i></div>'; 1831 if(rows.length){ h+='<div class="accwrap-head" style="margin-top:16px">My Rewards</div>'+rows.map(function(r){ const icon=String(r.kind)==='eotm'?'ti-crown':(String(r.kind)==='badge'?'ti-medal':(String(r.kind)==='achievement'?'ti-star':'ti-coin')); return '<div class="rw-item"><i class="ti '+icon+'"></i><div><b>'+esc(r.title||r.kind)+'</b>'+(r.note?'<div style="font-size:12px;color:var(--muted)">'+esc(r.note)+'</div>':'')+'</div>'+(Number(r.points)>0?'<span class="rw-badge">+'+r.points+'</span>':'')+'</div>'; }).join(''); } 1832 const board=lb.rows||[]; 1833 if(board.length){ h+='<div class="accwrap-head" style="margin-top:16px">Leaderboard</div>'+board.map(function(b,i){ const me=String(b.employeeId)===String(id); return '<div class="lb-row'+(me?' me':'')+'"><span class="lb-rank">'+(i+1)+'</span><div class="lb-name"><b>'+esc(b.name||b.employeeId)+'</b>'+(me?' <span style="color:var(--gold-soft)">(you)</span>':'')+'</div><span class="lb-pts">'+(b.points||0)+' pts'+(b.badges?(' · '+b.badges+'\u{1F3C5}'):'')+'</span></div>'; }).join(''); } 1834 if(!rows.length && !board.length) h+='<div class="empty" style="margin-top:16px">No rewards yet. Keep up the great work!</div>'; 1835 el.innerHTML=h; 1836 }
1837 /* ---- Phase 4 (Module 3): Learning (employee) ---- */ 1838 var _myCourses=[]; 1839 async function loadCourses(){ 1840 const id=currentId||ssGet('ems_id'); if(!id) return; 1841 const d=await api({action:'emsMyCourses', employeeId:id}); _myCourses=(d&&d.rows)||[]; 1842 const el=document.getElementById('coursesWrap'); if(!el) return; 1843 if(!_myCourses.length){ el.innerHTML='<div class="empty">No courses assigned yet.</div>'; return; } 1844 const tc=function(s){ s=String(s||'').replace('_',' '); return s.charAt(0).toUpperCase()+s.slice(1); }; 1845 el.innerHTML=_myCourses.map(function(c){ 1846 const st=String(c.status||'assigned'); const comp=st==='completed'; const pct=comp?100:(Number(c.progress)||0); 1847 const mand=(c.mandatory===true||String(c.mandatory)==='true'); 1848 const openBtn=c.url?'<a class="dl ghost" href="'+esc(c.url)+'" target="_blank" style="padding:7px 12px"><i class="ti ti-external-link"></i> Open course</a>':''; 1849 let ctrl=''; 1850 if(comp){ ctrl='<button class="btn" onclick="genCertificate(\''+esc(c.id)+'\')"><i class="ti ti-certificate"></i> Download Certificate</button>'; } 1851 else { ctrl='<div class="crs-edit"><input type="range" min="0" max="100" value="'+pct+'" id="cp_'+esc(c.id)+'" oninput="document.getElementById(\'cv_'+esc(c.id)+'\').textContent=this.value+\'%\'"><span id="cv_'+esc(c.id)+'">'+pct+'%</span><button class="dl ghost" style="padding:7px 12px" onclick="courseSaveProgress(\''+esc(c.id)+'\')">Save</button><button class="btn" onclick="courseComplete(\''+esc(c.id)+'\')"><i class="ti ti-check"></i> Mark complete</button></div>'; } 1852 return '<div class="crs-card"><div class="ctop"><div><b>'+esc(c.course_title||'')+'</b>'+(mand?' <span class="cs" style="background:rgba(243,160,90,.18);color:#f3a05a">Mandatory</span>':'')+'</div><span class="cs '+(comp?'resolved':'open')+'">'+esc(tc(st))+'</span></div><div class="crs-meta">'+esc(tc(c.category||''))+(c.provider?(' · '+esc(c.provider)):'')+(c.duration?(' · '+esc(c.duration)):'')+(c.due_date&&!comp?(' · due '+esc(c.due_date)):'')+(comp&&c.completed_on?(' · completed '+esc(fmtDate(c.completed_on)||c.completed_on)):'')+'</div>'+(c.description?'<div class="crs-desc">'+esc(c.description)+'</div>':'')+'<div class="goal-bar"><span style="width:'+pct+'%"></span></div><div class="crs-actions">'+openBtn+ctrl+'</div></div>'; 1853 }).join(''); 1854 } 1855 async function courseSaveProgress(cid){ 1856 const id=currentId||ssGet('ems_id'); const el=document.getElementById('cp_'+cid); if(!el) return; 1857 const d=await api({action:'emsCourseProgress', employeeId:id, id:cid, progress:el.value}); 1858 if(d&&d.status==='success'){ toast('Progress saved \u2713'); loadCourses(); } else toast((d&&d.message)||'Failed'); 1859 } 1860 async function courseComplete(cid){ 1861 if(!confirm('Mark this course as complete? Your certificate will be ready to download.')) return; 1862 const id=currentId||ssGet('ems_id'); 1863 const d=await api({action:'emsCourseProgress', employeeId:id, id:cid, complete:'true'}); 1864 if(d&&d.status==='success'){ toast('Completed \u2713'); loadCourses(); } else toast((d&&d.message)||'Failed'); 1865 } 1866 function genCertificate(cid){ 1867 try{ 1868 const c=(_myCourses||[]).find(function(x){return String(x.id)===String(cid);}); if(!c){ toast('Course not found'); return; } 1869 const e=BUNDLE.employee||{}, co=company(); 1870 const doc=new window.jspdf.jsPDF({unit:'pt',format:'a4',orientation:'landscape'}); const W=842,H=595; 1871 doc.setFillColor(17,17,22); doc.rect(0,0,W,H,'F'); 1872 doc.setDrawColor(200,155,60); doc.setLineWidth(3); doc.rect(24,24,W-48,H-48); 1873 doc.setLineWidth(1); doc.rect(34,34,W-68,H-68); 1874 try{ doc.addImage(logoImg(),'JPEG',W/2-26,58,52,52); }catch(_){} 1875 doc.setTextColor(232,201,119); doc.setFont('helvetica','bold'); doc.setFontSize(15); doc.text(String(co.name||'').toUpperCase(),W/2,132,{align:'center'}); 1876 doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.setFontSize(30); doc.text('CERTIFICATE OF COMPLETION',W/2,190,{align:'center'}); 1877 doc.setDrawColor(200,155,60); doc.setLineWidth(2); doc.line(W/2-150,206,W/2+150,206); 1878 doc.setTextColor(200,200,205); doc.setFont('helvetica','normal'); doc.setFontSize(13); doc.text('This is to certify that',W/2,250,{align:'center'}); 1879 doc.setTextColor(232,201,119); doc.setFont('helvetica','bold'); doc.setFontSize(28); doc.text(String(e.name||''),W/2,290,{align:'center'}); 1880 doc.setTextColor(200,200,205); doc.setFont('helvetica','normal'); doc.setFontSize(13); doc.text('has successfully completed the course',W/2,325,{align:'center'}); 1881 doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.setFontSize(19); doc.text(doc.splitTextToSize('"'+String(c.course_title||'')+'"',W-220),W/2,360,{align:'center'}); 1882 const dt=fmtDate(c.completed_on)||c.completed_on||new Date().toLocaleDateString('en-IN'); 1883 doc.setTextColor(185,185,190); doc.setFont('helvetica','normal'); doc.setFontSize(11); 1884 doc.text('Date of Completion: '+dt,W/2,422,{align:'center'}); 1885 if(c.provider) doc.text('Provider: '+String(c.provider),W/2,440,{align:'center'}); 1886 doc.setDrawColor(150,150,155); doc.setLineWidth(.8); doc.line(W-262,502,W-92,502); 1887 doc.setTextColor(200,200,205); doc.setFontSize(11); doc.text('Authorised Signatory',W-177,518,{align:'center'}); 1888 doc.setTextColor(140,140,145); doc.setFontSize(8.5); doc.text(String(co.name||'')+' \u00b7 '+String(co.hr||'HR Department'),W-177,532,{align:'center'}); 1889 doc.setTextColor(120,120,125); doc.setFontSize(8.5); doc.text('Employee ID: '+String(e.employeeId||''),72,532); 1890 doc.save((String(e.name||'employee').replace(/\s+/g,'_'))+'_Certificate_'+(String(c.course_title||'course').replace(/\s+/g,'_'))+'.pdf'); 1891 }catch(err){ toast('Could not generate certificate. Please try again.'); } 1892 }
1893 /* ---- Phase 4 (Module 4): Engagement (employee) ---- */ 1894 var _myPolls=[]; 1895 async function loadEngagement(){ 1896 const id=currentId||ssGet('ems_id'); if(!id) return; 1897 const res=await Promise.all([api({action:'emsMyPolls', employeeId:id}), api({action:'emsSuggestionBrowse', employeeId:id})]); 1898 _myPolls=(res[0]&&res[0].rows)||[]; 1899 renderEngagement(_myPolls, (res[1]&&res[1].rows)||[]); 1900 } 1901 function renderEngagement(polls, suggestions){ 1902 const el=document.getElementById('engageWrap'); if(!el) return; 1903 let h=''; 1904 const pulses=polls.filter(function(p){return String(p.type)==='pulse' && !p.responded;}); 1905 pulses.forEach(function(p){ h+='<div class="pulse-card"><div class="pulse-q"><i class="ti ti-mood-smile"></i> '+esc((p.questions[0]||{}).q||'How are you feeling?')+'</div><div class="pulse-scale">'+[1,2,3,4,5].map(function(n){ return '<button class="pulse-dot" onclick="submitPulse(\''+esc(p.id)+'\','+n+')">'+n+'</button>'; }).join('')+'</div></div>'; }); 1906 const active=polls.filter(function(p){return String(p.type)!=='pulse';}); 1907 if(active.length){ h+='<div class="accwrap-head" style="margin-top:14px">Polls & Surveys</div>'; active.forEach(function(p){ if(p.responded){ h+='<div class="poll-card"><b>'+esc(p.title)+'</b> <span class="cs resolved">Thanks for responding</span></div>'; return; } h+='<div class="poll-card"><b>'+esc(p.title)+'</b>'+(p.description?'<div style="color:var(--muted);font-size:13px;margin:4px 0">'+esc(p.description)+'</div>':'')+(p.questions||[]).map(function(q,qi){ return '<div class="poll-q"><div class="poll-qt">'+esc(q.q||'')+'</div>'+pollInput(p.id,qi,q)+'</div>'; }).join('')+'<button class="btn" style="margin-top:10px" onclick="submitPoll(\''+esc(p.id)+'\')"><i class="ti ti-send"></i> Submit</button></div>'; }); } 1908 h+='<div class="accwrap-head" style="margin-top:24px">Suggestion Box</div><div class="sug-form"><div class="lv-form"><div><label>Category</label><select class="inp" id="sugCat"><option value="general">General</option><option value="workplace">Workplace</option><option value="process">Process</option><option value="tools">Tools</option><option value="culture">Culture</option><option value="other">Other</option></select></div><div style="grid-column:span 2"><label>Title</label><input class="inp" id="sugTitle"></div></div><textarea id="sugMsg" placeholder="Share your ideaâ¦"></textarea><label class="lv-half"><input type="checkbox" id="sugAnon"> Submit anonymously</label><button class="btn" onclick="submitSuggestion()"><i class="ti ti-send"></i> Submit idea</button></div><div style="margin-top:16px">'+renderSuggestions(suggestions)+'</div>'; 1909 el.innerHTML=h; 1910 } 1911 function pollInput(pid,qi,q){ 1912 const kind=String(q.kind||'single'); 1913 if(kind==='rating'){ return '<div class="pulse-scale" data-p="'+esc(pid)+'" data-q="'+qi+'">'+[1,2,3,4,5].map(function(n){ return '<button type="button" class="pulse-dot" onclick="pickRating(this)">'+n+'</button>'; }).join('')+'</div>'; } 1914 if(kind==='text'){ return '<textarea class="inp" id="pa_'+esc(pid)+'_'+qi+'" rows="2"></textarea>'; } 1915 const tp=(kind==='multi'?'checkbox':'radio'); 1916 return (q.options||[]).map(function(o){ return '<label class="poll-opt"><input type="'+tp+'" name="pa_'+esc(pid)+'_'+qi+'" value="'+esc(o)+'"> '+esc(o)+'</label>'; }).join(''); 1917 } 1918 function pickRating(btn){ const wrap=btn.parentNode; wrap.setAttribute('data-val',btn.textContent); [].forEach.call(wrap.children,function(b){b.classList.remove('on');}); btn.classList.add('on'); } 1919 async function submitPoll(pid){ 1920 const p=(_myPolls||[]).find(function(x){return String(x.id)===String(pid);}); if(!p) return; 1921 const answers=(p.questions||[]).map(function(q,qi){ 1922 const kind=String(q.kind||'single'); 1923 if(kind==='rating'){ const w=document.querySelector('.pulse-scale[data-p="'+pid+'"][data-q="'+qi+'"]'); return w?Number(w.getAttribute('data-val')||0):0; } 1924 if(kind==='text'){ const t=document.getElementById('pa_'+pid+'_'+qi); return t?t.value.trim():''; } 1925 if(kind==='multi'){ return [].slice.call(document.querySelectorAll('input[name="pa_'+pid+'_'+qi+'"]:checked')).map(function(c){return c.value;}); } 1926 const r=document.querySelector('input[name="pa_'+pid+'_'+qi+'"]:checked'); return r?r.value:''; 1927 }); 1928 const id=currentId||ssGet('ems_id'); 1929 const d=await api({action:'emsPollRespond', employeeId:id, pollId:pid, answers:JSON.stringify(answers)}); 1930 if(d&&d.status==='success'){ toast('Response submitted \u2713'); loadEngagement(); } else toast((d&&d.message)||'Failed'); 1931 } 1932 async function submitPulse(pid,n){ 1933 const id=currentId||ssGet('ems_id'); 1934 const d=await api({action:'emsPollRespond', employeeId:id, pollId:pid, answers:JSON.stringify([n])}); 1935 if(d&&d.status==='success'){ toast('Thanks for sharing \u2713'); loadEngagement(); } else toast((d&&d.message)||'Failed'); 1936 }
1937 function renderSuggestions(rows){ 1938 if(!rows||!rows.length) return '<div class="empty">No suggestions yet.</div>'; 1939 return rows.map(function(s){ const anon=(s.anonymous===true||String(s.anonymous)==='true'); const voted=s.voted; const st=String(s.status||'new'); return '<div class="sug-item"><button class="sug-vote'+(voted?' on':'')+'" onclick="voteSuggestion(\''+esc(s.id)+'\')"><i class="ti ti-arrow-big-up'+(voted?'-filled':'')+'"></i><span>'+(s.votes||0)+'</span></button><div class="sug-body"><b>'+esc(s.title||'')+'</b> <span class="cs '+(st==='done'?'resolved':(st==='declined'?'':'open'))+'">'+esc(st)+'</span><div class="sug-meta">'+esc(s.category||'')+' \u00b7 '+(anon?'Anonymous':esc(s.name||''))+'</div>'+(s.message?'<div class="sug-msg">'+esc(s.message)+'</div>':'')+(s.admin_note?'<div class="creply"><b>HR:</b> '+esc(s.admin_note)+'</div>':'')+'</div></div>'; }).join(''); 1940 } 1941 async function submitSuggestion(){ 1942 const id=currentId||ssGet('ems_id'); 1943 const title=document.getElementById('sugTitle').value.trim(), msg=document.getElementById('sugMsg').value.trim(); 1944 if(!title){ toast('Add a title'); return; } 1945 const d=await api({action:'emsSuggestionSubmit', employeeId:id, category:document.getElementById('sugCat').value, title:title, message:msg, anonymous:document.getElementById('sugAnon').checked?'true':'false'}); 1946 if(d&&d.status==='success'){ toast('Idea submitted \u2713'); loadEngagement(); } else toast('Failed'); 1947 } 1948 async function voteSuggestion(sid){ 1949 const id=currentId||ssGet('ems_id'); 1950 const d=await api({action:'emsSuggestionVote', employeeId:id, id:sid}); 1951 if(d&&d.status==='success'){ loadEngagement(); } else toast('Failed'); 1952 } 1953 /* ---- Team Directory ---- */ 1954 var _dir=[]; 1955 async function loadDirectory(){ 1956 const id=currentId||ssGet('ems_id'); if(!id) return; 1957 const d=await api({action:'emsDirectory', employeeId:id}); _dir=(d&&d.rows)||[]; renderDirectory(); 1958 } 1959 function renderDirectory(){ 1960 const el=document.getElementById('dirList'); if(!el) return; 1961 const ql=((document.getElementById('dirSearch')||{}).value||'').trim().toLowerCase(); 1962 let rows=_dir; if(ql) rows=_dir.filter(function(e){ return (String(e.name)+' '+String(e.department)+' '+String(e.designation)+' '+String(e.employeeId)).toLowerCase().indexOf(ql)>=0; }); 1963 if(!rows.length){ el.innerHTML='<div class="empty">No colleagues found.</div>'; return; } 1964 const groups={}; rows.forEach(function(e){ const d=e.department||'Other'; (groups[d]=groups[d]||[]).push(e); }); 1965 let h=''; Object.keys(groups).sort().forEach(function(dep){ 1966 h+='<div class="dir-dep">'+esc(dep)+' <span>'+groups[dep].length+'</span></div><div class="dir-grid">'; 1967 h+=groups[dep].map(function(e){ const ini=(String(e.name||'?').trim()[0]||'?').toUpperCase(); return '<div class="dir-card"><div class="dir-av">'+esc(ini)+'</div><div class="dir-info"><b>'+esc(e.name||'')+'</b><span>'+esc(e.designation||'\u2014')+'</span></div></div>'; }).join(''); 1968 h+='</div>'; 1969 }); 1970 el.innerHTML=h; 1971 } 1972 var _attMonth=null, _leaveTypes=[]; 1973 async function renderAssetsCard(){ 1974 const id=currentId||ssGet('ems_id'); const el=document.getElementById('assetsCard'); if(!id||!el) return; 1975 try{ 1976 const d=await api({action:'emsMyAssets', employeeId:id}); 1977 const rows=(d&&d.rows)||[]; if(!d||d.status!=='success'||!rows.length){ el.innerHTML=''; return; } 1978 el.innerHTML='<div class="asset-card"><div class="asset-head"><i class="ti ti-devices"></i> My Assets <span>'+rows.length+'</span></div>'+rows.map(function(a){ const ck=(a.ack===true||String(a.ack)==='true'); const icon={laptop:'ti-device-laptop',phone:'ti-device-mobile',sim:'ti-sim-card',id_card:'ti-id',furniture:'ti-armchair-2',software:'ti-app-window'}[String(a.category)]||'ti-device-desktop'; const cond=String(a.condition||''); return '<div class="asset-item"><i class="ti '+icon+'"></i><div class="asset-info"><b>'+esc(a.name||'')+'</b>'+(a.tag?' <span class="asset-tag">'+esc(a.tag)+'</span>':'')+'<div class="asset-meta">'+esc(cond.charAt(0).toUpperCase()+cond.slice(1))+(a.assigned_on?(' · since '+esc(fmtDate(a.assigned_on)||a.assigned_on)):'')+'</div></div>'+(ck?'<span class="asset-ack done"><i class="ti ti-circle-check-filled"></i> Received</span>':'<button class="asset-ack" onclick="ackAsset(\''+esc(a.id)+'\')">Acknowledge</button>')+'</div>'; }).join('')+'</div>'; 1979 }
1979catch(e){ el.innerHTML=''; } 1980 } 1981 async function ackAsset(aid){ 1982 const emp=currentId||ssGet('ems_id'); 1983 const d=await api({action:'emsAssetAck', employeeId:emp, id:aid}); 1984 if(d&&d.status==='success'){ toast('Receipt acknowledged \u2713'); renderAssetsCard(); } else toast((d&&d.message)||'Failed'); 1985 } 1986 async function renderOnboardCard(){ 1987 const id=currentId||ssGet('ems_id'); const el=document.getElementById('onboardCard'); if(!id||!el) return; 1988 try{ 1989 const d=await api({action:'emsMyOnboarding', employeeId:id}); 1990 const rows=(d&&d.rows)||[]; if(!d||d.status!=='success'||!rows.length){ el.innerHTML=''; return; } 1991 const done=d.done||0, total=d.total||rows.length; if(done>=total){ el.innerHTML=''; return; } 1992 const pct=Math.round(done/Math.max(1,total)*100); 1993 el.innerHTML='<div class="onb-card"><div class="onb-head"><div><div class="onb-title"><i class="ti ti-rocket"></i> Welcome aboard!</div><div class="onb-sub">Your onboarding checklist \u00b7 '+done+' of '+total+' complete</div></div><div class="onb-pct">'+pct+'%</div></div><div class="onb-bar"><span style="width:'+pct+'%"></span></div><div class="onb-steps">'+rows.map(function(r){ const ck=(r.done===true||String(r.done)==='true'); return '<div class="onb-step'+(ck?' done':'')+'"><i class="ti '+(ck?'ti-circle-check-filled':'ti-circle')+'"></i> <span>'+esc(r.task)+'</span></div>'; }).join('')+'</div></div>'; 1994 }catch(e){ el.innerHTML=''; } 1995 } 1996 async function renderPunchCard(){ 1997 // Attendance disabled for this employee â no punch card at all. 1998 if(window._attEnabled===false){ var _pc0=document.getElementById('punchCard'); if(_pc0){ _pc0.innerHTML=''; _pc0.style.display='none'; } return; } 1999 try{ window._onDuty=!!localStorage.getItem('ems_onduty_'+_todayISO()); }catch(_od){} 2000 const id=currentId||ssGet('ems_id'); const el=document.getElementById('punchCard'); if(!id||!el) return; 2001 try{ const d=await api({action:'emsAttToday', employeeId:id}); if(!d||d.status!=='success'){ el.innerHTML=''; return; } 2002 const t=d.today||{}; const state=d.state; 2003 var _pi=(state==='in'&&t.punch_in)?String(t.punch_in).split(':'):null; 2004 window._pinSec=_pi?((+_pi[0])*3600+(+_pi[1]||0)*60+(+_pi[2]||0)):null; 2005 window._pinMin=_pi?((+_pi[0])*60+(+_pi[1]||0)):null; 2006 window._pbreak=Number(t.break_mins)||0; 2007 function _liveWorkSecs(){ if(window._pinSec==null) return (Number(t.work_mins)||0)*60; var now=new Date(); var s=(now.getHours()*3600+now.getMinutes()*60+now.getSeconds())-window._pinSec-window._pbreak*60; return s>0?s:0; } 2008 function _fmtHMS(s){ var h=Math.floor(s/3600),m=Math.floor((s%3600)/60),x=s%60; return (h<10?'0':'')+h+':'+(m<10?'0':'')+m+':'+(x<10?'0':'')+x; } 2009 const work=Math.floor(_liveWorkSecs()/60); const wh=(state==='in')?_fmtHMS(_liveWorkSecs()):((Math.floor(work/60))+'h '+(work%60)+'m'); 2010 const pin=esc(t.punch_in?fmt12(t.punch_in):'\u2014'), pout=esc(t.punch_out?fmt12(t.punch_out):'\u2014'), pct=Number(d.pct)||0; 2011 let badge, action; 2012 if(state==='none'){ badge='<span class="p2-badge wait">Not started</span>'; action='<button class="p2-btn in" onclick="doPunch()"><i class="ti ti-fingerprint"></i> Punch In</button>'; } 2013 else if(state==='in'){ badge='<span class="p2-badge live"><span class="p2-dot"></span> Working</span>'; action='<div class="p2-actions"><button class="p2-btn out" onclick="doPunch()"><i class="ti ti-door-exit"></i> Punch Out</button><button class="p2-duty'+(window._onDuty?' on':'')+'" onclick="toggleDuty()"><i class="ti ti-map-pin"></i> '+(window._onDuty?'On field duty':'On Duty')+'</button></div>'; } 2014 else { badge='<span class="p2-badge done"><i class="ti ti-circle-check-filled"></i> Done for today</span>'; action=''; } 2015 el.innerHTML='<div class="punch2"><div class="p2-head"><span class="p2-title"><i class="ti ti-clock-check"></i> Today\u2019s Attendance</span>'+badge+'</div>' 2016 +'<div class="p2-times"><div class="p2-time in"><span>IN</span><b>'+pin+'</b></div><div class="p2-sep"><i class="ti ti-arrow-right"></i></div><div class="p2-time out"><span>OUT</span><b>'+pout+'</b></div><div class="p2-time worked'+(state==='in'?' live':'')+'"><span>WORKED'+(state==='in'?' <b style="color:#34d399;font-size:8px">\u25cf LIVE</b>':'')+'</span><b id="p2Worked">'+wh+'</b></div></div>' 2017 +'<div class="p2-foot"><div class="p2-month"><div class="p2-mlbl">This month <b>'+pct+'%</b></div><div class="p2-bar"><span style="width:'+pct+'%"></span></div></div>'+action+'</div></div>';
2018 try{ clearInterval(window._workTick); }catch(_wt){} 2019 if(state==='in'){ window._workTick=setInterval(function(){ var wb=document.getElementById('p2Worked'); if(!wb||window._pinSec==null){ try{clearInterval(window._workTick);}catch(_){} return; } var now=new Date(); var s=(now.getHours()*3600+now.getMinutes()*60+now.getSeconds())-window._pinSec-window._pbreak*60; if(s<0)s=0; var h=Math.floor(s/3600),m=Math.floor((s%3600)/60),x=s%60; wb.textContent=(h<10?'0':'')+h+':'+(m<10?'0':'')+m+':'+(x<10?'0':'')+x; }, 1000); } 2020 try{ const nw=await api({action:'emsMyIp', employeeId:id}); if(nw&&nw.status==='success'){ window._attSec=nw; } 2021 if(nw&&nw.status==='success'){ const host=el.querySelector('.punch2'); 2022 if(host && nw.lock){ const w=document.createElement('div'); w.className='net-status'; w.innerHTML= nw.office ? '<div class="net-ok"><i class="ti ti-wifi"></i> Office network verified'+(nw.location?' \u00b7 '+esc(nw.location):'')+'</div>' : '<div class="net-warn"><i class="ti ti-camera"></i> Off office network \u2014 a live selfie is needed to punch (HR is notified)</div>'; host.appendChild(w); if(!window._netTimer){ window._netTimer=setInterval(netCheck,120000); } } 2023 if(host && nw.geoLock){ _geoStatusInto(host, nw.geoPoints||[]); } 2024 if(host && nw.faceLock){ try{ _faceInit(); }catch(_){} 2025 var fw=document.createElement('div'); fw.className='net-status'; fw.style.marginTop='6px'; 2026 if(nw.faceRegistered){ fw.innerHTML='<div class="net-ok"><i class="ti ti-user-check"></i> Face ID active â your face is verified on every punch</div>'; } 2027 else { fw.innerHTML='<div class="net-warn" style="cursor:pointer" onclick="startFaceSetup()"><i class="ti ti-user-scan"></i> Set up your Face ID once to enable punching â tap here</div>'; } 2028 host.appendChild(fw); 2029 } 2030 } 2031 }catch(_n){} 2032 }catch(e){ el.innerHTML=''; } 2033 } 2034 function emsDeviceId(){ var d=null; try{ d=localStorage.getItem('ems_device'); }catch(_){} if(!d){ d='DEV-'+Math.random().toString(36).slice(2,10)+Date.now().toString(36); try{ localStorage.setItem('ems_device',d); }catch(_){} } return d; } 2035 // ===== GPS geo-fence helpers ===== 2036 function _distM(la1,lo1,la2,lo2){ var R=6371000,tR=function(d){return d*Math.PI/180;}; var dLa=tR(la2-la1),dLo=tR(lo2-lo1); var a=Math.sin(dLa/2)*Math.sin(dLa/2)+Math.cos(tR(la1))*Math.cos(tR(la2))*Math.sin(dLo/2)*Math.sin(dLo/2); return Math.round(R*2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a))); } 2037 function _getGeo(){ return new Promise(function(res,rej){ if(!navigator.geolocation){ rej(new Error('This phone does not support GPS location.')); return; } navigator.geolocation.getCurrentPosition(function(p){ res({lat:p.coords.latitude,lng:p.coords.longitude,acc:p.coords.accuracy}); }, function(err){ rej(new Error((err&&err.code===1)?'Location permission denied â allow Location for the portal, then punch.':'Could not get your location â turn on GPS and try again.')); }, {enableHighAccuracy:true,timeout:12000,maximumAge:0}); }); } 2038 function _geoStatusInto(host, points){ 2039 if(!host) return; var w=document.createElement('div'); w.className='net-status'; w.style.marginTop='6px'; 2040 w.innerHTML='<div class="net-warn"><i class="ti ti-map-pin"></i> Checking your locationâ¦</div>'; host.appendChild(w); 2041 if(!points||!points.length){ w.innerHTML='<div class="net-warn"><i class="ti ti-map-pin-exclamation"></i> Office location not set yet â ask HR.</div>'; return; } 2042 _getGeo().then(function(g){ window._punchGeo=g; var best=null; points.forEach(function(p){ var d=_distM(g.lat,g.lng,p.lat,p.lng); if(best===null||d<best.d) best={d:d,r:Number(p.radius)||150,n:p.name}; }); 2043 if(best.d<=best.r) w.innerHTML='<div class="net-ok"><i class="ti ti-map-pin-check"></i> At '+esc(best.n)+' · '+best.d+' m (within '+best.r+' m)</div>'; 2044 else w.innerHTML='<div class="net-bad"><i class="ti ti-map-pin-off"></i> '+best.d+' m from '+esc(best.n)+' â outside office area ('+best.r+' m)<div style="font-size:11px;opacity:.85;margin-top:2px">Your location: '+g.lat.toFixed(5)+', '+g.lng.toFixed(5)+'</div></div>'; 2045 }).catch(function(e){ w.innerHTML='<div class="net-warn"><i class="ti ti-map-pin-exclamation"></i> '+esc((e&&e.message)||'Turn on location (GPS) to punch')+'</div>'; }); 2046 } 2047 async function doPunch(){ 2048 const id=currentId||ssGet('ems_id'); if(!id) return; 2049 const sec=window._attSec||{}; 2050 if(sec.faceLock && !sec.faceRegistered){ toast('Set up your Face ID first'); startFaceSetup(); return; } 2051 if(sec.geoLock){
2052 try{ window._punchGeo=await _getGeo(); } 2053 catch(e){ toast((e&&e.message)||'Location needed â turn on GPS and allow it'); return; } 2054 } else { window._punchGeo=null; } 2055 if(sec.selfie || sec.faceLock){ window._selfieMode='punch'; openSelfie(); return; } 2056 sendPunch(id,''); 2057 } 2058 function _reSyncPunch(){ renderPunchCard(); const a=document.getElementById('attendance'); if(a&&a.classList.contains('show')) loadAttendance(); } 2059 async function sendPunch(id,photo){ 2060 // Debounce: ignore a second tap while a punch is already in flight â stops a double-tap from firing two 2061 // concurrent punches (which raced into a duplicate-key error on the server). 2062 if(window._punchBusy) return; window._punchBusy=true; 2063 try{ 2064 var _g=window._punchGeo||{}; var _fd=window._punchFaceDesc; 2065 const d=await Promise.race([ 2066 api({action:'emsAttPunch', employeeId:id, token:ssGet('ems_tok'), device:emsDeviceId(), photo:photo||'', lat:(_g.lat!=null?_g.lat:''), lng:(_g.lng!=null?_g.lng:''), faceDesc:(_fd?JSON.stringify(_fd):'')}), 2067 new Promise(function(_,rej){ setTimeout(function(){ rej(new Error('timeout')); }, 15000); }) 2068 ]); 2069 if(d&&d.status==='success'){ toast(d.state==='in'?'Punched in \u2713':(d.state==='out'?'Punched out \u2713':'Done \u2713')); _reSyncPunch(); } 2070 else if(d&&d.geoBlocked){ toast((d.message)||'You are outside the office area'); try{ alert((d.message)||'You are outside the office area.'); }catch(_al){} } 2071 else if(d&&d.faceSetup){ toast((d.message)||'Set up your Face ID first'); startFaceSetup(); } 2072 else if(d&&d.faceFail){ toast((d.message)||'Face did not match'); try{ alert((d.message)||'Face did not match the registered employee.'); }catch(_al){} } 2073 else if(d&&d.selfieRequired){ toast('Selfie needed \u2014 please try again'); } 2074 else if(d&&d.ipBlocked){ toast((d.message)||'Not on office network'); } 2075 else if(d&&d.deviceBlocked){ toast((d.message)||'This device is not registered'); } 2076 else { var _m=(d&&d.message)||'Punch failed \u2014 please try again'; toast(_m); try{ alert('Punch could not complete:\n\n'+_m); }catch(_al){} } 2077 }catch(e){ 2078 // slow connection: the punch may already be saved on the server â re-check quietly 2079 toast('Finishing up\u2026'); setTimeout(_reSyncPunch, 2000); 2080 }finally{ window._punchBusy=false; } 2081 } 2082 var _selfieStream=null; 2083 function _selfieMsg(t,ok){ var e=document.getElementById('selfieState'); if(e){ e.textContent=t||''; e.style.color=ok?'#7ee0a8':'#ffb0a2'; } } 2084 // Switch the selfie sheet into a clean "use your phone camera" state \u2014 no broken black box, native photo 2085 // is the primary action (works everywhere, even where the live camera is blocked or the page isn't HTTPS). 2086 function _selfieFallback(){ 2087 var v=document.getElementById('selfieVid'); if(v){ v.style.display='none'; } 2088 var ph=document.getElementById('selfiePh'); if(ph){ ph.style.display='flex'; } 2089 var cap=document.getElementById('selfieCapBtn'); if(cap){ cap.style.display='none'; } 2090 _selfieMsg(''); 2091 var nb=document.getElementById('selfieNativeBtn'); 2092 if(nb){ nb.style.borderColor='var(--gold-soft)'; nb.style.background='var(--gold-soft)'; nb.style.color='#1a1305'; nb.style.marginTop='4px'; nb.style.fontSize='15px'; nb.style.padding='13px'; nb.innerHTML='<i class="ti ti-camera"></i> '+((window._selfieMode==='register')?'Take a selfie to set up':'Take photo with camera'); } 2093 } 2094 // Camera UI state: 'starting' | 'live' | 'error'. When 'error', the main button retries the live camera. 2095 var _selfieCamState='live'; 2096 // Open the front camera trying several constraint sets \u2014 some phones / in-app WebViews reject the strict 2097 // {facingMode:'user'} but accept {video:true}. First match wins; only if ALL fail do we show the retry state. 2098 function _getSelfieCam(){ 2099 var tries=[ 2100 {video:{facingMode:'user'},audio:false}, 2101 {video:{facingMode:{ideal:'user'}},audio:false}, 2102 {video:true,audio:false} 2103 ]; 2104 return new Promise(function(resolve,reject){ 2105 var i=0, lastErr=null; 2106 (function next(){ 2107 if(i>=tries.length){ reject(lastErr||new Error('no camera')); return; } 2108 var c=tries[i++]; 2109 navigator.mediaDevices.getUserMedia(c).then(resolve).cat
2109ch(function(e){ lastErr=e; next(); }); 2110 })(); 2111 }); 2112 } 2113 function _selfieCamError(err){ 2114 _selfieCamState='error'; 2115 var name=(err&&err.name)||''; 2116 var v=document.getElementById('selfieVid'); if(v){ v.style.display='none'; } 2117 var ph=document.getElementById('selfiePh'); if(ph){ ph.style.display='flex'; var t=ph.querySelector('.selfie-ph-t'); if(t) t.textContent='Camera needs permission'; } 2118 var cap=document.getElementById('selfieCapBtn'); 2119 // Keep the LIVE CAMERA as the primary action \u2014 the main button becomes "Allow camera & retry". 2120 if(cap){ cap.style.display=''; cap.disabled=false; cap.style.opacity='1'; cap.innerHTML='<i class="ti ti-refresh"></i> Allow camera & retry'; } 2121 var secure=(typeof window.isSecureContext==='undefined')?true:window.isSecureContext; 2122 var msg = (!secure) ? 'For the camera, open this page on https (not http).' 2123 : (name==='NotAllowedError'||name==='SecurityError'||name==='PermissionDeniedError') ? 'Allow camera access when your phone asks, then tap \u201cAllow camera & retry\u201d.' 2124 : (name==='NotFoundError'||name==='DevicesNotFoundError') ? 'No camera found on this device.' 2125 : (name==='NotReadableError'||name==='TrackStartError') ? 'Camera is busy in another app \u2014 close it, then tap retry.' 2126 : 'Could not open the camera \u2014 tap \u201cAllow camera & retry\u201d.'; 2127 _selfieMsg(msg); 2128 // Secondary, low-emphasis fallback: take a photo with the phone camera app. 2129 var nb=document.getElementById('selfieNativeBtn'); if(nb){ nb.style.borderColor='rgba(255,255,255,.22)'; nb.style.background='none'; nb.style.color='var(--muted)'; nb.style.marginTop='9px'; nb.style.fontSize='13px'; nb.style.padding='11px'; nb.innerHTML='<i class="ti ti-camera-plus"></i> Or take a photo with your camera'; } 2130 } 2131 function openSelfie(){ 2132 var ov=document.getElementById('selfieOverlay'); if(!ov)return; ov.classList.add('on'); 2133 var cap=document.getElementById('selfieCapBtn'); _selfieMsg(''); 2134 _selfieCamState='starting'; 2135 // stop any previous stream before restarting (retry) 2136 if(_selfieStream){ try{ _selfieStream.getTracks().forEach(function(t){t.stop();}); }catch(_){} _selfieStream=null; } 2137 var v0=document.getElementById('selfieVid'); if(v0){ v0.style.display=''; } 2138 var ph0=document.getElementById('selfiePh'); if(ph0){ ph0.style.display='none'; } 2139 if(cap){ cap.style.display=''; cap.disabled=true; cap.style.opacity='1'; cap.innerHTML='<i class="ti ti-camera"></i> Starting camera\u2026'; } 2140 var nb0=document.getElementById('selfieNativeBtn'); if(nb0){ nb0.style.borderColor='rgba(255,255,255,.22)'; nb0.style.background='none'; nb0.style.color='var(--muted)'; nb0.style.marginTop='9px'; nb0.style.fontSize='13px'; nb0.style.padding='11px'; nb0.innerHTML='<i class="ti ti-camera-plus"></i> Camera not opening? Take photo instead'; } 2141 if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia){ _selfieCamError({name:'NotFoundError'}); return; } 2142 _getSelfieCam().then(function(st){ 2143 _selfieStream=st; var v=document.getElementById('selfieVid'); 2144 if(!v){ _selfieMsg('Camera view missing.'); return; } 2145 if(v.style.display==='none') v.style.display=''; 2146 var ph=document.getElementById('selfiePh'); if(ph) ph.style.display='none'; 2147 v.srcObject=st; v.setAttribute('playsinline',''); v.muted=true; var pp=v.play(); if(pp&&pp.catch)pp.catch(function(){}); 2148 var tries=0; var iv=setInterval(function(){ tries++; 2149 if(v.videoWidth>0){ clearInterval(iv); _selfieCamState='live'; _selfieMsg(''); if(cap){ cap.disabled=false; cap.innerHTML=_selfieCapText(); } } 2150 else if(tries>50){ clearInterval(iv); _selfieCamState='live'; _selfieMsg('Camera slow to start \u2014 tap Capture to try anyway.'); if(cap){ cap.disabled=false; cap.innerHTML=_selfieCapText(); } } 2151 },100); 2152 }).catch(function(err){ _selfieCamError(err); }); 2153 } 2154 function selfieNativePick(){ var fi=document.getElementById('selfieFileInput'); if(fi){ fi.value=''; fi.click(); } } 2155 function selfieFilePicked(inp){ 2156 var f=inp.files&&inp.files[0]; if(!f){ return; } 2157 _selfieMsg('Reading photo\u2026', true); 2158 var r=new FileReader(); 2159 r.onload=function(){ 2160 var img=new Image(); 2161 img.onload=function(){ 2162 try{ 2163 var cv=_cvFrom(img); 2164 var photo=cv.toDataURL('image/jpeg',0.82); 2165 if(!photo||photo.length<200){ _selfieMsg('Could not read the photo. Please try again.'); return; } 2166 _faceHandleCanvas(cv, photo); 2167 }catch(e){ _selfieMsg('Photo error: '+((e&&e.message)||e)); } 2168 }; 2169 img.onerror=function(){ _selfieMsg('Could not open the photo. Please try again.'); }; 2170 img.src=String(r.result||''); 2171 }; 2172 r.onerror=function(){ _selfieMsg('Could not read the photo. Please try again.'); };
2173 r.readAsDataURL(f); 2174 } 2175 function closeSelfie(){ var ov=document.getElementById('selfieOverlay'); if(ov)ov.classList.remove('on'); if(_selfieStream){ try{ _selfieStream.getTracks().forEach(function(t){t.stop();}); }catch(_){} _selfieStream=null; } var fi=document.getElementById('selfieFileInput'); if(fi)fi.value=''; } 2176 function _doCapture(v){ 2177 try{ 2178 var cv=_cvFrom(v); 2179 var photo=cv.toDataURL('image/jpeg',0.82); 2180 if(!photo||photo.length<200){ _selfieMsg('Could not read the camera frame. Reopen and try again.'); return; } 2181 _faceHandleCanvas(cv, photo); 2182 }catch(e){ _selfieMsg('Capture error: '+((e&&e.message)||e)); } 2183 } 2184 function captureSelfie(){ 2185 // In the error state this button re-opens the live camera (asks permission again) instead of capturing. 2186 if(_selfieCamState==='error'){ openSelfie(); return; } 2187 var v=document.getElementById('selfieVid'); if(!v){ _selfieMsg('Camera not found.'); return; } 2188 if(v.videoWidth>0){ _doCapture(v); return; } 2189 _selfieMsg('Camera warming up\u2026'); var pp=v.play(); if(pp&&pp.catch)pp.catch(function(){}); 2190 setTimeout(function(){ if(v.videoWidth>0){ _doCapture(v); } else { _selfieMsg('Camera not ready. Close, reopen, and allow camera permission.'); } }, 800); 2191 } 2192 // ===================== Face ID (register + verify) ===================== 2193 var FACE_SCRIPTS=['https://cdn.jsdelivr.net/npm/@vladmandic/face-api/dist/face-api.js','https://unpkg.com/@vladmandic/face-api/dist/face-api.js']; 2194 var FACE_MODEL_URLS=['https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model','https://cdn.jsdelivr.net/gh/vladmandic/face-api/model','https://unpkg.com/@vladmandic/face-api/model']; 2195 var _faceLib=null, _faceReady=false, _faceLoading=null; 2196 function _loadScript(src){ return new Promise(function(res,rej){ var s=document.createElement('script'); s.src=src; s.onload=function(){res();}; s.onerror=function(){ rej(new Error('load failed')); }; document.head.appendChild(s); }); } 2197 async function _loadFaceLib(){ if(window.faceapi) return; var last=null; for(var i=0;i<FACE_SCRIPTS.length;i++){ try{ await _loadScript(FACE_SCRIPTS[i]); if(window.faceapi) return; }catch(e){ last=e; } } if(!window.faceapi) throw (last||new Error('face lib')); } 2198 async function _loadFaceModels(f){ var last=null; for(var u=0;u<FACE_MODEL_URLS.length;u++){ try{ var M=FACE_MODEL_URLS[u]; await f.nets.tinyFaceDetector.loadFromUri(M); await f.nets.faceLandmark68Net.loadFromUri(M); await f.nets.faceRecognitionNet.loadFromUri(M); return; }catch(e){ last=e; } } throw (last||new Error('models')); } 2199 function _faceInit(){ 2200 if(_faceReady) return Promise.resolve(); 2201 if(_faceLoading) return _faceLoading; 2202 _faceLoading=(async function(){ 2203 await _loadFaceLib(); 2204 var f=window.faceapi; if(!f) throw new Error('face lib'); 2205 await _loadFaceModels(f); 2206 _faceLib=f; _faceReady=true; 2207 })().catch(function(e){ _faceLoading=null; throw e; }); 2208 return _faceLoading; 2209 } 2210 function _faceDistJs(a,b){ if(!a||!b||a.length!==b.length) return 99; var s=0; for(var i=0;i<a.length;i++){ var d=a[i]-b[i]; s+=d*d; } return Math.sqrt(s); } 2211 // Detect faces in a canvas \u2192 single clear face + its 128-descriptor, or a clear reason why not. 2212 async function _faceCheck(cv){ 2213 try{ await _faceInit(); }catch(e){ return {ok:false,msg:'Face check could not load \u2014 check your internet and try again.'}; } 2214 var f=_faceLib; 2215 var opts=new f.TinyFaceDetectorOptions({inputSize:320,scoreThreshold:0.4}); 2216 var all; 2217 try{ all=await f.detectAllFaces(cv,opts).withFaceLandmarks().withFaceDescriptors(); }catch(e){ return {ok:false,msg:'Could not read the photo. Try again in good light.'}; } 2218 if(!all||all.length===0) return {ok:false,msg:'No face detected \u2014 hold the phone in front of your face, remove any cover, and use good light.'}; 2219 if(all.length>1) return {ok:false,msg:'More than one person in the photo \u2014 only you should be in the frame.'}; 2220 var d=all[0]; 2221 if(d.detection.score<0.5) return {ok:false,msg:'Face not clear \u2014 remove mask/cover and face the camera in good light.'}; 2222 var box=d.detection.box, area=(box.width*box.height)/((cv.width*cv.height)||1); 2223 if(area<0.04) return {ok:false,msg:'Move closer \u2014 your face is too small in the frame.'}; 2224 return {ok:true, descriptor:Array.prototype.slice.call(d.descriptor)}; 2225 } 2226 function _cvFrom(vOrImg){ var w=Math.min((vOrImg.videoWidth||vOrImg.naturalWi
2226dth||vOrImg.width),640)||640; var ratio=((vOrImg.videoHeight||vOrImg.naturalHeight||vOrImg.height)/((vOrImg.videoWidth||vOrImg.naturalWidth||vOrImg.width)||1))||1; var h=Math.round(w*ratio)||640; var cv=document.createElement('canvas'); cv.width=w; cv.height=h; cv.getContext('2d').drawImage(vOrImg,0,0,w,h); return cv; } 2227 function _selfieCapText(){ return (window._selfieMode==='register')?'<i class="ti ti-camera"></i> Capture face':'<i class="ti ti-camera"></i> Capture & Punch'; } 2228 function startFaceSetup(){ window._selfieMode='register'; try{ _faceInit(); }catch(_){} openSelfie(); var t=document.getElementById('selfieTitle'); if(t)t.innerHTML='<i class="ti ti-user-scan"></i> Set up your Face ID'; var s=document.getElementById('selfieSub'); if(s)s.textContent='One-time setup. Face the camera in good light, no mask or cover, then tap Capture.'; var b=document.getElementById('selfieCapBtn'); if(b)b.innerHTML='<i class="ti ti-camera"></i> Capture face'; } 2229 // Shared handler for a captured frame (video/image) \u2192 register OR verify+punch. 2230 async function _faceHandleCanvas(cv, photo){ 2231 var sec=window._attSec||{}; var mode=window._selfieMode||'punch'; 2232 if(mode==='register'){ 2233 _selfieMsg('Checking your face\u2026', true); 2234 var chk=await _faceCheck(cv); if(!chk.ok){ _selfieMsg(chk.msg); return; } 2235 _selfieMsg('Saving your Face ID\u2026', true); 2236 var id=currentId||ssGet('ems_id');
2237 try{ var d=await api({action:'emsFaceRegister', employeeId:id, token:ssGet('ems_tok'), descriptor:JSON.stringify(chk.descriptor)}); 2238 if(d&&d.status==='success'){ closeSelfie(); toast('Face ID saved \u2713 \u2014 you can punch now'); if(window._attSec) window._attSec.faceRegistered=true; renderPunchCard(); } 2239 else _selfieMsg((d&&d.message)||'Could not save Face ID. Try again.'); 2240 }catch(e){ _selfieMsg('Network issue while saving \u2014 try again.'); } 2241 return; 2242 } 2243 // punch mode 2244 if(sec.faceLock){ 2245 _selfieMsg('Verifying your face\u2026', true); 2246 var chk2=await _faceCheck(cv); if(!chk2.ok){ _selfieMsg(chk2.msg); return; } 2247 if(sec.faceDescriptor){ var dist=_faceDistJs(chk2.descriptor, sec.faceDescriptor); if(dist>0.52){ _selfieMsg('Face does not match your registered Face ID \u2014 only you can punch.'); return; } } 2248 window._punchFaceDesc=chk2.descriptor; 2249 } else { window._punchFaceDesc=null; } 2250 var id2=currentId||ssGet('ems_id'); closeSelfie(); toast('Verifying\u2026'); sendPunch(id2, photo); 2251 } 2252 function toggleDuty(){ window._onDuty=!window._onDuty; try{ var k='ems_onduty_'+_todayISO(); if(window._onDuty)localStorage.setItem(k,'1'); else localStorage.removeItem(k); }catch(_){} toast(window._onDuty?'On field duty \u2014 you won\u2019t be auto punched-out.':'Field duty turned off.'); window._netOff=0; renderPunchCard(); } 2253 async function netCheck(){ 2254 const live=document.querySelector('#punchCard .p2-badge.live'); if(!live){ window._netWarned=false; window._netOff=0; return; } 2255 if(window._onDuty) return; 2256 const id=currentId||ssGet('ems_id'); if(!id) return; 2257 try{ const nw=await api({action:'emsMyIp', employeeId:id}); 2258 if(nw&&nw.status==='success'&&nw.lock&&!nw.office&&!nw.offNet){ 2259 window._netOff=(window._netOff||0)+1; 2260 if(window._netOff===1){ toast('\u26a0 You left office premises. On field duty? Tap \u201cOn Duty\u201d \u2014 otherwise you\u2019ll be auto punched-out shortly.'); } 2261 else if(window._netOff>=2){ window._netOff=0; try{ const d=await api({action:'emsAttPunch', employeeId:id, token:ssGet('ems_tok'), auto:'true'}); if(d&&d.state==='out'){ toast('Auto punched-out \u2014 you left office premises.'); renderPunchCard(); } }catch(_a){} } 2262 } else { window._netOff=0; window._netWarned=false; } 2263 }catch(_n){} 2264 } 2265 async function loadAttendance(){ 2266 const id=currentId||ssGet('ems_id'); if(!id) return; 2267 if(!_attMonth) _attMonth=new Date().toISOString().slice(0,7); 2268 try{ 2269 const d=await api({action:'emsAttMy', employeeId:id, month:_attMonth}); 2270 if(!d||d.status!=='success'){ var ac0=document.getElementById('attCalendar'); if(ac0)ac0.innerHTML='<div class="empty">Couldnât load attendance. Tap refresh and try again.</div>'; return; } 2271 loadRegMine(); 2272 renderAttCalendar(_attMonth, d.rows||[], d.holidays||[], d.summary||{}, d.weekOffDays||[]); 2273 }catch(e){ var ac=document.getElementById('attCalendar'); if(ac)ac.innerHTML='<div class="empty">Couldnât load attendance. Check your connection and tap refresh.</div>'; } 2274 } 2275 function attMonth(dir){ 2276 const parts=(_attMonth||new Date().toISOString().slice(0,7)).split('-').map(Number); 2277 const dt=new Date(parts[0],parts[1]-1+dir,1); 2278 if(dt>new Date()) return; 2279 _attMonth=dt.getFullYear()+'-'+String(dt.getMonth()+1).padStart(2,'0'); loadAttendance(); 2280 } 2281 function renderAttCalendar(ym, rows, holidays, summary, weekOffDays){ 2282 window.__attLast={ym:ym,rows:rows,holidays:holidays,summary:summary,weekOffDays:weekOffDays}; 2283 var woSet=new Set(weekOffDays||[]); 2284 const p=ym.split('-').map(Number), y=p[0], m=p[1]; 2285 const lbl=document.getElementById('acalLabel'); if(lbl) lbl.textContent=new Date(y,m-1,1).toLocaleString('en-IN',{month:'long',year:'numeric'}); 2286 const byDate={}; rows.forEach(r=>byDate[r.date]=r); 2287 const holByDate={}; (holidays||[]).forEach(h=>holByDate[h.date]=h); 2288 window.__attByDate=byDate; window.__holByDate=holByDate; window.__attWoSet=woSet; 2289 const today=_todayISO(); 2290 const days=new Date(y,m,0).getDate(), startDow=new Date(y,m-1,1).getDay();
2291 let cells=['S','M','T','W','T','F','S'].map(d=>'<div class="acal-dow">'+d+'</div>').join(''); 2292 for(let i=0;i<startDow;i++) cells+='<div class="acal-cell empty"></div>'; 2293 for(let d=1;d<=days;d++){ 2294 const ds=ym+'-'+String(d).padStart(2,'0'); const rec=byDate[ds], hol=holByDate[ds]; 2295 let cls='none'; 2296 if(rec) cls=(String(rec.status)==='early'?'present':String(rec.status)); // early leave shows as a present (green) day 2297 else if(hol && hol.type==='holiday') cls='holiday'; 2298 else if(hol && hol.type==='weekoff') cls='weekoff'; 2299 else if(hol && hol.type==='working') cls=(ds<today?'absent':(ds>today?'future':'none')); 2300 else if(woSet.has(new Date(y,m-1,d).getDay())) cls='weekoff'; 2301 else if(ds<today) cls='absent'; 2302 else if(ds>today) cls='future'; 2303 const tip=ds+(rec&&rec.punch_in?(' \u00b7 '+fmt12(rec.punch_in)+(rec.punch_out?('\u2013'+fmt12(rec.punch_out)):'')):'')+(hol?(' \u00b7 '+(hol.name||'Holiday')):''); 2304 var _rg=(window.__regByDate||{})[ds]; var _rgDot=_rg?('<i class="acal-reg '+esc(_rg.status)+'" title="Regularization '+esc(_rg.status)+'"></i>'):''; 2305 var _inc=(rec&&rec.punch_in&&!rec.punch_out&&ds<today)&&!(_rg&&(_rg.status==='pending'||_rg.status==='approved')); 2306 var _warn=_inc?'<span class="acal-warn" title="Punch out missing \u2014 tap to add">!</span>':''; 2307 cells+='<div class="acal-cell '+cls+(_inc?' incomplete':'')+(ds===today?' tdy':'')+'" title="'+esc(tip)+(_inc?' \u00b7 Punch out missing':'')+'" onclick="showAttDay(\''+ds+'\')"><span>'+d+'</span>'+_warn+_rgDot+'</div>'; 2308 } 2309 document.getElementById('attCalendar').innerHTML='<div class="acal-grid">'+cells+'</div>'; 2310 const s=summary||{}; 2311 document.getElementById('attSummary').innerHTML='<div class="att-stats"><div class="att-stat present"><b>'+(s.present||0)+'</b><span>Present</span></div><div class="att-stat half"><b>'+(s.half||0)+'</b><span>Half</span></div><div class="att-stat leave"><b>'+(s.leave||0)+'</b><span>Leave</span></div><div class="att-stat late"><b>'+(s.late||0)+'</b><span>Late</span></div></div>'; 2312 } 2313 function _hmNum(s){ var m=String(s||'').match(/(\d{1,2}):(\d{2})/); return m?((+m[1])*60+(+m[2])):-1; } 2314 function _wmFromRec(rec){ if(!rec) return 0; var im=_hmNum(rec.punch_in),om=_hmNum(rec.punch_out); if(im>=0&&om>=0&&om>=im){ var w=om-im-(Number(rec.break_mins)||0); return w>0?w:0; } return Number(rec.work_mins)||0; } 2315 function showAttDay(ds){ 2316 var rec=(window.__attByDate||{})[ds], hol=(window.__holByDate||{})[ds], woSet=window.__attWoSet||new Set(); 2317 var dt=new Date(ds+'T00:00:00'); var nice=isNaN(+dt)?ds:dt.toLocaleDateString('en-IN',{weekday:'long',day:'numeric',month:'long',year:'numeric'}); 2318 var today=_todayISO(); 2319 var st=''; 2320 if(rec) st=String(rec.status||''); 2321 else if(hol&&hol.type==='holiday') st='holiday'; 2322 else if(hol&&hol.type==='weekoff') st='weekoff'; 2323 else if(woSet.has(dt.getDay())) st='weekoff'; 2324 else if(ds<today) st='absent'; 2325 else if(ds>today) st='upcoming'; 2326 var stLabel={present:'Present',half:'Half Day',early:'Early Leave',leave:'On Leave',holiday:'Holiday',weekoff:'Week Off',absent:'Absent',upcoming:'Upcoming'}[st]||'No record'; 2327 var stCol={present:'#7fe0a8',half:'#f3d27a',early:'#7fe0a8',leave:'#8fb6f0',holiday:'#c6a3ff',absent:'#ff9c8b',weekoff:'#a9b6c9',upcoming:'#7c889a'}[st]||'#a9b6c9'; 2328 var _inc=!!(rec&&rec.punch_in&&!rec.punch_out&&ds<today); 2329 var r=[]; 2330 if(rec&&rec.punch_in) r.push(['Punch In',fmt12(rec.punch_in)]); 2331 if(rec&&rec.punch_out) r.push(['Punch Out',fmt12(rec.punch_out)]); 2332 else if(_inc) r.push(['Punch Out','__MISS__']); 2333 if(rec){ var wm=_wmFromRec(rec); if(wm>0) r.push(['Work Hours',Math.floor(wm/60)+'h '+(wm%60)+'m']); } 2334 if(rec&&Number(rec.late_mins)>0) r.push(['Late By',Number(rec.late_mins)+' min']); 2335 if(hol&&hol.name) r.push(['Note',hol.name]); 2336 else if(rec&&rec.note) r.push(['Note',rec.note]); 2337 var _warnBlk=_inc?'<div class="attd-warn"><i class="ti ti-alert-triangle-filled"></i><div><b>Punch out missing</b><span>You punched in but didn\u2019t punch out on this day. Add your correct punch-out time below \u2014 it goes to HR for approval.</span></div></div>':''; 2338 var body=_warnBlk+(r.length?r.map(function(x){var miss=(x[1]==='__MISS__');return '<div class="attd-row"><span>'+esc(x[0])+'</span><b'+(miss?' style="color:#f3902f"':'')+'>'+(miss?'<i class="ti ti-alert-triangle-filled" style="font-size:12.5px;vertical-align:-1px"></i> Missing':esc(String(x[1])))+'</b></div>';}).join(''):'<div class="attd-empty">'+(st==='upcoming'?'This day is yet to come.':((st==='present'||st==='half')?'No punch time recorded for this day.':'No attendance recorded for this day.'))+'</div>'); 2339 // Regularization: show any request status for this day, and offer "Add Punch" on a missed/absent past day. 2340 var reg=(window.__regByDate||{})[ds]; 2341 var regBlk=''; 2342 if(reg){ var rc={pending:'#f3b04f',approved:'#7fe0a8',rejected:'#ff9c8b'}[reg.status]||'#a9b6c9'; var rl={pending:'Pending with HR',approved:'Approved',rejected:'Rejected'}[reg.status]||reg.status; 2343 regBlk='<div class="reg-status" style="border-color:'+rc+'55"><div class="reg-status-h"><i class="ti ti-clock-hour-4" style="color:'+rc+'"></i> Regularization <span style="color:'+rc+';font-weight:800">'+esc(rl)+'</span></div><div class="reg-status-b">Requested: '+(reg.punchIn?('In '+fmt12(reg.punchIn)):'')+(reg.punchOut?((reg.punchIn?' · ':'')+'Out '+fmt12
2343(reg.punchOut)):'')+(reg.reason?('<br><span class="reg-reason">â'+esc(reg.reason)+'â</span>'):'')+(reg.status==='rejected'&®.decisionNote?('<br><span class="reg-reason">HR: '+esc(reg.decisionNote)+'</span>'):'')+'</div></div>'; 2344 } 2345 // Add Punch / correction stays available on ANY past-or-today day (Petpooja-style) â the employee 2346 // can always request a correction; hidden only on future days or when a request is already pending. 2347 var canReg=(ds<=today)&&!(reg&®.status==='pending'); 2348 var hasPunch=!!(rec&&(rec.punch_in||rec.punch_out)); 2349 var addBtn=canReg?('<button class="reg-addbtn'+(_inc?' warn':'')+'" onclick="openRegApply(\''+ds+'\')"><i class="ti ti-'+(_inc?'clock-plus':(hasPunch?'edit':'plus'))+'"></i> '+(_inc?'Add Punch Out':(hasPunch?'Edit / Correct Punch':'Add Punch & Request'))+'</button>'):''; 2350 var m=document.getElementById('attDayModal'); 2351 if(!m){ m=document.createElement('div'); m.id='attDayModal'; m.className='attd-modal'; m.addEventListener('click',function(ev){ if(ev.target===m) m.classList.remove('open'); }); document.body.appendChild(m); } 2352 m.innerHTML='<div class="attd-card"><div class="attd-head"><div><div class="attd-date">'+esc(nice)+'</div><span class="attd-pill" style="color:'+stCol+';border-color:'+stCol+'55">'+esc(stLabel)+'</span></div><button class="attd-x" onclick="document.getElementById(\'attDayModal\').classList.remove(\'open\')"><i class="ti ti-x"></i></button></div><div class="attd-body">'+body+regBlk+addBtn+'</div></div>'; 2353 m.classList.add('open'); 2354 } 2355 async function loadRegMine(){ 2356 try{ var id=currentId||ssGet('ems_id'); if(!id) return; var d=await api({action:'emsRegMine', employeeId:id}); if(d&&d.status==='success'){ var by={}; (d.rows||[]).forEach(function(x){ if(!by[x.date]||x.status==='pending') by[x.date]=x; }); window.__regByDate=by; try{ var _al=window.__attLast; var _ap=document.getElementById('attendance'); if(_al && _ap && _ap.classList.contains('show')) renderAttCalendar(_al.ym,_al.rows,_al.holidays,_al.summary,_al.weekOffDays); }catch(_){} } }catch(_){} 2357 } 2358 function openRegApply(ds){ 2359 var dt=new Date(ds+'T00:00:00'); var nice=isNaN(+dt)?ds:dt.toLocaleDateString('en-IN',{weekday:'short',day:'numeric',month:'long',year:'numeric'}); 2360 var rec=(window.__attByDate||{})[ds]||{}; 2361 var _hm=function(v){ var mm=String(v||'').match(/(\d{1,2}):(\d{2})/); return mm?(String(+mm[1]).padStart(2,'0')+':'+mm[2]):''; }; 2362 var pin=_hm(rec.punch_in)||'09:30', pout=_hm(rec.punch_out); 2363 var isEdit=!!(rec.punch_in||rec.punch_out); 2364 var incOut=!!(rec.punch_in && !rec.punch_out && ds < _todayISO()); 2365 var m=document.getElementById('regApplyModal'); 2366 if(!m){ m=document.createElement('div'); m.id='regApplyModal'; m.className='attd-modal'; m.addEventListener('click',function(ev){ if(ev.target===m) m.classList.remove('open'); }); document.body.appendChild(m); } 2367 m.innerHTML='<div class="attd-card"><div class="attd-head"><div><div class="attd-date">'+(isEdit?'Correct Punch':'Add Punch')+'</div><span class="attd-sub">'+esc(nice)+'</span></div><button class="attd-x" onclick="document.getElementById(\'regApplyModal\').classList.remove(\'open\')"><i class="ti ti-x"></i></button></div>' 2368 +'<div class="attd-body"><div class="reg-note"><i class="ti ti-info-circle"></i> '+(incOut?'You punched in but didn\u2019t punch out. Enter your correct punch-out time and a reason \u2014 it goes to HR for approval.':(isEdit?'Fix a wrong or missing punch time. Enter the correct times and a reason \u2014 your request goes to HR for approval.':'Forgot to punch or faced a server issue? Enter the correct time and a reason. Your request goes to HR for approval.'))+'</div>' 2369 +'<label class="reg-lbl">Punch In <span style="color:#ff9c8b">*</span></label><input type="time" id="regIn" class="reg-inp" value="'+esc(pin)+'">' 2370 +'<label class="reg-lbl">Punch Out '+(incOut?'<span style="color:#ff9c8b">*</span>':'<span class="reg-opt">(optional)</span>')+'</label><input type="time" id="regOut" class="reg-inp"'+(pout?(' value="'+esc(pout)+'"'):'')+(incOut?' autofocus':'')+'>' 2371 +'<label class="reg-lbl">Reason <span style="color:#ff9c8b">*</span></label><textarea id="regReason" class="reg-inp" rows="2" placeholder="e.g. Forgot to punch in / server issue"></textarea>' 2372 +'<div class="reg-actions"><button class="reg-cancel" onclick="document.getElementById(\'regApplyModal\').classList.remove(\'open\')">Cancel</button><button class="reg-submit" onclick="submitReg(\''+ds+'\')"><i class="ti ti-send"></i> Send Request</button></div></div></div>'; 2373 m.classList.add('open'); 2374 } 2375 async function submitReg(ds){ 2376 var id=currentId||ssGet('ems_id'); 2377 var pin=(document.getElementById('regIn')||{}).value||'', pout=(document.getElementById('regOut')||{}).value||'', reason=((document.getElementById('regReason')||{}).value||'').trim(); 2378 if(!pin&&!pout){ toast('Enter a punch time'); return; } 2379 if(!reason){ toast('Please add a reason'); return; } 2380 var btn=document.querySelector('#regApplyModal .reg-submit'); if(btn){ btn.disabled=true; btn.innerHTML='Sendingâ¦'; } 2381 try{ 2382 var d=await api({action:'emsRegApply', employeeId:id, token:ssGet('ems_tok'), date:ds, punchIn:pin, punchOut:pout, reason:reason}); 2383 if(d&&d.status==='success'){ toast('Request sent to HR â'); var rm=document.getElementById('regApplyModal'); if(rm)rm.classList.remove('open'); var am=document.getElementById('attDayModal'); if(am)am.classList.remove('open'); await loadRegMine(); loadAttendance(); } 2384 else { toast((d&&d.message)||'Could not send'); if(btn){ btn.disabled=false; btn.innerHTML='<i class="ti ti-send"></i> Send Request'; } } 2385 }catch(e){ toast('Could not send'); if(btn){ btn.disabled=false; btn.innerHTML='<i class="ti ti-send"></i> Send Request'; } } 2386 }
2387 async function loadLeave(){ 2388 const id=currentId||ssGet('ems_id'); if(!id) return; 2389 try{ 2390 const res=await Promise.all([ api({action:'emsLeaveInit', employeeId:id}), api({action:'emsLeaveMine', employeeId:id}) ]); 2391 const init=res[0], mine=res[1]; 2392 if(init&&init.status==='success'){ _leaveTypes=init.types||[]; renderLeaveBalances(init.types||[], init.balances||[]); fillLeaveTypeSelect(); } 2393 if(mine&&mine.status==='success') renderMyLeaves(mine.rows||[]); 2394 }catch(e){ var lb=document.getElementById('leaveBalances'); if(lb)lb.innerHTML='<div class="empty">Couldnât load leave. Check your connection and tap refresh.</div>'; } 2395 loadHolidays(); 2396 } 2397 function renderLeaveBalances(types, balances){ 2398 const byType={}; balances.forEach(b=>byType[b.type_code]=b); 2399 const el=document.getElementById('leaveBalances'); if(!el) return; 2400 el.innerHTML='<div class="lv-bals">'+types.map(function(t){ const b=byType[t.code]||{allotted:t.annual_quota||0,used:0,pending:0}; const all=Number(b.allotted!=null?b.allotted:(t.annual_quota||0))||0; const avail=(all>0)?(all-(Number(b.used)||0)-(Number(b.pending)||0)):'\u221e'; const paid=(String(t.paid)!=='false'); const tag='<span class="lv-tag '+(paid?'paid':'unpaid')+'">'+(paid?'PAID':'UNPAID')+'</span>'; return '<div class="lv-bal"><div class="lv-bal-k">'+esc(t.name||t.code)+tag+'</div><div class="lv-bal-v">'+avail+'</div><div class="lv-bal-s">'+(all>0?('of '+all+' \u00b7 used '+(Number(b.used)||0)):'unlimited')+'</div></div>'; }).join('')+'</div>'; 2401 } 2402 async function loadHolidays(){ 2403 const host=document.getElementById('leaveHolidays'); if(!host) return; 2404 try{ 2405 const yr=new Date().getFullYear(); 2406 const d=await api({action:'emsHolidaysYear', year:yr}); 2407 const rows=((d&&d.rows)||[]).filter(function(h){ return String(h.type||'holiday')==='holiday'; }); 2408 const head='<div class="accwrap-head" style="margin-top:16px"><i class="ti ti-calendar-star"></i> Company Holidays '+yr+'</div><div class="hol-note"><i class="ti ti-info-circle"></i> All company holidays are paid and applied automatically. No leave request is required, and your leave balance is not affected.</div>'; 2409 if(!rows.length){ host.innerHTML=head+'<div class="empty">Holiday calendar not published yet.</div>'; return; } 2410 const today=_todayISO(); 2411 host.innerHTML=head+'<div class="hol-grid">'+rows.map(function(h){ const dd=new Date(String(h.date)+'T00:00:00'); const ok=!isNaN(+dd); const past=String(h.date)<today; const wd=ok?dd.toLocaleDateString('en-IN',{weekday:'long'}):''; const dnum=ok?dd.getDate():'\u2014'; const mon=ok?dd.toLocaleDateString('en-IN',{month:'short'}):''; return '<div class="hol-card'+(past?' past':'')+'"><div class="hol-date"><b>'+dnum+'</b><span>'+esc(mon)+'</span></div><div class="hol-info"><b>'+esc(h.name||'Holiday')+'</b><span>'+esc(wd)+' \u00b7 Paid holiday</span></div></div>'; }).join('')+'</div>'; 2412 }catch(e){ host.innerHTML=''; } 2413 } 2414 function fillLeaveTypeSelect(){ const s=document.getElementById('lvType'); if(s&&_leaveTypes.length) s.innerHTML=_leaveTypes.map(t=>'<option value="'+esc(t.code)+'">'+esc(t.name||t.code)+'</option>').join(''); } 2415 async function applyLeave(){ 2416 const id=currentId||ssGet('ems_id'); 2417 const type=document.getElementById('lvType').value, from=document.getElementById('lvFrom').value, to=document.getElementById('lvTo').value||document.getElementById('lvFrom').value, half=document.getElementById('lvHalf').checked, reason=document.getElementById('lvReason').value.trim(); 2418 if(!from){ toast('Choose a date'); return; } 2419 if(!reason){ toast('Add a reason'); return; } 2420 try{ 2421 const d=await api({action:'emsLeaveApply', employeeId:id, token:ssGet('ems_tok'), typeCode:type, fromDate:from, toDate:to, halfDay:half?'true':'false', reason:reason}); 2422 if(d&&d.status==='success'){ toast('Leave applied \u2713'); document.getElementById('lvReason').value=''; document.getElementById('lvHalf').checked=false; loadLeave(); } 2423 else toast((d&&d.message)||'Apply failed'); 2424 }catch(e){ toast('Network issue \u2014 please try again.'); } 2425 } 2426 async function cancelLeave(lid){ 2427 if(!confirm('Cancel this leave request?')) return; 2428 const id=currentId||ssGet('ems_id'); 2429 try{ 2430 const d=await api({action:'emsLeaveCancel', employeeId:id, id:lid}); 2431 if(d&&d.status==='success'){ toast('Cancelled');
2431 loadLeave(); } else toast((d&&d.message)||'Failed'); 2432 }catch(e){ toast('Network issue \u2014 please try again.'); } 2433 } 2434 function renderMyLeaves(rows){ 2435 const el=document.getElementById('myLeaves'); if(!el) return; 2436 if(!rows.length){ el.innerHTML='<div class="accwrap-head">My leaves</div><div class="empty">No leave requests yet.</div>'; return; } 2437 el.innerHTML='<div class="accwrap-head">My leaves</div>'+rows.map(function(r){ const s=String(r.status||'pending'); const badge=s==='approved'?'<span class="cs resolved">Approved</span>':(s==='rejected'?'<span class="cs" style="background:rgba(224,83,63,.15);color:#ff9c8b">Rejected</span>':(s==='cancelled'?'<span class="cs">Cancelled</span>':'<span class="cs open">Pending</span>')); const cancel=(s==='pending')?'<button class="dl ghost" style="margin-top:8px;padding:6px 12px" onclick="cancelLeave(\''+esc(r.id)+'\')"><i class="ti ti-x"></i> Cancel</button>':''; const dn=r.decision_note?'<div class="creply"><b>HR:</b> '+esc(r.decision_note)+'</div>':''; return '<div class="cmp-item"><div class="ctop"><b>'+esc(r.type_code||'')+' \u00b7 '+esc(String(r.days||''))+'d</b>'+badge+'</div><div style="font-size:12.5px;color:var(--muted)">'+esc(r.from_date||'')+(r.to_date&&r.to_date!==r.from_date?(' \u2192 '+esc(r.to_date)):'')+'</div>'+(r.reason?'<div style="margin-top:4px">'+esc(r.reason)+'</div>':'')+dn+cancel+'</div>'; }).join(''); 2438 } 2439 2440 /* ---- tabs ---- */ 2441 async function loadMyLetters(){ 2442 const box=document.getElementById('myLettersList'); if(!box)return; 2443 box.innerHTML='<div class="empty">Loading\u2026</div>'; 2444 try{ 2445 const d=await api({action:'emsMyLetters', employeeId:currentId||ssGet('ems_id'), token:ssGet('ems_tok')}); 2446 const rows=(d&&d.rows)||[]; 2447 if(!rows.length){ box.innerHTML='<div class="empty">No letters issued yet.</div>'; return; } 2448 box.innerHTML=rows.map(function(r){ return '<div class="drow"><div class="ic"><i class="ti ti-file-text"></i></div><div class="dt"><b>'+esc(r.letterName||'Letter')+'</b><span>'+esc(r.refNo||'')+' \u00b7 '+esc(r.generatedAt||'')+'</span></div>'+(r.pdfLink?'<a class="dl" href="'+esc(r.pdfLink)+'" target="_blank"><i class="ti ti-download"></i> Download</a>':'<button class="dl lock" disabled><i class="ti ti-lock"></i> N/A</button>')+'</div>'; }).join(''); 2449 }catch(e){ box.innerHTML='<div class="empty">Could not load letters.</div>'; } 2450 } 2451 function typeName(name){ var el=document.getElementById('eName'); if(!el)return; name=String(name||'Employee'); clearInterval(window._nameTimer); el.classList.add('type-cursor'); el.textContent=''; var i=0; window._nameTimer=setInterval(function(){ if(i<=name.length){ el.textContent=name.slice(0,i); i++; } else { clearInterval(window._nameTimer); } }, 65); } 2452 function showTab(id,btn){ 2453 // Attendance & Leave are blocked entirely when attendance is turned off for this employee â even a stray 2454 // click or a direct call can never open them; they land on the Dashboard instead. 2455 if((id==='attendance'||id==='leave') && window._attEnabled===false){ var _dbt=document.querySelector('.tab[data-tab="dashboard"]'); if(_dbt){ return showTab('dashboard',_dbt); } } 2456 [...document.querySelectorAll('.tab')].forEach(t=>t.classList.remove('active')); if(btn) btn.classList.add('active'); [...document.querySelectorAll('.panel')].forEach(p=>p.classList.remove('show')); document.getElementById(id).classList.add('show'); window._curTab=id; ping(id); if(id==='notice'){ setTimeout(function(){ try{ _noticeMarkSeen(); }catch(_){} }, 2500); } var _bk=document.getElementById('backBtn'); if(_bk)_bk.style.display=(id==='dashboard'?'none':''); if(id==='letters') loadMyLetters(); else if(id==='attendance') loadAttendance(); else if(id==='leave') loadLeave(); else if(id==='goals') loadGoals(); else if(id==='tasks') loadTasks(); else if(id==='performance') loadPerformance(); else if(id==='rewards') loadRewards(); else if(id==='learning') loadCourses(); else if(id==='engagement') loadEngagement(); else if(id==='directory') loadDirectory(); else if(id==='orgchart') loadOrgChart(); else if(id==='timeline') loadTimeline(); else if(id==='kb') loadKB(); else if(id==='meetings') loadMeetings(); else if(id==='forms') loadForms(); else if(id==='messages') loadMessages(); else if(id==='dashboard'){ loadDashboard(); if(window._empName) typeName(window._empName); } } 2457 2458 /* ---- refresh + session ---- */ 2459 async function refresh(silent){ 2460 const id=currentId||ssGet('ems_id'); if(!id)return; 2461 const b=document.getElementById('refBtn'); if(!silent)b.querySelector('i').classList.add('spin'); 2462 try{ const d=await api({action:'emsGetEmployee', employeeId:id, token:ssGet('ems_tok'), lite: silent?'1':''}); 2463 if(d.status==='session_replaced'){ ssDel('ems_id');ssDel('ems_tok'); location.reload(); return; } 2464 if(d.status==='success'){ currentId=id; load(d); } 2465 }catch(e){} 2466 setTimeout(()=>b.querySelector('i').classList.remove('spin'),600); 2467 } 2468 function ping(activity, hb){ const id=currentId||ssGet('ems_id'); if(!id)return; var pg=activity||window._curTab||'dashboard'; api({action:'emsPing', employeeId:id, name:(BUNDLE&&BUNDLE.employee||{}).name||'', activity:pg, page:pg, hb: hb?'1':''}).catch(()=>{}); }
2469 // Background refresh of the full employee bundle: every 2 min, and only while the person is actually using 2470 // the app (visible + touched within 3 min). Was every 35s regardless â the heaviest read in the portal, 2471 // multiplied by every open employee app. Any tab switch / manual refresh still loads fresh data instantly. 2472 setInterval(()=>{ if(document.visibilityState!=='visible' || !ssGet('ems_id')) return; if(Date.now()-(window._lastAct||0)>3*60*1000) return; refresh(true); }, 120000); 2473 window._curTab=window._curTab||'dashboard'; 2474 // "Live" must mean ACTUALLY using the app right now. Heartbeats stop the moment the app goes to the 2475 // background, and also when the person hasn't touched/scrolled/typed for 3 minutes â so a login left 2476 // open (or a backgrounded tab) drops off the HR "Live now" list within ~2 minutes instead of showing 2477 // live forever. Coming back to the app makes them live again instantly. 2478 window._lastAct=Date.now(); 2479 ['pointerdown','keydown','touchstart','scroll'].forEach(function(_ev){ document.addEventListener(_ev,function(){ window._lastAct=Date.now(); },{passive:true}); }); 2480 document.addEventListener('visibilitychange',function(){ if(!document.hidden){ window._lastAct=Date.now(); try{ ping(window._curTab||'dashboard', true); }catch(_){} } }); 2481 setInterval(function(){ if(document.hidden) return; if(Date.now()-(window._lastAct||0)>3*60*1000) return; ping(window._curTab||'dashboard', true); }, 45000); 2482 setTimeout(()=>{ try{ ping(window._curTab||'dashboard'); }catch(_){} }, 1500); 2483 (function(){ const id=ssGet('ems_id'); if(id){ currentId=id; _mvsSaveSession(id, ssGet('ems_tok'), id); document.getElementById('login').classList.add('hidden'); document.getElementById('app').style.display='block'; refresh(true); } })(); 2484 2485 function renderPolicies(d){ 2486 const list=d.policies||[]; 2487 if(!list.length){document.getElementById('policyList').innerHTML='<div class="empty">No policies posted yet.</div>';return} 2488 document.getElementById('policyList').innerHTML=list.map(p=>{ 2489 const btn=p.pdfLink?'<div style="margin-top:10px"><a class="dl" href="'+esc(p.pdfLink)+'" target="_blank"><i class="ti ti-download"></i> Download PDF</a></div>':''; 2490 return accItem('ti-book', esc(p.title), 'Tap to read', '<div class="ptext">'+esc(p.content||'')+'</div>'+btn); 2491 }).join(''); 2492 } 2493 function renderWarnings(d){ 2494 const list=d.warnings||[]; const box=document.getElementById('warnTop'); if(!box)return; if(!list.length){box.innerHTML='';return} 2495 box.innerHTML='<div class="warncard"><div class="sectitle" style="color:#ff9c8b;margin-bottom:10px"><i class="ti ti-alert-triangle"></i> Disciplinary Warning'+(list.length>1?'s ('+list.length+')':'')+'</div>'+list.slice().reverse().map(w=>'<div class="cmp-item" style="border-color:rgba(224,83,63,.5);background:rgba(224,83,63,.1)"><div class="ctop"><b style="color:#ffb3a6">'+esc(w.type)+'</b><span class="hint">'+esc(fmtDT(w.time))+'</span></div><div style="font-size:13px;color:var(--txt)">'+esc(w.reason||'')+'</div><button class="dl ghost" style="margin-top:8px" onclick=\'genWarning('+JSON.stringify(JSON.stringify(w))+')\'><i class="ti ti-download"></i> Warning Letter PDF</button></div>').join('')+'</div>'; 2496 } 2497 function renderResignation(d){ 2498 const list=d.resignations||[]; const pending=list.find(r=>String(r.status).toLowerCase()==='pending'); const box=document.getElementById('resignBox'); 2499 if(list.length){ 2500 box.innerHTML=list.slice().reverse().map(r=>{const s=String(r.status).toLowerCase();const badge=s==='pending'?'<span class="cs open">Pending</span>':(s==='accepted'||s==='relieved'||s==='approved'||s==='settled'?'<span class="cs resolved">'+esc(r.status)+'</span>':'<span class="cs">'+esc(r.status)+'</span>');return '<div class="cmp-item"><div class="ctop"><b>Resignation</b>'+badge+'</div><div style="font-size:12.5px;color:var(--muted)">Last working day: '+esc(fmtDate(r.lastWorkingDay))+'</div><div style="margin-top:4px">'+esc(r.reason||'')+'</div>'+(s==='pending'?'<div class="creply" style="color:var(--warn)"><b>Status:</b> Your resignation is pending HR department review. You will be notified once HR responds.</div>':'')+(r.settlement?'<div class="creply"><b>
2500HR:</b> '+esc(r.settlement)+'</div>':'')+'</div>';}).join(''); 2501 if(!pending) box.innerHTML+=resignForm(); 2502 } else box.innerHTML=resignForm(); 2503 } 2504 function resignForm(){ return '<label style="margin-top:6px;display:block">Last working day</label><input class="inp" id="rsDate" type="date"><label style="margin-top:8px;display:block">Reason</label><textarea id="rsReason" placeholder="Reason for resignationâ¦"></textarea><button class="btn danger" style="margin-top:10px;background:linear-gradient(180deg,#e8786a,var(--red));color:#fff" onclick="submitResign()">Submit Resignation</button>'; } 2505 async function submitResign(){ const dt=document.getElementById('rsDate').value, r=document.getElementById('rsReason').value.trim(); if(!r){toast('Enter a reason');return} if(!confirm('Submit your resignation? HR will be notified.'))return; const d=await api({action:'emsResign', employeeId:currentId, name:(BUNDLE.employee||{}).name||'', reason:r, lastWorkingDay:dt}); if(d.status==='success'){toast('Resignation submitted');refresh(true);} else toast(d.message||'Failed'); } 2506 function genWarning(jsonStr){ const w=JSON.parse(jsonStr); const e=BUNDLE.employee||{}, c=company(); const doc=jp(); letterHead(doc,c); 2507 doc.setTextColor(192,57,43); doc.setFont('helvetica','bold'); doc.setFontSize(14); doc.text(String(w.type||'WARNING').toUpperCase(),297,128,{align:'center'}); 2508 doc.setTextColor(90,90,90); doc.setFont('helvetica','normal'); doc.setFontSize(10); doc.text('Date: '+fmtDate(w.time),555,150,{align:'right'}); doc.text('Ref: MVS/WARN/'+(e.employeeId||''),40,150); 2509 doc.setTextColor(20,20,20); doc.setFontSize(11); doc.text('To: '+(e.name||'')+' ('+(e.employeeId||'')+')',40,180); 2510 const body='This letter is to formally place on record a '+(w.type||'warning')+' issued to you for the following reason:\n\n'+(w.reason||'')+'\n\nYou are advised to take corrective action immediately. Repeated instances may lead to further disciplinary action as per company policy.'; 2511 const lines=doc.splitTextToSize(body,515); doc.setFontSize(11); doc.text(lines,40,206); 2512 sigBlock(doc,c,Math.min(206+lines.length*15+40,690)); letterFoot(doc,c); doc.save((e.name||'emp').replace(/\s+/g,'_')+'_warning.pdf'); 2513 } 2514 2515 /* ================= PDF GENERATION ================= */ 2516 function logoImg(){ return "data:image/jpeg;base64,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"; }
2517 function company(){ const c=(BUNDLE&&BUNDLE.config)||{}; return {name:c.companyName||'MVS Foundation', addr:c.companyAddress||'', email:c.companyEmail||'', phone:c.companyPhone||'', web:c.website||'', hr:c.hrName||'HR Department', hrDes:c.hrDesignation||'Human Resources'}; } 2518 function jp(){ return new window.jspdf.jsPDF({unit:'pt', format:'a4'}); } 2519 function letterHead(doc, c){ 2520 doc.setFillColor(19,41,75); doc.rect(0,0,595,90,'F'); 2521 try{ doc.addImage(logoImg(),'JPEG',40,22,46,46); }catch(e){} 2522 doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.setFontSize(18); doc.text(c.name,100,46); 2523 doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(220,210,180); 2524 doc.text([c.addr, [c.email,c.phone,c.web].filter(Boolean).join(' | ')].filter(Boolean).join('\n'),100,60); 2525 doc.setDrawColor(202,165,74); doc.setLineWidth(2); doc.line(40,90,555,90); 2526 } 2527 function letterFoot(doc,c){ 2528 doc.setDrawColor(220,220,220); doc.setLineWidth(.5); doc.line(40,770,555,770); 2529 doc.setFontSize(8); doc.setTextColor(150,150,150); 2530 doc.text(c.name+' ⢠This is a system-generated document from the MVS Employee Portal.',40,784); 2531 } 2532 function sigBlock(doc,c,y,stampKey){
2533 const cfg=(BUNDLE&&BUNDLE.config)||{}; 2534 doc.setTextColor(30,30,30); doc.setFont('helvetica','normal'); doc.setFontSize(11); 2535 doc.text('For '+c.name+',',40,y); 2536 // One official stamp / seal, placed just below the sign-off line with its true aspect ratio 2537 // (no stretching). Prefers the company seal, then the HR signature. 2538 let placedH=0; 2539 try{ 2540 const st=cfg[stampKey]||cfg.stampDirector||cfg.signatureHR||cfg.signature||cfg.stamp; 2541 if(st&&String(st).indexOf('data:')===0){ 2542 const props=doc.getImageProperties(st); const maxW=118, maxH=78; 2543 const scale=Math.min(maxW/props.width, maxH/props.height); 2544 const w=props.width*scale, h=props.height*scale; 2545 const fmt=String(st).indexOf('image/png')>0?'PNG':'JPEG'; 2546 doc.addImage(st,fmt,40,y+10,w,h); placedH=h; 2547 } 2548 }catch(_){} 2549 const ny=y+(placedH>0?placedH+24:36); 2550 doc.setFont('helvetica','bold'); doc.setTextColor(30,30,30); doc.setFontSize(11); doc.text(c.hr||'HR Department', 40, ny); 2551 doc.setFont('helvetica','normal'); doc.setFontSize(10); doc.setTextColor(90,90,90); doc.text(c.hrDes||'Human Resources',40,ny+14); 2552 } 2553 function salaryRows(e){ 2554 const earn=[['Basic',numv(e.basic)],['HRA',numv(e.hra)],['Other Allowance',numv(e.allowance)]]; 2555 const ded=[['Provident Fund (PF)',numv(e.pf)],['ESI',numv(e.esi)],['Professional Tax',numv(e.profTax)],['TDS',numv(e.tds)]]; 2556 const gross=earn.reduce((a,b)=>a+b[1],0), totded=ded.reduce((a,b)=>a+b[1],0); 2557 return {earn,ded,gross,totded,net:gross-totded}; 2558 } 2559 function moneyTable(doc, x, y, title, rows, total, totalLabel){ 2560 doc.setFillColor(244,247,252); doc.rect(x,y,255,24,'F'); 2561 doc.setTextColor(19,41,75); doc.setFont('helvetica','bold'); doc.setFontSize(11); doc.text(title,x+10,y+16); 2562 doc.setFont('helvetica','normal'); doc.setFontSize(10); doc.setTextColor(40,40,40); let yy=y+42; 2563 rows.forEach(r=>{ doc.text(String(r[0]),x+10,yy); doc.text(rupee(r[1]),x+245,yy,{align:'right'}); yy+=20; }); 2564 doc.setDrawColor(210,210,210); doc.line(x,yy-12,x+255,yy-12); 2565 doc.setFont('helvetica','bold'); doc.text(totalLabel,x+10,yy+2); doc.text(rupee(total),x+245,yy+2,{align:'right'}); 2566 return yy+18; 2567 } 2568 function openKRA(){ const k=(BUNDLE&&BUNDLE.employee||{}).kra||''; document.getElementById('kraBody').textContent = k ? k : 'Your Key Responsibility Areas have not been set by HR yet. Please check back later.'; document.getElementById('kraModal').style.display='flex'; } 2569 function genKRA(){ 2570 try{ 2571 if(!BUNDLE){return} const e=BUNDLE.employee||{}, c=company(); const doc=jp(); 2572 letterHead(doc,c); doc.setTextColor(30,30,30); 2573 const ref='MVS/KRA/'+(e.employeeId||'')+'/'+new Date().getFullYear(); 2574 doc.setFontSize(10); doc.setTextColor(90,90,90); doc.text('Ref: '+ref,40,118); doc.text('Date: '+fmtDate(new Date()),555,118,{align:'right'}); 2575 doc.setTextColor(20,20,20); doc.setFont('helvetica','bold'); doc.setFontSize(13); doc.text('KEY RESPONSIBILITY AREAS (KRA)',297,150,{align:'center'}); 2576 doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(30,30,30); 2577 doc.text('Employee:',40,186); doc.setFont('helvetica','bold'); doc.text(String(e.name||''),120,186); 2578 doc.setFont('helvetica','normal'); doc.text('Employee ID: '+(e.employeeId||''),380,186); 2579 var y=214; var rows=[['Department',e.department||'-'],['Designation / Role',e.designation||'-'],['Employment Type',e.employmentType||'-'],['Reporting Manager',BUNDLE.reportingManager||e.reportingManager||'-'],['Work Location',e.workLocation||'-'],['Assigned Work',e.assignedWork||'-']]; 2580 doc.setFontSize(10.5); 2581 rows.forEach(function(r){ const vlines=doc.splitTextToSize(String(r[1]),330); const rowH=Math.max(27, vlines.length*13.5+12); doc.setTextColor(120,120,120); doc.setFont('helvetica','normal'); doc.text(String(r[0]),40,y); doc.setTextColor(25,25,25); doc.setFont('helvetica','bold'); doc.text(vlines,210,y); doc.setDrawColor(232,232,232); doc.setLineWidth(.5); doc.line(40,y+rowH-9,555,y+rowH-9); y+=rowH; }); 2582 y+=16; doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.setTextColor(19,41,75); doc.text('Key Responsibility Areas',40,y); 2583 doc.setDrawColor(202,165,74); doc.setLineWidth(1.6); doc.line(40,y+7,205,y+7); y+=28; 2584 doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(45,45,45); 2585 var kra=String(e.kra||'').trim() || 'Your Key Responsibility Areas have not been set by HR yet.'; 2586 doc.text(doc.splitTextToSize(kra,515),40,y); 2587 letterFoot(doc,c); doc.save(String(e.name||'employee').replace(/\s+/g,'_')+'_KRA.pdf'); 2588 }catch(err){ toast('Could not generate KRA. Please try again.'); } 2589 } 2590 function qrDataUrl(text){ 2591 return new Promise(function(res){ try{ const img=new Image(); img.crossOrigin='anonymous'; img.onload=function(){ try{ const cv=document.createElement('canvas'); cv.width=img.width; cv.height=img.height; cv.getContext('2d').drawImage(img,0,0); res(cv.toDataURL('image/png')); }catch(_){ res(''); } }; img.onerror=function(){ res(''); }; img.src='https://api.qrserver.com/v1/create-qr-code/?size=220x220&margin=0&data='+encodeURIComponent(text); }catch(_){ res(''); } }); 2592 } 2593 async function genICard(){ 2594 try{ 2595 const e=BUNDLE.employee||{}, c=company(); 2596 const doc=new window.jspdf.jsPDF({unit:'pt',format:[340,540]}); 2597 doc.setFillColor(14,27,44); doc.rect(0,0,340,540,'F'); 2598 doc.setFillColor(202,165,74); doc.rect(0,0,340,8,'F');
2599 try{ doc.addImage(logoImg(),'JPEG',143,22,54,54); }catch(_){} 2600 doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.setFontSize(15); 2601 doc.text(String(c.name||''),170,96,{align:'center'}); 2602 doc.setTextColor(232,201,119); doc.setFont('helvetica','normal'); doc.setFontSize(8); 2603 doc.text('EMPLOYEE IDENTITY CARD',170,110,{align:'center'}); 2604 const ph=String(e.photo||''); 2605 if(ph.indexOf('data:')===0){ const fmt=ph.indexOf('image/png')>0?'PNG':'JPEG'; try{ doc.addImage(ph,fmt,130,128,80,80); }catch(_){} doc.setDrawColor(202,165,74); doc.setLineWidth(2); doc.rect(130,128,80,80); } 2606 else { doc.setFillColor(255,255,255); doc.circle(170,168,40,'F'); doc.setTextColor(19,41,75); doc.setFont('helvetica','bold'); doc.setFontSize(34); doc.text(String((e.name||'?').slice(0,1)).toUpperCase(),170,180,{align:'center'}); } 2607 doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.setFontSize(18); 2608 doc.text(String(e.name||''),170,242,{align:'center'}); 2609 doc.setTextColor(232,201,119); doc.setFont('helvetica','normal'); doc.setFontSize(12); 2610 doc.text(String(e.designation||''),170,260,{align:'center'}); 2611 let y=302; const row=(k,v)=>{ doc.setTextColor(169,182,201); doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.text(String(k),30,y); doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.text(String(v||'-'),310,y,{align:'right'}); doc.setDrawColor(90,105,130); doc.setLineWidth(.4); doc.line(30,y+9,310,y+9); y+=31; }; 2612 row('Employee ID', e.employeeId); row('Department', e.department); row('Date of Joining', fmtDate(e.doj)); row('Mobile', e.mobile); 2613 doc.setFillColor(202,165,74); doc.rect(0,512,340,28,'F'); doc.setTextColor(26,18,7); doc.setFont('helvetica','bold'); doc.setFontSize(9); 2614 doc.text(String(c.web||c.email||c.name||''),170,530,{align:'center'}); 2615 try{ 2616 const vcard='BEGIN:VCARD\nVERSION:3.0\nFN:'+String(e.name||'')+'\nORG:'+String(c.name||'')+'\nTITLE:'+String(e.designation||'')+'\nNOTE:Employee ID '+String(e.employeeId||'')+' - Verified '+String(c.name||'')+'\nEND:VCARD'; 2617 const qr=await qrDataUrl(vcard); 2618 if(qr){ doc.setFillColor(255,255,255); doc.roundedRect(134,428,72,72,7,7,'F'); doc.addImage(qr,'PNG',139,433,62,62); doc.setTextColor(169,182,201); doc.setFont('helvetica','normal'); doc.setFontSize(7); doc.text('Scan to verify',170,508,{align:'center'}); } 2619 }catch(_q){} 2620 doc.save(String(e.name||'employee').replace(/\s+/g,'_')+'_ICard.pdf'); 2621 }catch(err){ toast('Could not generate I-Card. Please try again.'); } 2622 } 2623 function genDoc(kind){ 2624 if(kind==='icard'){ return genICard(); } 2625 if(!BUNDLE){return} const e=BUNDLE.employee||{}, c=company(); const doc=jp(); 2626 letterHead(doc,c); doc.setTextColor(30,30,30); 2627 const ref='MVS/'+(e.employeeId||'')+'/'+new Date().getFullYear(); 2628 doc.setFontSize(10); doc.setTextColor(90,90,90); doc.text('Ref: '+ref,40,118); doc.text('Date: '+fmtDate(new Date()),555,118,{align:'right'}); 2629 doc.setTextColor(20,20,20); doc.setFont('helvetica','bold'); doc.setFontSize(13); 2630 let title={offer:'OFFER LETTER',appointment:'APPOINTMENT LETTER',experience:'EXPERIENCE / RELIEVING LETTER'}[kind]; 2631 doc.text(title,297,150,{align:'center'}); 2632 doc.setFont('helvetica','normal'); doc.setFontSize(11); let y=186; 2633 doc.text('To,',40,y); doc.setFont('helvetica','bold'); doc.text(String(e.name||''),40,y+16); 2634 doc.setFont('helvetica','normal'); doc.setTextColor(90,90,90); doc.setFontSize(10); 2635 doc.text([String(e.designation||''),String(e.department?('Department: '+e.department):'')].filter(Boolean).join('\n'),40,y+32); 2636 doc.setTextColor(30,30,30); doc.setFontSize(11); y+=70; 2637 const wrap=(t)=>doc.splitTextToSize(t,515); 2638 let body=[]; 2639 if(kind==='offer'){ 2640 body=['Dear '+(e.name||'')+',', 2641 'We are pleased to offer you the position of '+(e.designation||'')+' in the '+(e.department||'')+' department at '+c.name+'. Your tentative date of joining is '+fmtDate(e.doj)+'.',
2642 'Your annual Cost to Company (CTC) is '+rupee(e.ctc)+'. A detailed salary structure (Annexure A) is provided below. Your employment will be governed by the company policies in force.', 2643 'We look forward to a long and rewarding association. Kindly sign and return a copy as a token of your acceptance.']; 2644 } else if(kind==='appointment'){ 2645 body=['Dear '+(e.name||'')+',', 2646 'With reference to your joining on '+fmtDate(e.doj)+', we are pleased to confirm your appointment as '+(e.designation||'')+' in the '+(e.department||'')+' department at '+c.name+'.', 2647 'You will report to '+(BUNDLE.reportingManager||'your reporting manager')+'. Your annual CTC is '+rupee(e.ctc)+' as detailed in Annexure A below. All other terms of your employment remain as per company policy.', 2648 'We congratulate you on your confirmation and wish you continued success.']; 2649 } else { 2650 body=['To Whomsoever It May Concern,', 2651 'This is to certify that '+(e.name||'')+' (Employee ID: '+(e.employeeId||'')+') has been associated with '+c.name+' as '+(e.designation||'')+' in the '+(e.department||'')+' department since '+fmtDate(e.doj)+'.', 2652 'During the tenure, conduct and performance have been found to be satisfactory. We wish them success in all future endeavours.']; 2653 } 2654 doc.setFontSize(11); doc.setTextColor(30,30,30); 2655 body.forEach(p=>{ const lines=wrap(p); doc.text(lines,40,y); y+=lines.length*15+8; }); 2656 if(kind==='offer'||kind==='appointment'){ 2657 const s=salaryRows(e); y+=4; 2658 doc.setFont('helvetica','bold'); doc.setTextColor(19,41,75); doc.text('Annexure A â Salary Structure (Monthly)',40,y); y+=10; 2659 const y2=moneyTable(doc,40,y,'Earnings',s.earn,s.gross,'Gross Earnings'); 2660 moneyTable(doc,300,y,'Deductions',s.ded,s.totded,'Total Deductions'); 2661 y=Math.max(y2,y)+14; doc.setTextColor(19,41,75); doc.setFont('helvetica','bold'); doc.setFontSize(12); 2662 doc.text('Net Monthly Pay: '+rupee(s.net),40,y); y+=10; 2663 } 2664 sigBlock(doc,c,Math.min(y+30,690)); letterFoot(doc,c); 2665 doc.save((e.name||'employee').replace(/\s+/g,'_')+'_'+kind+'.pdf'); 2666 } 2667 function fmtAmt(n){ return Number(n||0).toLocaleString('en-IN',{minimumFractionDigits:2,maximumFractionDigits:2}); } 2668 async function genSlip(year, mIdx){ 2669 try{ 2670 const e=BUNDLE.employee||{}, c=company(); const doc=jp(); 2671 const mName=new Date(year,mIdx,1).toLocaleString('en-IN',{month:'long'}); 2672 const days=new Date(year,mIdx+1,0).getDate(); 2673 const period=mName+' '+year; const W=595, M=40, RW=W-2*M; 2674 const ym=year+'-'+String(mIdx+1).padStart(2,'0'); 2675 let ps=null; try{ const pd=await api({action:'emsMyPayslips', employeeId:currentId||ssGet('ems_id')}); if(pd&&pd.rows) ps=pd.rows.find(function(r){return String(r.month)===ym;}); }catch(_){} 2676 if(!ps){ toast('Payslip for '+period+' will be available once HR finalizes that monthâs payroll.'); return; } 2677 const num=function(v){return Number(v||0);}; 2678 const baseEarn=[['Basic',num(ps.basic!=null?ps.basic:e.basic)],['HRA',num(ps.hra!=null?ps.hra:e.hra)],['Other Allowance',num(ps.allowance!=null?ps.allowance:e.allowance)]]; 2679 const baseDed=[['Provident Fund (PF)',num(ps.pf!=null?ps.pf:e.pf)],['ESI',num(ps.esi!=null?ps.esi:e.esi)],['Professional Tax',num(ps.prof_tax!=null?ps.prof_tax:e.profTax)],['TDS',num(ps.tds!=null?ps.tds:e.tds)]]; 2680 const LABEL={overtime:'Over Time',extra_day:'Extra Day',incentive:'Incentive',bonus:'Bonus',reimbursement:'Reimbursement',late_coming:'Late Coming',early_going:'Early Going',asset_recovery:'Assets Recovery',pf:'Provident Fund (PF)',esi:'ESI',tds:'TDS',deduction:'Other Deduction'}; 2681 const DEDK={deduction:1,late_coming:1,early_going:1,asset_recovery:1,pf:1,esi:1,tds:1}; 2682 const payDays=(ps.paid_days!=null?num(ps.paid_days):30), lopDays=num(ps.lop_days); 2683 const presentDays=(ps.present_days!=null?num(ps.present_days):null); 2684 let comps=[]; try{ comps=JSON.parse(ps.earnings_extra||'[]'); }catch(_){} 2685 const earnC=comps.filter(function(x){return !DEDK[x.kind];}).map(function(x){return [LABEL[x.kind]||x.label||x.kind, num(x.amount)];}).filter(function(x){return num(x[1])>0;}); 2686 const dedC=comps.filter(function(x){return DEDK[x.kind];}).map(function(x){return [LABEL[x.kind]||x.label||x.kind, num(x.amount)];}).filter(function(x){return num(x[1])>0;}); 2687 const dedSeen={}; dedC.forEach(function(x){ dedSeen[String(x[0]).toLowerCase()]=1; }); 2688 let ded=[]; if(num(ps.lop_amount)>0) ded.push(['Leave / LOP ('+lopDays+'d)', num(ps.lop_amount)]); 2689 ded=ded.concat(dedC).concat(baseDed.filter(function(x){ return num(x[1])>0 && !dedSeen[String(x[0]).toLowerCase()]; })); 2690 const earn=baseEarn.filter(function(x){ return String(x[0])==='Basic' || num(x[1])>0; }).concat(earnC); 2691 const s={earn:earn, ded:ded, gross:num(ps.gross), totded:num(ps.total_deductions), net:num(ps.net)}; 2692 const esiDed=s.ded.some(function(x){return /esi/i.test(x[0]) && num(x[1])>0;}); 2693 const pfDed=s.ded.some(function(x){return /provident/i.test(x[0]) && num(x[1])>0;}); 2694 doc.setFillColor(21,21,27); doc.rect(0,0,W,98,'F'); 2695 doc.setFillColor(200,155,60); doc.rect(0,98,W,3.2,'F');
2696 try{ doc.addImage(logoImg(),'JPEG',M,26,48,48); }catch(_){} 2697 doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.setFontSize(19); doc.text(String(c.name||''),100,46); 2698 doc.setFont('helvetica','normal'); doc.setFontSize(8.5); doc.setTextColor(206,196,172); doc.text(doc.splitTextToSize(String(c.addr||''),330),100,62); 2699 doc.setTextColor(232,201,119); doc.setFont('helvetica','bold'); doc.setFontSize(11); doc.text('PAYSLIP',W-M,42,{align:'right'}); 2700 doc.setTextColor(220,210,190); doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.text(period,W-M,58,{align:'right'}); 2701 let y=128; doc.setTextColor(24,24,30); doc.setFont('helvetica','bold'); doc.setFontSize(15); doc.text('Salary Slip',W/2,y,{align:'center'}); 2702 doc.setDrawColor(200,155,60); doc.setLineWidth(1.5); doc.line(W/2-46,y+7,W/2+46,y+7); y+=22; 2703 const cells=[['Employee ID',e.employeeId||'-'],['Employee Name',e.name||'-'],['Designation',e.designation||'-'],['Month / Year',period],['Department',e.department||'-'],['Date of Joining',fmtDate(e.doj)||'-'],['Bank Name',e.bankName||'-'],['Account No.',e.bankAccount||'-'],['IFSC',e.ifsc||'-'],['Pay Days',String(payDays)+(lopDays>0?(' \u00b7 LOP '+lopDays+'d'):'')]]; 2704 if(presentDays!=null) cells.push(['Present Days',String(presentDays)]); 2705 if(esiDed && e.esiNo) cells.push(['ESI No.',String(e.esiNo)]); 2706 if(pfDed && (e.pfNo||e.uan)) cells.push(['PF No. / UAN',String(e.pfNo||e.uan)]); 2707 if(cells.length%2) cells.push(['','']); 2708 const rows=[]; for(let ci=0;ci<cells.length;ci+=2) rows.push([cells[ci][0],cells[ci][1],cells[ci+1][0],cells[ci+1][1]]); 2709 const rh=21, cLbl=92, colW=RW/2; doc.setLineWidth(.6); doc.setDrawColor(205,205,212); 2710 rows.forEach(function(r,i){ const ry=y+i*rh; 2711 doc.setFillColor(246,247,249); doc.rect(M,ry,cLbl,rh,'F'); doc.rect(M+colW,ry,cLbl,rh,'F'); 2712 doc.rect(M,ry,colW,rh); doc.rect(M+colW,ry,colW,rh); doc.rect(M+cLbl,ry,colW-cLbl,rh); doc.rect(M+colW+cLbl,ry,colW-cLbl,rh); 2713 doc.setFontSize(9); doc.setFont('helvetica','bold'); doc.setTextColor(75,75,85); doc.text(String(r[0]),M+7,ry+13.5); doc.text(String(r[2]),M+colW+7,ry+13.5); 2714 doc.setFont('helvetica','normal'); doc.setTextColor(25,25,30); 2715 doc.text(doc.splitTextToSize(String(r[1]),colW-cLbl-12),M+cLbl+7,ry+13.5); doc.text(doc.splitTextToSize(String(r[3]),colW-cLbl-12),M+colW+cLbl+7,ry+13.5); 2716 }); 2717 y+=rows.length*rh+18; const hw=RW/2-6; 2718 function half(x,title,rws,total,tl){ let yy=y; 2719 doc.setFillColor(21,21,27); doc.rect(x,yy,hw,24,'F'); 2720 doc.setTextColor(232,201,119); doc.setFont('helvetica','bold'); doc.setFontSize(10.5); doc.text(title,x+9,yy+16); doc.text('Amount (INR)',x+hw-9,yy+16,{align:'right'}); yy+=24; 2721 doc.setFont('helvetica','normal'); doc.setFontSize(10); doc.setTextColor(35,35,42); doc.setDrawColor(214,214,220); doc.setLineWidth(.5); 2722 rws.forEach(function(r){ doc.rect(x,yy,hw,20); doc.text(String(r[0]),x+9,yy+13.5); doc.text(fmtAmt(r[1]),x+hw-9,yy+13.5,{align:'right'}); yy+=20; }); 2723 doc.setFillColor(243,245,248); doc.rect(x,yy,hw,22,'F'); doc.rect(x,yy,hw,22); 2724 doc.setFont('helvetica','bold'); doc.setTextColor(21,21,27); doc.text(tl,x+9,yy+15); doc.text(fmtAmt(total),x+hw-9,yy+15,{align:'right'}); return yy+22; 2725 } 2726 const ly=half(M,'Earnings',s.earn,s.gross,'Total Earnings'); 2727 const ry=half(M+hw+12,'Deductions',s.ded,s.totded,'Total Deductions'); 2728 y=Math.max(ly,ry)+18; 2729 doc.setFillColor(21,21,27); doc.roundedRect(M,y,RW,44,6,6,'F'); doc.setFillColor(200,155,60); doc.rect(M,y,5,44,'F'); 2730 doc.setTextColor(255,255,255); doc.setFont('helvetica','bold'); doc.setFontSize(13); doc.text('Total Net Payable',M+18,y+21); 2731 doc.setTextColor(232,201,119); doc.setFontSize(17); doc.text('INR '+fmtAmt(s.net),W-M-14,y+24,{align:'right'}); 2732 doc.setTextColor(198,198,205); doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.text('Rupees '+inWords(s.net)+' only.',M+18,y+37); y+=64; 2733 doc.setTextColor(60,60,70); doc.setFont('helvetica','normal'); doc.setFontSize(9.5); doc.text('Salary paid by NEFT / UPI to the bank account on record.',M,y); 2734 doc.setDrawColor(228,228,230); doc.setLineWidth(.5); doc.line(M,792,W-M,792); 2735 doc.setFont('helvetica','italic'); doc.setFontSize(8.5); doc.setTextColor(120,120,130); doc.text('This is a computer-generated salary slip and does not require a signature.',W/2,806,{align:'center'}); 2736 doc.setFont('helvetica','normal'); doc.setFontSize(7.5); doc.setTextColor(150,150,155); doc.text(String(c.name||'')+' \u2022 '+period,W/2,818,{align:'center'}); 2737 doc.save(String(e.name||'employee').replace(/\s+/g,'_')+'_Payslip_'+mName+'_'+year+'.pdf'); 2738 }catch(err){ toast('Could not generate payslip. Please try again.'); } 2739 } 2740 function genNotice(jsonStr){ 2741 const n=JSON.parse(jsonStr); const c=company(); const doc=jp(); letterHead(doc,c); 2742 doc.setTextColor(19,41,75); doc.setFont('helvetica','bold'); doc.setFontSize(14); doc.text('NOTICE',297,128,{align:'center'}); 2743 doc.setTextColor(90,90,90); doc.setFont('helvetica','normal'); doc.setFontSize(10); doc.text('Date: '+fmtDate(n.t),555,150,{align:'right'}); 2744 doc.setTextColor(20,20,20); doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.text('Subject: '+(n.s||''),40,180); 2745 doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(30,30,30); 2746 const lines=doc.splitTextToSize(String(n.m||''),515); doc.text(lines,40,210);
2747 sigBlock(doc,c,Math.min(210+lines.length*15+50,690)); letterFoot(doc,c); 2748 doc.save('notice_'+(n.s||'mvs').replace(/\s+/g,'_').slice(0,24)+'.pdf'); 2749 } 2750 function inWords(num){ num=Math.round(num); if(num===0)return'Zero'; 2751 const a=['','One','Two','Three','Four','Five','Six','Seven','Eight','Nine','Ten','Eleven','Twelve','Thirteen','Fourteen','Fifteen','Sixteen','Seventeen','Eighteen','Nineteen']; 2752 const b=['','','Twenty','Thirty','Forty','Fifty','Sixty','Seventy','Eighty','Ninety']; 2753 function two(n){ return n<20?a[n]:b[Math.floor(n/10)]+(n%10?' '+a[n%10]:''); } 2754 function three(n){ return (n>=100?a[Math.floor(n/100)]+' Hundred'+(n%100?' ':''):'')+two(n%100); } 2755 let s=''; const cr=Math.floor(num/10000000); num%=10000000; const lk=Math.floor(num/100000); num%=100000; const th=Math.floor(num/1000); num%=1000; 2756 if(cr)s+=three(cr)+' Crore '; if(lk)s+=three(lk)+' Lakh '; if(th)s+=three(th)+' Thousand '; if(num)s+=three(num); 2757 return s.trim(); 2758 } 2759</script>
2759 2760</body> 2761</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.