File manager - Edit - /home/webtrixia/.trash/index.html.3
Back
<!DOCTYPE html> <html lang="bg"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Webtrixia Hub</title> <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"> <script> (function(){ var t=localStorage.getItem('wt_theme')||'light'; document.documentElement.setAttribute('data-theme',t); })(); </script> <style> [data-theme="dark"]{--bg:#0a0a0f;--sur:#111118;--sur2:#18181f;--sur3:#1e1e28;--brd:rgba(255,255,255,.07);--brd2:rgba(255,255,255,.13);--tx:#e8e8f0;--tx2:#9090a8;--tx3:#5a5a72;--shd:0 4px 24px rgba(0,0,0,.4)} [data-theme="light"]{--bg:#f0f0f8;--sur:#fff;--sur2:#f5f5fc;--sur3:#eaeaf5;--brd:rgba(0,0,0,.08);--brd2:rgba(0,0,0,.14);--tx:#1a1a2e;--tx2:#4a4a6a;--tx3:#8888aa;--shd:0 4px 24px rgba(108,99,255,.08)} :root{--acc:#6c63ff;--acc2:#a78bfa;--glow:rgba(108,99,255,.14);--grn:#22c55e;--gdim:rgba(34,197,94,.12);--amb:#f59e0b;--adim:rgba(245,158,11,.12);--red:#ef4444;--rdim:rgba(239,68,68,.12);--blu:#38bdf8;--bdim:rgba(56,189,248,.12);--fn:'DM Sans',sans-serif;--mo:'DM Mono',monospace} *{margin:0;padding:0;box-sizing:border-box} body{font-family:var(--fn);background:var(--bg);color:var(--tx);min-height:100vh;display:flex} .lw{position:fixed;inset:0;background:var(--bg);z-index:9999;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:14px} .lb{background:var(--sur);border:1px solid var(--brd2);border-radius:20px;padding:44px 40px;width:380px;text-align:center;box-shadow:var(--shd)} .llogo{height:34px;margin-bottom:20px} [data-theme="dark"] .llogo{filter:brightness(1.1)} .ltitle{font-size:22px;font-weight:600;color:var(--tx);margin-bottom:4px} .lsub{font-size:13px;color:var(--tx3);margin-bottom:6px} .aind{display:flex;align-items:center;justify-content:center;gap:8px;font-size:12px;color:var(--tx3);margin-bottom:20px;padding:6px 14px;border-radius:20px;background:var(--sur2);border:1px solid var(--brd)} .adot{width:8px;height:8px;border-radius:50%;background:var(--amb);transition:background .3s;flex-shrink:0} .adot.ok{background:var(--grn)}.adot.err{background:var(--red)} .lf{margin-bottom:12px;text-align:left} .lf label{display:block;font-size:11px;color:var(--tx3);text-transform:uppercase;letter-spacing:.07em;margin-bottom:5px} .lf input{width:100%;background:var(--sur2);border:1px solid var(--brd);border-radius:10px;padding:11px 14px;color:var(--tx);font-family:var(--fn);font-size:14px;outline:none;transition:border-color .15s} .lf input:focus{border-color:var(--acc)} .lbtn{width:100%;padding:12px;border-radius:10px;background:var(--acc);border:none;color:#fff;font-size:14px;font-weight:600;cursor:pointer;font-family:var(--fn);margin-top:4px} .lbtn:hover{opacity:.9} .lerr{font-size:12px;color:var(--red);margin-top:8px;display:none} .tbtn2{background:var(--sur);border:1px solid var(--brd);border-radius:8px;padding:8px 18px;color:var(--tx2);cursor:pointer;font-family:var(--fn);font-size:13px} .tbtn2:hover{background:var(--sur2);color:var(--tx)} .sb{width:220px;height:100vh;background:var(--sur);border-right:1px solid var(--brd);display:flex;flex-direction:column;position:fixed;top:0;left:0;z-index:100;overflow:hidden} .slogo{padding:16px 14px 12px;border-bottom:1px solid var(--brd);display:flex;align-items:center;gap:10px;flex-shrink:0} .slogo img{height:26px} [data-theme="dark"] .slogo img{filter:brightness(1.1)} .lsub2{font-size:10px;color:var(--tx3);letter-spacing:.08em;text-transform:uppercase} .lver{font-size:11px;color:var(--acc2);font-weight:500;margin-top:1px} nav{padding:8px 6px;flex:1;min-height:0;overflow-y:auto;scrollbar-width:thin} nav::-webkit-scrollbar{width:6px} nav::-webkit-scrollbar-thumb{background:var(--brd2);border-radius:10px} .nsec{font-size:10px;color:var(--tx3);letter-spacing:.1em;text-transform:uppercase;padding:10px 8px 5px;font-weight:500} .ni{display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:8px;cursor:pointer;transition:all .15s;font-size:13px;color:var(--tx2);border:1px solid transparent;margin-bottom:1px;background:none;width:100%;text-align:left;font-family:var(--fn)} .ni:hover{background:var(--sur2);color:var(--tx)} .ni.active{background:var(--glow);color:var(--acc2);border-color:rgba(108,99,255,.2)} .nic{font-size:13px;width:15px;text-align:center;flex-shrink:0} .nbadge{margin-left:auto;font-size:10px;background:var(--red);color:#fff;border-radius:10px;padding:1px 6px;font-weight:600} .nbadge.g{background:var(--grn)}.nbadge.a{background:var(--amb);color:#000} .sbot{padding:10px 6px;border-top:1px solid var(--brd);flex-shrink:0} .trow{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:8px} .av{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0} .av-p{background:rgba(108,99,255,.25);color:#a78bfa}.av-t{background:rgba(20,184,166,.25);color:#2dd4bf} .av-g{background:rgba(34,197,94,.2);color:#22c55e}.av-a{background:rgba(245,158,11,.2);color:#f59e0b} .av-r{background:rgba(239,68,68,.2);color:#ef4444}.av-b{background:rgba(56,189,248,.2);color:#38bdf8} .main{margin-left:220px;flex:1;min-height:100vh;display:flex;flex-direction:column} .topbar{height:52px;background:var(--sur);border-bottom:1px solid var(--brd);display:flex;align-items:center;padding:0 22px;gap:10px;position:sticky;top:0;z-index:50;flex-shrink:0} .ttl{font-size:14px;font-weight:500;color:var(--tx);flex:1} .tdate{font-size:11px;color:var(--tx3);font-family:var(--mo)} .btn{padding:6px 12px;border-radius:8px;border:1px solid var(--brd2);background:transparent;color:var(--tx2);font-size:12px;cursor:pointer;transition:all .15s;font-family:var(--fn);white-space:nowrap} .btn:hover{background:var(--sur2);color:var(--tx)} .btn.p{background:var(--acc);border-color:var(--acc);color:#fff}.btn.p:hover{opacity:.9} .btn.s{border-color:var(--grn);color:var(--grn)} .btn.d{border-color:var(--red);color:var(--red)} .tbtn{padding:5px 9px;border-radius:8px;border:1px solid var(--brd2);background:transparent;cursor:pointer;font-size:15px} .tbtn:hover{background:var(--sur2)} .ubdg{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--tx2);padding:4px 10px;border-radius:8px;border:1px solid var(--brd);cursor:pointer} .ubdg:hover{background:var(--sur2)} .con{padding:22px;flex:1} .sec{display:none}.sec.active{display:block} .kg{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-bottom:20px} .kc{background:var(--sur);border:1px solid var(--brd);border-radius:12px;padding:14px 16px;position:relative;overflow:hidden} .kc:hover{border-color:var(--brd2)} .kl{font-size:10px;color:var(--tx3);text-transform:uppercase;letter-spacing:.07em;font-weight:500;margin-bottom:6px} .kv{font-size:21px;font-weight:600;color:var(--tx);font-family:var(--mo)} .kv.g{color:var(--grn)}.kv.a{color:var(--amb)}.kv.ac{color:var(--acc2)}.kv.r{color:var(--red)} .ks{font-size:10px;color:var(--tx3);margin-top:2px} .kg2{position:absolute;bottom:-20px;right:-20px;width:55px;height:55px;border-radius:50%;opacity:.07} .panel{background:var(--sur);border:1px solid var(--brd);border-radius:14px;overflow:hidden;margin-bottom:16px} .ph{padding:13px 17px;border-bottom:1px solid var(--brd);display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap} .pt{font-size:13px;font-weight:500;color:var(--tx)} .pa{font-size:11px;color:var(--acc2);cursor:pointer;padding:3px 9px;border-radius:6px;border:1px solid rgba(108,99,255,.2);background:none;font-family:var(--fn)} .pa:hover{background:var(--glow)} .loading{padding:36px;text-align:center;color:var(--tx3);font-size:13px} .loading::after{content:'';display:inline-block;width:14px;height:14px;border:2px solid var(--brd2);border-top-color:var(--acc);border-radius:50%;animation:spin .7s linear infinite;margin-left:8px;vertical-align:middle} @keyframes spin{to{transform:rotate(360deg)}} .emp{padding:40px 20px;text-align:center} .ei{font-size:30px;margin-bottom:10px;opacity:.4} .et{font-size:14px;font-weight:500;color:var(--tx2);margin-bottom:4px} .es{font-size:12px;color:var(--tx3);margin-bottom:14px} .pr{padding:11px 17px;border-bottom:1px solid var(--brd);display:grid;grid-template-columns:1fr auto auto auto;align-items:center;gap:12px;cursor:pointer;transition:background .15s} .pr:last-child{border-bottom:none}.pr:hover{background:var(--sur2)} .pn{font-size:13px;font-weight:500;color:var(--tx)}.pm{font-size:11px;color:var(--tx3);margin-top:1px} .ptags{display:flex;gap:4px;margin-top:4px;flex-wrap:wrap} .tag{font-size:10px;padding:2px 7px;border-radius:20px;font-weight:500} .tag.web{background:var(--bdim);color:var(--blu)}.tag.design{background:var(--glow);color:var(--acc2)} .tag.seo{background:var(--gdim);color:var(--grn)}.tag.social{background:rgba(244,114,182,.12);color:#f472b6} .tag.hosting{background:var(--adim);color:var(--amb)}.tag.rush{background:var(--rdim);color:var(--red)} .sp{font-size:10px;padding:3px 9px;border-radius:20px;font-weight:500;white-space:nowrap} .sp-a{background:var(--gdim);color:var(--grn)}.sp-n{background:var(--bdim);color:var(--blu)} .sp-r{background:var(--adim);color:var(--amb)}.sp-d{background:rgba(128,128,128,.1);color:var(--tx3)} .amt{text-align:right;white-space:nowrap} .at{font-size:13px;font-weight:600;font-family:var(--mo);color:var(--tx)} .ar{font-size:10px;color:var(--grn);margin-top:1px}.ap{font-size:10px;color:var(--amb);margin-top:1px} .dlc{text-align:right;white-space:nowrap} .dld{font-size:11px;font-family:var(--mo);color:var(--tx2)}.dld.u{color:var(--red);font-weight:500} .dln{font-size:10px;color:var(--tx3);margin-top:1px}.dln.u{color:var(--red)} .pb{height:3px;background:var(--sur3);border-radius:2px;margin-top:5px;overflow:hidden} .pbf{height:100%;border-radius:2px} .g2{display:grid;grid-template-columns:1fr 300px;gap:16px;margin-bottom:16px} .g3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:16px} .g22{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px} .ti{padding:9px 15px;border-bottom:1px solid var(--brd);display:flex;align-items:flex-start;gap:9px;cursor:pointer;transition:background .15s} .ti:last-child{border-bottom:none}.ti:hover{background:var(--sur2)} .tck{width:15px;height:15px;border-radius:4px;border:1px solid var(--brd2);flex-shrink:0;margin-top:1px;display:flex;align-items:center;justify-content:center;font-size:9px} .tck.done{background:var(--grn);border-color:var(--grn);color:#fff} .ttx{font-size:12px;color:var(--tx2);flex:1;line-height:1.4}.ttx.done{text-decoration:line-through;color:var(--tx3)} .tass{font-size:10px;color:var(--acc2);background:var(--glow);padding:2px 7px;border-radius:20px;white-space:nowrap;flex-shrink:0} .ni2{padding:10px 15px;border-bottom:1px solid var(--brd);cursor:pointer;transition:background .15s} .ni2:last-child{border-bottom:none}.ni2:hover{background:var(--sur2)} .nt{font-size:12px;font-weight:500;color:var(--tx)} .np{font-size:11px;color:var(--tx3);margin-top:2px;line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical} .nf{display:flex;gap:8px;margin-top:4px;font-size:10px;color:var(--tx3)} .ai{padding:9px 15px;border-bottom:1px solid var(--brd);display:flex;gap:9px;align-items:flex-start} .ai:last-child{border-bottom:none} .aav{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;flex-shrink:0} .atx{font-size:12px;color:var(--tx2);line-height:1.4;flex:1}.atx strong{color:var(--tx);font-weight:500} .atm{font-size:10px;color:var(--tx3);margin-top:1px;font-family:var(--mo)} .dli{padding:9px 15px;border-bottom:1px solid var(--brd);display:flex;align-items:center;gap:10px} .dli:last-child{border-bottom:none} .dld2{width:7px;height:7px;border-radius:50%;flex-shrink:0} .dlin{flex:1;min-width:0}.dlnm{font-size:12px;font-weight:500;color:var(--tx)}.dlmt{font-size:10px;color:var(--tx3);margin-top:1px} .dlbdg{font-size:11px;font-weight:600;font-family:var(--mo);white-space:nowrap} .dlbdg.u{color:var(--red)}.dlbdg.s{color:var(--amb)}.dlbdg.o{color:var(--grn)} .fr{padding:9px 15px;border-bottom:1px solid var(--brd);display:flex;justify-content:space-between;align-items:center} .fr:last-child{border-bottom:none} .fl{font-size:12px;color:var(--tx2)}.fv{font-size:13px;font-weight:600;font-family:var(--mo)} .fv.g{color:var(--grn)}.fv.a{color:var(--amb)}.fv.r{color:var(--red)} .chips{display:flex;gap:5px;flex-wrap:wrap} .chip{font-size:11px;padding:3px 9px;border-radius:20px;border:1px solid var(--brd);color:var(--tx3);cursor:pointer;background:none;transition:all .15s} .chip:hover{border-color:var(--brd2);color:var(--tx2)}.chip.active{background:var(--glow);border-color:rgba(108,99,255,.25);color:var(--acc2)} .hg{display:grid;grid-template-columns:repeat(auto-fill,minmax(265px,1fr));gap:13px} .hcard{background:var(--sur);border:1px solid var(--brd);border-radius:12px;padding:15px} .hcard.warn{border-color:var(--amb)} .hst{background:var(--sur2);border-radius:8px;padding:7px 9px} .hsl{font-size:10px;color:var(--tx3);margin-bottom:2px}.hsv{font-size:12px;font-weight:500;color:var(--tx)} .ssl-ok{color:var(--grn)}.ssl-warn{color:var(--amb)}.ssl-bad{color:var(--red)} .cg{display:grid;grid-template-columns:repeat(auto-fill,minmax(245px,1fr));gap:13px} .cc{background:var(--sur);border:1px solid var(--brd);border-radius:12px;padding:15px;cursor:pointer} .cc:hover{border-color:var(--brd2)} .pwr{padding:11px 17px;border-bottom:1px solid var(--brd);display:flex;align-items:center;gap:12px;transition:background .15s} .pwr:hover{background:var(--sur2)}.pwr:last-child{border-bottom:none} .pws{font-size:13px;font-weight:500;color:var(--tx);flex:1} .pwu{font-size:11px;color:var(--tx3)} .pwm{font-size:12px;font-family:var(--mo);color:var(--tx3);background:var(--sur3);padding:3px 10px;border-radius:6px;cursor:pointer;transition:all .15s} .pwm:hover{color:var(--acc2);background:var(--glow)} .pwtop{padding:13px 17px;border-bottom:1px solid var(--brd);display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap} .pwgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px;padding:14px 17px} .pwclient{background:var(--sur2);border:1px solid var(--brd);border-radius:12px;padding:14px;cursor:pointer;transition:all .15s} .pwclient:hover{border-color:var(--acc);background:var(--glow)} .pwcn{font-size:14px;font-weight:600;color:var(--tx);margin-bottom:3px} .pwcm{font-size:11px;color:var(--tx3);line-height:1.45} .pwcstat{display:flex;gap:7px;flex-wrap:wrap;margin-top:11px} .pwchip{font-size:10px;padding:3px 8px;border-radius:20px;background:var(--sur);border:1px solid var(--brd);color:var(--tx3)} .pwhead{padding:13px 17px;border-bottom:1px solid var(--brd);display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;background:var(--sur2)} .pwtitle{font-size:14px;font-weight:600;color:var(--tx)} .pwsub{font-size:11px;color:var(--tx3);margin-top:2px} .ot{width:100%;border-collapse:collapse} .ot th{font-size:10px;color:var(--tx3);text-transform:uppercase;letter-spacing:.08em;padding:10px 15px;text-align:left;border-bottom:1px solid var(--brd);font-weight:500} .ot td{padding:11px 15px;border-bottom:1px solid var(--brd);font-size:12px;color:var(--tx2)} .ot tr:last-child td{border-bottom:none}.ot tr:hover td{background:var(--sur2);color:var(--tx)} .obtn{font-size:10px;padding:3px 8px;border-radius:5px;border:1px solid var(--brd);background:none;color:var(--tx3);cursor:pointer;font-family:var(--fn);transition:all .15s;margin-right:4px} .obtn:hover{background:var(--sur2);color:var(--tx)} .rg{display:grid;grid-template-columns:1fr 1fr;gap:16px} .br{display:flex;align-items:center;gap:10px;margin-bottom:9px} .brl{font-size:11px;color:var(--tx3);width:110px;flex-shrink:0} .brt{flex:1;height:7px;background:var(--sur2);border-radius:4px;overflow:hidden} .brf{height:100%;border-radius:4px;transition:width .6s ease} .brv{font-size:11px;font-family:var(--mo);color:var(--tx2);min-width:70px;text-align:right} .ur{padding:12px 17px;border-bottom:1px solid var(--brd);display:flex;align-items:center;gap:14px;transition:background .15s} .ur:last-child{border-bottom:none}.ur:hover{background:var(--sur2)} .si{background:var(--sur2);border:1px solid var(--brd);border-radius:8px;padding:6px 11px;color:var(--tx);font-family:var(--fn);font-size:12px;outline:none;width:155px;transition:border-color .15s} .si:focus{border-color:var(--acc)} .modgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px;padding:16px} .modcard{background:var(--sur2);border:1px solid var(--brd);border-radius:12px;padding:14px;display:flex;align-items:center;justify-content:space-between;gap:12px} .modname{font-size:13px;font-weight:600;color:var(--tx)} .modhint{font-size:11px;color:var(--tx3);margin-top:3px} .sw{width:44px;height:24px;border-radius:30px;border:1px solid var(--brd2);background:var(--sur3);position:relative;cursor:pointer;flex-shrink:0} .sw:before{content:'';position:absolute;width:18px;height:18px;border-radius:50%;background:var(--tx3);top:2px;left:3px;transition:.18s} .sw.on{background:var(--gdim);border-color:rgba(34,197,94,.35)} .sw.on:before{left:21px;background:var(--grn)} .adminnote{margin:16px;padding:12px 14px;border:1px solid var(--brd);border-radius:10px;background:var(--sur2);font-size:12px;color:var(--tx2);line-height:1.5} .calg{display:grid;grid-template-columns:1fr 300px;gap:18px;align-items:start} .cald{display:grid;grid-template-columns:repeat(7,1fr);gap:3px} .call{font-size:10px;color:var(--tx3);text-align:center;padding:5px 0;font-weight:600;text-transform:uppercase} .calday{min-height:68px;background:var(--sur);border:1px solid var(--brd);border-radius:7px;padding:5px;cursor:pointer;transition:all .15s;overflow:hidden} .calday:hover{border-color:var(--acc);background:var(--sur2)} .calday.today{border-color:var(--acc);background:var(--glow)} .calday.om{opacity:.3;pointer-events:none} .calday.sel{border-color:var(--acc2);box-shadow:0 0 0 2px rgba(167,139,250,.2)} .caldn{font-size:11px;font-weight:600;color:var(--tx);margin-bottom:3px} .calday.today .caldn{color:var(--acc2)} .calev{font-size:9px;padding:1px 4px;border-radius:3px;margin-bottom:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--glow);color:var(--acc2)} .mo{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:9998;align-items:center;justify-content:center} .mo.open{display:flex} .mbox{background:var(--sur);border:1px solid var(--brd2);border-radius:16px;padding:24px;width:520px;max-width:96vw;max-height:90vh;overflow-y:auto;animation:fi .2s ease;box-shadow:var(--shd)} .mbox h3{font-size:15px;font-weight:500;margin-bottom:17px;color:var(--tx)} .mf{margin-bottom:11px}.mf label{display:block;font-size:11px;color:var(--tx3);margin-bottom:4px;text-transform:uppercase;letter-spacing:.06em} .mf input,.mf select,.mf textarea{width:100%;background:var(--sur2);border:1px solid var(--brd);border-radius:8px;padding:8px 10px;color:var(--tx);font-family:var(--fn);font-size:12px;outline:none;transition:border-color .15s} .mf input:focus,.mf select:focus,.mf textarea:focus{border-color:var(--acc)} .mf select option{background:var(--sur)} .mg{display:grid;grid-template-columns:1fr 1fr;gap:9px} .ma{display:flex;gap:8px;justify-content:flex-end;margin-top:16px} .toast{position:fixed;bottom:22px;right:22px;background:var(--sur);border:1px solid var(--brd2);border-radius:10px;padding:10px 16px;font-size:13px;color:var(--tx);z-index:9999;display:none;animation:su .3s ease;box-shadow:var(--shd)} .toast.show{display:block}.toast.ok{border-color:var(--grn);color:var(--grn)}.toast.err{border-color:var(--red);color:var(--red)} @keyframes fi{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} @keyframes su{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}} ::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--sur3);border-radius:2px} </style> </head> <body> <div class="lw" id="lw"> <div class="lb"> <img class="llogo" src="https://webtrixia.com/wp-content/uploads/2025/04/wwww.png" onerror="this.style.display='none'" alt=""> <div class="ltitle">Webtrixia Hub</div> <div class="lsub">Влезте с вашия акаунт</div> <div class="aind" style="flex-direction:column;align-items:stretch;gap:6px"> <div style="display:flex;align-items:center;gap:8px"> <div class="adot" id="adot"></div> <span id="asts" style="flex:1">Свързва се...</span> </div> <div style="display:flex;gap:6px"> <input id="apiUrlInput" placeholder="https://..." style="flex:1;background:transparent;border:none;outline:none;font-size:10px;font-family:var(--mo);color:var(--tx3)" onkeydown="if(event.key==='Enter')changeApi()"> <button onclick="changeApi()" style="font-size:10px;padding:2px 8px;border-radius:5px;background:var(--acc);color:#fff;border:none;cursor:pointer;font-family:var(--fn)">Смени</button> </div> </div> <div class="lf"><label>Потребителско име</label><input type="text" id="lu" placeholder="admin" onkeydown="if(event.key==='Enter')login()"></div> <div class="lf"><label>Парола</label><input type="password" id="lp" placeholder="••••••••" onkeydown="if(event.key==='Enter')login()"></div> <button class="lbtn" onclick="login()">Влез →</button> <div class="lerr" id="lerr">Грешно потребителско име или парола</div> </div> <button class="tbtn2" onclick="tgl()">☀️ / 🌙 Смени тема</button> </div> <aside class="sb" id="sb" style="display:none"> <div class="slogo"> <img src="https://webtrixia.com/wp-content/uploads/2025/04/wwww.png" onerror="this.style.display='none'" style="height:26px" alt=""> <div><div class="lsub2">Agency</div><div class="lver">Hub v4.0</div></div> </div> <nav> <div class="nsec">Основно</div> <button class="ni active" onclick="go('dashboard',this)"><span class="nic">⬡</span>Dashboard</button> <button class="ni" data-module="projects" onclick="go('projects',this)"><span class="nic">◈</span>Проекти<span class="nbadge g" id="nb-p">0</span></button> <button class="ni" data-module="clients" onclick="go('clients',this)"><span class="nic">◎</span>Клиенти</button> <button class="ni" data-module="finance" onclick="go('finance',this)"><span class="nic">◇</span>Финанси</button> <div class="nsec">Работа</div> <button class="ni" data-module="tasks" onclick="go('tasks',this)"><span class="nic">▷</span>Задачи<span class="nbadge" id="nb-t">0</span></button> <button class="ni" data-module="offers" onclick="go('offers',this)"><span class="nic">◻</span>Оферти</button> <button class="ni" data-module="contracts" onclick="go('contracts',this)"><span class="nic">◈</span>Договори</button> <button class="ni" data-module="calendar" onclick="go('calendar',this)"><span class="nic">📅</span>Календар</button> <button class="ni" data-module="invoicing" onclick="window.open('invoices.html','_blank')"><span class="nic">🧾</span>Фактуриране</button> <button class="ni" data-module="hr_requests" onclick="window.open('hr.html','_blank')"><span class="nic">📄</span>Молби</button> <button class="ni" data-module="waybills" onclick="window.open('waybills.html','_blank')"><span class="nic">🚚</span>Пътни листове</button> <div class="nsec">Агенция</div> <button class="ni" data-module="hosting" onclick="go('hosting',this)"><span class="nic">⬡</span>Хостинги<span class="nbadge a" id="nb-h">0</span></button> <button class="ni" data-module="passwords" onclick="go('passwords',this)"><span class="nic">◎</span>Достъпи</button> <button class="ni" data-module="notes" onclick="go('notes',this)"><span class="nic">◻</span>Бележки</button> <button class="ni" data-module="reports" onclick="go('reports',this)"><span class="nic">▷</span>Отчети</button> <div class="nsec">Екип</div> <button class="ni" data-module="presence" onclick="go('presence',this)"><span class="nic">🟢</span>Присъствие</button> <button class="ni" data-module="targets" onclick="go('targets',this)"><span class="nic">🎯</span>Таргети</button> <div id="admnav" style="display:none"> <div class="nsec">Админ</div> <button class="ni" data-module="workreport" onclick="go('workreport',this)"><span class="nic">⏱</span>Работен рипорт</button> <button class="ni" data-module="users" onclick="go('users',this)"><span class="nic">👤</span>Потребители</button> <button class="ni" onclick="go('modules_settings',this)"><span class="nic">⚙</span>Модули</button> <button class="ni" id="licenseNav" style="display:none" onclick="openLicensePanel()"><span class="nic">▣</span>Лицензи</button> </div> </nav> <div class="sbot"> <div class="nsec" style="padding-top:0">Екип</div> <div id="tlist"></div> </div> </aside> <main class="main" id="app" style="display:none"> <div class="topbar"> <div class="ttl" id="ptitle">Dashboard</div> <div class="tdate" id="tdate"></div> <button class="tbtn" onclick="tgl()" id="tbtn">☀️</button> <div class="ubdg" id="ubdg"></div> <button class="btn" onclick="modal('project')">+ Проект</button> <button class="btn p" onclick="window.open('offer.html?type=one_time','_blank')">⊕ Оферта</button> <button class="btn" onclick="out()" style="padding:6px 10px">⊗</button> </div> <div class="con"> <div id="sec-dashboard" class="sec active"> <div class="kg"> <div class="kc"><div class="kl">Проекти</div><div class="kv ac" id="kp">—</div><div class="ks">активни</div><div class="kg2" style="background:var(--acc)"></div></div> <div class="kc"><div class="kl">Оферирано</div><div class="kv" id="ko">—</div><div class="ks">общо</div><div class="kg2" style="background:var(--blu)"></div></div> <div class="kc"><div class="kl">Получено</div><div class="kv g" id="kr">—</div><div class="ks">потвърдено</div><div class="kg2" style="background:var(--grn)"></div></div> <div class="kc"><div class="kl">Очаква се</div><div class="kv a" id="kpn">—</div><div class="ks">неплатено</div><div class="kg2" style="background:var(--amb)"></div></div> <div class="kc"><div class="kl">Задачи</div><div class="kv r" id="kt">—</div><div class="ks">незавършени</div><div class="kg2" style="background:var(--red)"></div></div> </div> <div class="g2"> <div class="panel"><div class="ph"><div class="pt">Последни проекти</div><button class="pa" onclick="go('projects',null)">Виж всички</button></div><div id="dp"><div class="loading">Зарежда</div></div></div> <div> <div class="panel"><div class="ph"><div class="pt">⚡ Срокове</div></div><div id="dd"><div class="loading">Зарежда</div></div></div> <div class="panel"><div class="ph"><div class="pt">💰 Финанси</div></div><div id="df"></div></div> </div> </div> <div class="g3"> <div class="panel"><div class="ph"><div class="pt">✓ Задачи</div><button class="pa" onclick="modal('task')">+ Добави</button></div><div id="dt"><div class="loading">Зарежда</div></div></div> <div class="panel"><div class="ph"><div class="pt">📋 Бележки</div></div><div id="dn"><div class="loading">Зарежда</div></div></div> <div class="panel"><div class="ph"><div class="pt">⟳ Активност</div></div><div id="da"><div class="loading">Зарежда</div></div></div> </div> </div> <div id="sec-projects" class="sec"><div class="panel"><div class="ph"><div class="pt">Всички проекти</div><div style="display:flex;gap:8px"><input class="si" type="text" placeholder="Търси..." oninput="fp(this.value)"><button class="btn p" onclick="modal('project')">+ Нов</button></div></div><div id="fp"><div class="loading">Зарежда</div></div></div></div> <div id="sec-clients" class="sec"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px"><span style="font-size:12px;color:var(--tx3)" id="cs">—</span><button class="btn p" onclick="modal('client')">+ Нов клиент</button></div><div class="cg" id="cg"><div class="loading">Зарежда</div></div></div> <div id="sec-finance" class="sec"> <div class="kg" style="grid-template-columns:repeat(4,1fr);margin-bottom:16px"> <div class="kc"><div class="kl">Оборот общо</div><div class="kv ac" id="ft">—</div></div> <div class="kc"><div class="kl">Получено</div><div class="kv g" id="fr">—</div></div> <div class="kc"><div class="kl">Очаква се</div><div class="kv a" id="fpn">—</div></div> <div class="kc"><div class="kl">Абонаменти/мес</div><div class="kv g" id="fs">—</div></div> </div> <div class="g22"><div class="panel"><div class="ph"><div class="pt">Неплатени</div></div><div id="fu"></div></div><div class="panel"><div class="ph"><div class="pt">Абонаменти</div></div><div id="fsl"></div></div></div> </div> <div id="sec-tasks" class="sec"><div class="panel"><div class="ph"><div class="pt">Задачи</div><div style="display:flex;gap:8px"><div class="chips"><div class="chip active" onclick="chipS(this)">Всички</div><div class="chip" onclick="chipS(this)">Незавършени</div><div class="chip" onclick="chipS(this)">Спешни</div></div><button class="btn p" onclick="modal('task')">+ Задача</button></div></div><div id="tf"><div class="loading">Зарежда</div></div></div></div> <div id="sec-offers" class="sec"><div class="panel"><div class="ph"><div class="pt">Оферти</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input class="si" style="width:220px" type="text" placeholder="Търси оферта..." oninput="OF.q=this.value;rOffers()"><select class="si" style="width:145px" onchange="OF.status=this.value;rOffers()"><option value="">Всички</option><option>Чакаща</option><option>Приета</option><option>Отказана</option></select><button class="btn s" onclick="window.open('offer.html?type=monthly','_blank')">+ Месечна</button><button class="btn p" onclick="window.open('offer.html?type=one_time','_blank')">⊕ Нова оферта</button></div></div><div id="of"><div class="loading">Зарежда</div></div></div></div> <div id="sec-contracts" class="sec"><div class="panel"><div class="ph"><div class="pt">Договори</div><div style="display:flex;gap:8px"><button class="btn p" onclick="modal('contract')">+ Нов</button><button class="btn" onclick="window.open('contracts.html','_blank')">Генерирай договор</button></div></div><div id="cf"><div class="loading">Зарежда</div></div></div></div> <div id="sec-calendar" class="sec"><div id="calf"></div></div> <div id="sec-hosting" class="sec"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px"><span style="font-size:12px;color:var(--tx3)" id="hs">—</span><button class="btn p" onclick="modal('hosting')">+ Нов хостинг</button></div><div class="hg" id="hg"><div class="loading">Зарежда</div></div></div> <div id="sec-passwords" class="sec"><div class="panel"><div class="ph"><div><div class="pt">🔐 Достъпи по клиенти</div><div style="font-size:11px;color:var(--tx3);margin-top:2px">Търси клиент, отвори го и виж само неговите достъпи.</div></div><button class="btn p" onclick="openPassModal()">+ Добави достъп</button></div><div class="pwtop"><input class="si" id="pwSearch" style="width:260px" type="text" placeholder="Търси клиент..." oninput="PW.search=this.value;rPasswords()"><button class="btn" onclick="PW.client='';PW.search='';var s=document.getElementById('pwSearch');if(s)s.value='';rPasswords()">Всички клиенти</button></div><div id="pwl"><div class="loading">Зарежда</div></div></div></div> <div id="sec-notes" class="sec"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px"><span style="font-size:12px;color:var(--tx3)">Бележки</span><button class="btn p" onclick="modal('note')">+ Нова</button></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:14px" id="ng"></div></div> <div id="sec-reports" class="sec"><div class="rg"><div class="panel"><div class="ph"><div class="pt">Приходи по месеци</div></div><div style="padding:15px 17px" id="rr"></div></div><div class="panel"><div class="ph"><div class="pt">По тип услуга</div></div><div style="padding:15px 17px" id="rs"></div></div><div class="panel"><div class="ph"><div class="pt">Топ клиенти</div></div><div style="padding:15px 17px" id="rc"></div></div><div class="panel"><div class="ph"><div class="pt">Натоварване екип</div></div><div style="padding:15px 17px" id="rt"></div></div></div></div> <div id="sec-presence" class="sec"><div id="pref"><div class="loading">Зарежда</div></div></div> <div id="sec-targets" class="sec"><div id="tgf"><div class="loading">Зарежда</div></div></div> <div id="sec-workreport" class="sec"><div id="wrf"><div class="loading">Зарежда</div></div></div> <div id="sec-users" class="sec"><div class="panel"><div class="ph"><div class="pt">👤 Потребители</div><button class="btn p" onclick="modal('newUser')">+ Нов служител</button></div><div id="ul"><div class="loading">Зарежда</div></div></div></div> <div id="sec-modules_settings" class="sec"><div class="panel"><div class="ph"><div><div class="pt">Модули в този хъб</div><div style="font-size:11px;color:var(--tx3);margin-top:2px">Администраторът може да скрива функции от лявото меню според клиента.</div></div><button class="btn" onclick="rModules()">Обнови</button></div><div id="modlist"><div class="loading">Зарежда</div></div></div></div> </div> </main> <div class="mo" id="mo" onclick="if(event.target===this)cmo()"><div class="mbox" id="mb"></div></div> <div class="toast" id="toast"></div> <script> 'use strict'; var API=(function(){ var base=window.location.origin+window.location.pathname.replace(/index\.html$/,'').replace(/\/$/,''); var own=base+'/api.php'; var saved=localStorage.getItem('wt_api'); if(saved){ try{ var u=new URL(saved,window.location.href); if(u.origin===window.location.origin)return saved; localStorage.removeItem('wt_api'); }catch(e){localStorage.removeItem('wt_api');} } return own; })(); var CU=null; var D={projects:[],tasks:[],notes:[],clients:[],hosting:[],passwords:[],offers:[],contracts:[],users:[],activity:[]}; var PW={client:'',search:'',formClient:''}; var OF={q:'',status:''}; var moduleWatchStarted=false; var licenseSyncBusy=false; var MODULE_LABELS={ projects:'Проекти',clients:'Клиенти',finance:'Финанси',tasks:'Задачи',offers:'Оферти',contracts:'Договори', calendar:'Календар',invoicing:'Фактуриране',hr_requests:'Молби',waybills:'Пътни листове', hosting:'Хостинги',passwords:'Достъпи',notes:'Бележки',reports:'Отчети',presence:'Присъствие',targets:'Таргети', workreport:'Работен рипорт',users:'Потребители' }; var ABG={'av-p':'rgba(108,99,255,.25)','av-t':'rgba(20,184,166,.25)','av-g':'rgba(34,197,94,.2)','av-a':'rgba(245,158,11,.2)','av-r':'rgba(239,68,68,.2)','av-b':'rgba(56,189,248,.2)'}; var ACV={'av-p':'#a78bfa','av-t':'#2dd4bf','av-g':'#22c55e','av-a':'#f59e0b','av-r':'#ef4444','av-b':'#38bdf8'}; // ── THEME ── function tgl(){ var c=document.documentElement.getAttribute('data-theme'); var n=c==='dark'?'light':'dark'; document.documentElement.setAttribute('data-theme',n); localStorage.setItem('wt_theme',n); var b=document.getElementById('tbtn'); if(b)b.textContent=n==='dark'?'☀️':'🌙'; } (function(){ var b=document.getElementById('tbtn'); var t=document.documentElement.getAttribute('data-theme'); if(b)b.textContent=t==='dark'?'☀️':'🌙'; })(); // ── API ── async function af(action,method,body,extra){ method=method||'GET';extra=extra||''; try{ var o={method:method,headers:{'Content-Type':'application/json'},credentials:'same-origin'}; if(body)o.body=JSON.stringify(body); var r=await fetch(API+'?action='+action+extra,o); var j=await r.json(); if(!r.ok){ if(r.status===402) renderLicenseLock((j&&j.message)||(j&&j.license&&j.license.message)||'Лицензът не е активен.'); return null; } return j.ok?j.data:null; }catch(e){console.error(action,e.message);return null;} } // ── VERIFY ── (async function(){ var d=document.getElementById('adot'); var s=document.getElementById('asts'); var r=await af('stats'); if(r!==null){ d.className='adot ok'; s.textContent='✓ Свързано — влезте'; s.style.color='#22c55e'; var inp=document.getElementById('apiUrlInput'); if(inp)inp.value=API; }else{ d.className='adot err'; s.textContent='✗ Няма връзка — въведи правилния URL'; s.style.color='#ef4444'; var inp=document.getElementById('apiUrlInput'); if(inp)inp.value=API; } })(); // ── CHANGE API ── function changeApi(){ var inp=document.getElementById('apiUrlInput'); if(!inp||!inp.value.trim())return; var url=inp.value.trim(); if(!url.endsWith('.php'))url=url.replace(/\/$/,'')+'/api.php'; API=url; localStorage.setItem('wt_api',url); verifyApi(); } // ── SESSION ── (function(){ try{ var s=sessionStorage.getItem('wt_s'); if(s){var u=JSON.parse(s);if(u&&u.id)showApp(u);} }catch(e){sessionStorage.removeItem('wt_s');} })(); // ── LOGIN ── async function login(){ var u=document.getElementById('lu').value.trim(); var p=document.getElementById('lp').value; if(!u||!p){serr('Попълнете всички полета');return;} var r=await af('login','POST',{username:u,password:p}); if(r){sessionStorage.setItem('wt_s',JSON.stringify(r));showApp(r);} else{serr('Грешно потребителско име или парола');document.getElementById('lp').value='';} } function serr(m){ var e=document.getElementById('lerr');e.textContent=m;e.style.display='block'; setTimeout(function(){e.style.display='none';},3000); } function showApp(u){ CU=u; document.getElementById('lw').style.display='none'; document.getElementById('sb').style.display='flex'; document.getElementById('app').style.display='flex'; if(u.is_admin==1)document.getElementById('admnav').style.display='block'; var col=u.color||'av-p'; document.getElementById('ubdg').innerHTML='<div style="width:22px;height:22px;border-radius:50%;background:'+ABG[col]+';color:'+ACV[col]+';display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700">'+(u.initials||'?')+'</div><span>'+u.name+'</span>'+(u.is_admin==1?'<span style="font-size:10px;color:#f59e0b">👑</span>':''); initApp(); startModuleWatch(); } function out(){ CU=null;sessionStorage.removeItem('wt_s'); document.getElementById('lw').style.display='flex'; document.getElementById('sb').style.display='none'; document.getElementById('app').style.display='none'; document.getElementById('lu').value='';document.getElementById('lp').value=''; } // ── INIT ── function initApp(){ var d=new Date(); var days=['Неделя','Понеделник','Вторник','Сряда','Четвъртък','Петък','Събота']; var mons=['яну','фев','мар','апр','май','юни','юли','авг','сеп','окт','ное','дек']; document.getElementById('tdate').textContent=days[d.getDay()]+', '+d.getDate()+' '+mons[d.getMonth()]+' '+d.getFullYear(); var t=document.documentElement.getAttribute('data-theme'); var b=document.getElementById('tbtn');if(b)b.textContent=t==='dark'?'☀️':'🌙'; refreshLicenseAndModules().then(function(ok){ if(ok!==false) loadAll(); }); } // ── MODULE VISIBILITY (respects remote toggles from w_admin.php / master hub) ── async function applyModuleVisibility(){ try{ var r=await af('branding_settings'); if(!r||!r.modules_enabled)return; if(r.license_state==='locked'||r.license_state==='invalid'){ renderLicenseLock(r.license_message||'Лицензът не е активен.'); return false; } var mods=r.modules_enabled; document.querySelectorAll('[data-module]').forEach(function(el){ var key=el.getAttribute('data-module'); if(mods.hasOwnProperty(key) && !mods[key]) el.style.display='none'; else el.style.display=''; }); if(r.product_name){ var lsub=document.querySelector('.lsub2'); if(lsub) lsub.textContent=r.product_name.toUpperCase(); } var ln=document.getElementById('licenseNav'); if(ln) ln.style.display=(CU&&CU.is_admin==1&&r.license_role==='master')?'':'none'; return true; }catch(e){} } async function syncLicenseFromMaster(){ if(licenseSyncBusy)return; licenseSyncBusy=true; try{ var cfg=await af('license_client_config'); if(!cfg||!cfg.verify_url)return; var rr=await fetch(cfg.verify_url,{cache:'no-store',mode:'cors'}); var payload=await rr.json(); if(payload&&payload.state) await af('license_sync','POST',{payload:payload}); }catch(e){ console.warn('license sync',e.message); }finally{ licenseSyncBusy=false; } } async function refreshLicenseAndModules(){ await syncLicenseFromMaster(); return await applyModuleVisibility(); } function startModuleWatch(){ if(moduleWatchStarted)return; moduleWatchStarted=true; setInterval(function(){ if(CU) refreshLicenseAndModules(); },6000); window.addEventListener('focus',function(){ if(CU) refreshLicenseAndModules(); }); } function renderLicenseLock(msg){ document.body.innerHTML='<div style="min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;background:#0a0a12;color:#ececf5;font-family:DM Sans,sans-serif">'+ '<div style="max-width:460px;text-align:center;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:18px;padding:38px 30px">'+ '<div style="font-size:46px;margin-bottom:12px">🔒</div><h2 style="font-size:22px;margin-bottom:10px">Хъбът е заключен</h2>'+ '<p style="color:#a0a0c0;font-size:14px;line-height:1.6">'+esc(msg)+'</p>'+ '<p style="color:#6f6f8e;font-size:12px;margin-top:14px">Свържете се с доставчика за подновяване.</p></div></div>'; } function openLicensePanel(){ window.open('licenses/admin/index.php','_blank'); } async function rModules(){ var el=document.getElementById('modlist'); if(!el)return; if(!CU||CU.is_admin!=1){el.innerHTML='<div class="adminnote">Само администратор може да управлява модулите.</div>';return;} el.innerHTML='<div class="loading">Зарежда</div>'; var r=await af('branding_settings'); var mods=(r&&r.modules_enabled)?r.modules_enabled:{}; var keys=Object.keys(MODULE_LABELS); el.innerHTML='<div class="adminnote">Изключените модули се скриват от лявото меню. Лицензният панел може допълнително да ограничава модулите за клиентски хъбове.</div>' +'<div class="modgrid">'+keys.map(function(k){ var on=!mods.hasOwnProperty(k)||!!Number(mods[k]); return '<div class="modcard"><div><div class="modname">'+MODULE_LABELS[k]+'</div><div class="modhint">'+(on?'Активен':'Скрит от менюто')+'</div></div><button class="sw '+(on?'on':'')+'" title="Включи/изключи" onclick="toggleModule('+jsArg(k)+','+(on?0:1)+')"></button></div>'; }).join('')+'</div>'; } async function toggleModule(key,enabled){ var r=await af('module_toggle','POST',{module:key,enabled:enabled}); if(r){ toast(enabled?'Модулът е включен':'Модулът е изключен','ok'); await applyModuleVisibility(); await rModules(); }else{ toast('Нужни са администраторски права','err'); } } // ── NAV ── function go(sec,el){ document.querySelectorAll('.sec').forEach(function(s){s.classList.remove('active');}); var s=document.getElementById('sec-'+sec);if(s)s.classList.add('active'); if(el){document.querySelectorAll('.ni').forEach(function(n){n.classList.remove('active');});el.classList.add('active');} var T={dashboard:'Dashboard',workreport:'Работен рипорт',targets:'Дневни таргети',projects:'Проекти',clients:'Клиенти',finance:'Финанси',tasks:'Задачи',offers:'Оферти',contracts:'Договори',calendar:'Календар',hosting:'Хостинги',passwords:'Достъпи',notes:'Бележки',reports:'Отчети',presence:'Присъствие',users:'Потребители',modules_settings:'Модули'}; document.getElementById('ptitle').textContent=T[sec]||sec; ls(sec); } // ── FORMAT ── function fmt(n){return '€ '+Number(n||0).toLocaleString('de-DE',{minimumFractionDigits:2,maximumFractionDigits:2});} function fd(s){if(!s)return '';try{return new Date(s).toLocaleDateString('bg-BG',{day:'numeric',month:'short',year:'numeric'});}catch(e){return s;}} function fdt(s){if(!s)return '';try{return new Date(s).toLocaleDateString('bg-BG',{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'});}catch(e){return s;}} function pj(s,d){try{return JSON.parse(s||'[]');}catch(e){return d;}} function esc(s){return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');} function jsArg(s){return esc(JSON.stringify(String(s||'')));} function gv(id){var e=document.getElementById(id);return e?e.value.trim():'';} // ── LOAD ── async function loadAll(){ var R=await Promise.all([af('projects'),af('tasks'),af('notes'),af('clients'),af('hosting'),af('passwords'),af('offers'),af('contracts'),af('users'),af('activity'),af('stats')]); D.projects=(R[0]||[]).map(function(p){return Object.assign({},p,{tags:pj(p.tags,[])});}); D.tasks=R[1]||[];D.notes=R[2]||[];D.clients=R[3]||[];D.hosting=R[4]||[]; D.passwords=R[5]||[];D.offers=R[6]||[];D.contracts=R[7]||[];D.users=R[8]||[];D.activity=R[9]||[]; var st=R[10]; if(st){ document.getElementById('kp').textContent=st.active_projects; document.getElementById('ko').textContent=fmt(st.total_offered); document.getElementById('kr').textContent=fmt(st.total_received); document.getElementById('kpn').textContent=fmt(st.total_pending); document.getElementById('kt').textContent=st.tasks_pending; document.getElementById('nb-p').textContent=st.active_projects; document.getElementById('nb-t').textContent=st.tasks_pending; document.getElementById('nb-h').textContent=st.ssl_warnings; } rTeam();rDash(); } async function ls(s){ if(s==='dashboard'){await loadAll();return;} var m={ projects:async function(){D.projects=((await af('projects'))||[]).map(function(p){return Object.assign({},p,{tags:pj(p.tags,[])});});rPrj('');}, clients:async function(){D.clients=await af('clients')||[];rCli();}, finance:async function(){D.projects=((await af('projects'))||[]).map(function(p){return Object.assign({},p,{tags:pj(p.tags,[])});});rFin();}, tasks:async function(){D.tasks=await af('tasks')||[];rTasks();}, offers:async function(){D.offers=await af('offers')||[];rOffers();}, contracts:async function(){D.contracts=await af('contracts')||[];rContracts();}, hosting:async function(){D.hosting=await af('hosting')||[];rHosting();}, passwords:async function(){D.passwords=await af('passwords')||[];rPasswords();}, notes:async function(){D.notes=await af('notes')||[];rNotes();}, reports:async function(){D.projects=((await af('projects'))||[]).map(function(p){return Object.assign({},p,{tags:pj(p.tags,[])});});D.tasks=await af('tasks')||[];rReports();}, presence:async function(){if(!D.users.length)D.users=await af('users')||[];rPresence();}, calendar:async function(){if(!D.tasks.length)D.tasks=await af('tasks')||[];if(!D.users.length)D.users=await af('users')||[];rCalendar();}, users:async function(){D.users=await af('users')||[];rUsers();}, workreport:async function(){if(!D.users.length)D.users=await af('users')||[];rWorkReport();}, targets:async function(){if(!D.users.length)D.users=await af('users')||[];rTargets();}, modules_settings:async function(){rModules();} }; if(m[s])await m[s](); } // ── HELPERS ── var TM={WooCommerce:'web',WordPress:'web','E-commerce':'web',SEO:'seo','Google Ads':'seo',Instagram:'social',Facebook:'social',Viber:'social',cPanel:'hosting',Поддръжка:'hosting',Лого:'design',Брандинг:'design',Дизайн:'design',Реклами:'social',Спешно:'rush'}; var SPC={active:'sp-a',new:'sp-n',review:'sp-r',paused:'sp-d',done:'sp-d'}; var SPL={active:'В процес',new:'Нов',review:'Ревизия',paused:'Пауза',done:'Завършен'}; function pRow(p){ var tags=(p.tags||[]).map(function(t){return '<span class="tag '+(TM[t]||'web')+'">'+t+'</span>';}).join(''); var tot=Number(p.total||0),rec=Number(p.received||0); var rv=p.recurring?fmt(tot)+'/'+p.recurring:fmt(tot); var rl=rec>=tot&&tot>0?'<div class="ar">Платено ✓</div>':rec>0?'<div class="ar">'+fmt(rec)+' платено</div>':'<div class="ap">€ 0,00 платено</div>'; var dl=p.deadline?new Date(p.deadline):null; var dy=dl?Math.ceil((dl-new Date())/86400000):null; var ug=dy!==null&&dy<=7; var pr=p.progress||0; var bc=pr>=100?'var(--grn)':pr>=60?'var(--acc)':pr>=30?'var(--amb)':'var(--blu)'; return '<div class="pr" onclick="modal(\'editProject\',\''+p.id+'\')">' +'<div><div class="pn">'+p.name+'</div><div class="pm">'+(p.client||'—')+(p.phone?' · '+p.phone:'')+'</div>' +'<div class="ptags">'+tags+'</div>' +'<div class="pb"><div class="pbf" style="width:'+pr+'%;background:'+bc+'"></div></div></div>' +'<span class="sp '+(SPC[p.status]||'sp-n')+'">'+(SPL[p.status]||'Нов')+'</span>' +'<div class="amt"><div class="at">'+rv+'</div>'+rl+'</div>' +'<div class="dlc"><div class="dld'+(ug?' u':'')+'">'+( dl?dl.toLocaleDateString('bg-BG',{day:'numeric',month:'short'}):'—')+'</div>' +'<div class="dln'+(ug?' u':'')+'">'+( dy===null?'—':dy<=0?'Изтекъл':dy+' дни')+'</div></div>' +'</div>'; } function emp(ic,ti,su,bl,bf){ return '<div class="emp"><div class="ei">'+ic+'</div><div class="et">'+ti+'</div><div class="es">'+su+'</div>'+(bl?'<button class="btn p" onclick="'+bf+'">'+bl+'</button>':'')+'</div>'; } function tHTML(t){ return '<div class="ti" onclick="togTask(\''+t.id+'\')">' +'<div class="tck'+(t.done==1?' done':'')+'">'+( t.done==1?'✓':'')+'</div>' +'<div class="ttx'+(t.done==1?' done':'')+'">'+t.text+(t.urgent==1&&t.done!=1?'<span style="font-size:10px;color:var(--red);margin-left:4px">⚠</span>':'')+'</div>' +'<div class="tass">'+(t.assignee||'—')+'</div>' +'</div>'; } // ── DASHBOARD ── function rDash(){ var ac=D.projects.filter(function(p){return p.status!=='done';}); document.getElementById('dp').innerHTML=ac.length?ac.slice(0,4).map(pRow).join(''):emp('◈','Нямате проекти','','+ Нов проект',"modal('project')"); var up=D.projects.filter(function(p){return p.deadline&&p.status!=='done';}).sort(function(a,b){return new Date(a.deadline)-new Date(b.deadline);}).slice(0,5); document.getElementById('dd').innerHTML=up.length?up.map(function(p){ var dy=Math.ceil((new Date(p.deadline)-new Date())/86400000); var cl=dy<=3?'u':dy<=10?'s':'o'; var cr=dy<=3?'var(--red)':dy<=10?'var(--amb)':'var(--grn)'; return '<div class="dli"><div class="dld2" style="background:'+cr+'"></div><div class="dlin"><div class="dlnm">'+p.name+'</div><div class="dlmt">'+(p.client||'')+'</div></div><div class="dlbdg '+cl+'">'+(dy<=0?'Изтекъл':dy+' дни')+'</div></div>'; }).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма предстоящи срокове</div>'; var tot=ac.reduce(function(s,p){return s+Number(p.total||0);},0); var rec=ac.reduce(function(s,p){return s+Number(p.received||0);},0); document.getElementById('df').innerHTML='<div class="fr"><span class="fl">Оферирано</span><span class="fv">'+fmt(tot)+'</span></div><div class="fr"><span class="fl">Получено</span><span class="fv g">'+fmt(rec)+'</span></div><div class="fr"><span class="fl">Очаква се</span><span class="fv a">'+fmt(tot-rec)+'</span></div>'; document.getElementById('dt').innerHTML=D.tasks.length?D.tasks.slice(0,6).map(tHTML).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма задачи</div>'; document.getElementById('dn').innerHTML=D.notes.length?D.notes.slice(0,3).map(function(n){return '<div class="ni2"><div class="nt">'+n.title+'</div><div class="np">'+(n.body||'')+'</div><div class="nf"><span>'+(n.author||'')+'</span>'+(n.author?'<span>·</span>':'')+'<span>'+fdt(n.created_at)+'</span></div></div>';}).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма бележки</div>'; document.getElementById('da').innerHTML=D.activity.length?D.activity.slice(0,6).map(function(a){return '<div class="ai"><div class="aav '+(a.color||'av-p')+'">'+(a.initials||'?')+'</div><div><div class="atx"><strong>'+a.who+'</strong> '+a.what+'</div><div class="atm">'+fdt(a.created_at)+'</div></div></div>';}).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма активност</div>'; } function rTeam(){ document.getElementById('tlist').innerHTML=D.users.map(function(u){ return '<div class="trow"><div class="av '+(u.color||'av-p')+'">'+(u.initials||'?')+'</div><div style="flex:1"><div style="font-size:11px;font-weight:500;color:var(--tx)">'+u.name+'</div><div style="font-size:10px;color:var(--tx3)">'+(u.role||'')+'</div></div></div>'; }).join(''); } function rPrj(q){ var list=D.projects; if(q)list=list.filter(function(p){return p.name.toLowerCase().includes(q.toLowerCase())||(p.client||'').toLowerCase().includes(q.toLowerCase());}); document.getElementById('fp').innerHTML=list.length?list.map(pRow).join(''):emp('◈','Нямате проекти','','+ Нов проект',"modal('project')"); } function fp(q){rPrj(q);} function rCli(){ document.getElementById('cs').textContent=D.clients.length+' клиент'+(D.clients.length===1?'':'а'); if(!D.clients.length){document.getElementById('cg').innerHTML=emp('◎','Нямате клиенти','','+ Нов клиент',"modal('client')");return;} var cls=['av-p','av-t','av-g','av-a']; document.getElementById('cg').innerHTML=D.clients.map(function(c,i){ var pjs=D.projects.filter(function(p){return p.client===c.name;}).length; var tot=D.projects.filter(function(p){return p.client===c.name;}).reduce(function(s,p){return s+Number(p.total||0);},0); var col=cls[i%4]; return '<div class="cc"><div style="display:flex;align-items:center;gap:10px;margin-bottom:12px"><div class="av '+col+'" style="width:36px;height:36px;font-size:12px">'+(c.name||'?').slice(0,2).toUpperCase()+'</div><div><div style="font-size:13px;font-weight:500;color:var(--tx)">'+c.name+'</div><div style="font-size:11px;color:var(--tx3)">'+(c.company||'')+'</div></div></div>' +'<div style="display:flex;gap:10px"><div style="text-align:center;flex:1"><div style="font-size:15px;font-weight:600;font-family:var(--mo);color:var(--tx)">'+pjs+'</div><div style="font-size:10px;color:var(--tx3)">Проекта</div></div><div style="text-align:center;flex:1"><div style="font-size:15px;font-weight:600;font-family:var(--mo);color:var(--grn)">'+fmt(tot)+'</div><div style="font-size:10px;color:var(--tx3)">Общо</div></div></div>' +'<div style="margin-top:11px;padding-top:11px;border-top:1px solid var(--brd)">'+(c.email?'<div style="font-size:11px;color:var(--tx3);margin-bottom:2px">📧 <span style="color:var(--tx2)">'+c.email+'</span></div>':'')+(c.phone?'<div style="font-size:11px;color:var(--tx3)">📞 <span style="color:var(--tx2)">'+c.phone+'</span></div>':'')+'</div></div>'; }).join(''); } function rFin(){ var tot=D.projects.reduce(function(s,p){return s+Number(p.total||0);},0); var rec=D.projects.reduce(function(s,p){return s+Number(p.received||0);},0); var mon=D.projects.filter(function(p){return p.recurring;}).reduce(function(s,p){return s+Number(p.total||0);},0); document.getElementById('ft').textContent=fmt(tot);document.getElementById('fr').textContent=fmt(rec); document.getElementById('fpn').textContent=fmt(tot-rec);document.getElementById('fs').textContent=fmt(mon); var un=D.projects.filter(function(p){return Number(p.received||0)<Number(p.total||0)&&p.status!=='done';}); document.getElementById('fu').innerHTML=un.length?un.map(function(p){return '<div class="fr"><span class="fl">'+p.name+'</span><span class="fv a">'+fmt(Number(p.total||0)-Number(p.received||0))+'</span></div>';}).join(''):emp('◇','Всичко платено','','',''); var sb=D.projects.filter(function(p){return p.recurring;}); document.getElementById('fsl').innerHTML=sb.length?sb.map(function(p){return '<div class="fr"><span class="fl">'+p.name+'</span><span class="fv g">'+fmt(p.total)+'/'+p.recurring+'</span></div>';}).join(''):emp('◇','Нямате абонаменти','','',''); } function rTasks(){ document.getElementById('nb-t').textContent=D.tasks.filter(function(t){return t.done==0;}).length; document.getElementById('tf').innerHTML=D.tasks.length?D.tasks.map(tHTML).join(''):emp('▷','Нямате задачи','','+ Задача',"modal('task')"); } async function togTask(id){ var t=D.tasks.find(function(x){return x.id===id;});if(!t)return; t.done=t.done==1?0:1; await af('tasks','POST',{id:t.id,text:t.text,assignee:t.assignee,urgent:t.urgent,done:t.done,due_date:t.due_date||null}); rTasks();rDash();toast(t.done==1?'✓ Изпълнена':'Върната','ok'); } function rOffers(){ var el=document.getElementById('of'); if(!D.offers.length){el.innerHTML=emp('◻','Нямате оферти','','⊕ Нова оферта',"window.open('offer.html?type=one_time','_blank')");return;} var q=(OF.q||'').toLowerCase(); var rows=D.offers.filter(function(o){ var hay=[o.num,o.client,o.service,o.status,o.offer_type].join(' ').toLowerCase(); return (!q||hay.indexOf(q)>-1)&&(!OF.status||o.status===OF.status); }); if(!rows.length){el.innerHTML=emp('◻','Няма намерени оферти','Промени търсенето или филтъра.','','');return;} var accepted=rows.filter(function(o){return o.status==='Приета';}).length; var total=rows.reduce(function(s,o){return s+Number(o.total||0);},0); el.innerHTML='<div style="padding:12px 17px;border-bottom:1px solid var(--brd);display:flex;gap:10px;flex-wrap:wrap;font-size:12px;color:var(--tx2)"><span>'+rows.length+' оферти</span><span>·</span><span>'+accepted+' приети</span><span>·</span><span>'+fmt(total)+'</span></div><table class="ot"><thead><tr><th>#</th><th>Клиент</th><th>Тип</th><th>Услуга</th><th>Сума</th><th>Дата</th><th>Статус</th><th></th></tr></thead><tbody>' +rows.map(function(o){var tp=o.offer_type==='monthly'?'Месечна':'Еднократна';return '<tr><td style="font-family:var(--mo);color:var(--tx3)">'+esc(o.num)+'</td><td style="color:var(--tx);font-weight:500">'+esc(o.client)+'</td><td><span class="sp sp-n">'+tp+'</span></td><td>'+esc(o.service)+'</td><td style="font-family:var(--mo);font-weight:600;color:var(--tx)">'+fmt(o.total)+'</td><td>'+fd(o.created_at)+'</td><td><span class="sp '+(o.status==='Приета'?'sp-a':o.status==='Отказана'?'sp-d':'sp-r')+'">'+esc(o.status)+'</span></td><td><button class="obtn" onclick="window.open(\'offer.html?id='+o.id+'\',\'_blank\')">Редактирай</button><button class="obtn" style="color:var(--red)" onclick="dr(\'offers\',\''+o.id+'\')">✕</button></td></tr>';}).join('') +'</tbody></table>'; } function rContracts(){ var el=document.getElementById('cf'); el.innerHTML=D.contracts.length?D.contracts.map(function(c){return '<div class="pr" style="grid-template-columns:1fr auto auto"><div><div class="pn">'+(c.name||'—')+'</div><div class="pm">'+c.client+' · '+fd(c.sign_date)+'</div></div><span class="sp sp-a">Активен</span><button class="obtn" style="color:var(--red)" onclick="dr(\'contracts\',\''+c.id+'\')">✕</button></div>';}).join(''):emp('◈','Нямате договори','','+ Нов договор',"modal('contract')"); } function rHosting(){ var wc=D.hosting.filter(function(h){var d=h.ssl_expires?Math.ceil((new Date(h.ssl_expires)-new Date())/86400000):null;return d!==null&&d<30;}).length; document.getElementById('hs').textContent=D.hosting.length+' хостинга'; document.getElementById('nb-h').textContent=wc||0; if(!D.hosting.length){document.getElementById('hg').innerHTML=emp('⬡','Нямате хостинги','','+ Нов хостинг',"modal('hosting')");return;} document.getElementById('hg').innerHTML=D.hosting.map(function(h){ var d=h.ssl_expires?Math.ceil((new Date(h.ssl_expires)-new Date())/86400000):null; var sc=d===null?'':d<30?'ssl-bad':d<60?'ssl-warn':'ssl-ok'; var si=d===null?'—':d<30?'⚠':d<60?'⚡':'✓'; return '<div class="hcard'+(d!==null&&d<30?' warn':'')+'"><div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px"><div><div style="font-size:14px;font-weight:500;color:var(--tx)">'+h.domain+'</div><div style="font-size:11px;color:var(--tx3);margin-top:1px">'+(h.client||'—')+' · '+(h.type||'cPanel')+'</div></div><span class="sp sp-a">'+(h.plan||'—')+'</span></div>'+(h.server?'<div style="font-size:10px;color:var(--tx3);margin-bottom:7px">'+h.server+'</div>':'') +'<div style="display:grid;grid-template-columns:1fr 1fr;gap:7px"><div class="hst"><div class="hsl">Диск</div><div class="hsv">'+(h.disk||'—')+'</div></div><div class="hst"><div class="hsl">SSL</div><div class="hsv '+sc+'">'+si+' '+(h.ssl_expires?fd(h.ssl_expires):'—')+'</div></div><div class="hst"><div class="hsl">Дни</div><div class="hsv '+sc+'">'+(d!==null?d+' дни':'—')+'</div></div><div class="hst"><div class="hsl">Тип</div><div class="hsv">'+(h.type||'—')+'</div></div></div>' +'<div style="margin-top:10px;text-align:right"><button class="obtn" style="color:var(--red)" onclick="dr(\'hosting\',\''+h.id+'\')">Изтрий</button></div></div>'; }).join(''); } function pwNorm(s){return (s||'').trim();} function pwClientName(p){return pwNorm(p.client)||'Без клиент';} function pwCatLabel(c){ var m={hosting:'Хостинг',wordpress:'WordPress',google:'Google',social:'Социални мрежи',dns:'DNS',design:'Дизайн',other:'Друго'}; return m[c]||c||'Друго'; } function pwSelectClient(name){PW.client=name;var s=document.getElementById('pwSearch');if(s)s.value='';PW.search='';rPasswords();} function openPassModal(client,id){PW.formClient=client||PW.client||'';PW.editId=id||'';modal('password');} function pwClients(){ var map={}; D.clients.forEach(function(c){ var n=pwNorm(c.company)||pwNorm(c.name); if(!n)return; map[n]=map[n]||{name:n,meta:[],count:0,cats:{}}; map[n].meta=[c.name,c.email,c.phone].filter(Boolean); }); D.passwords.forEach(function(p){ var n=pwClientName(p); map[n]=map[n]||{name:n,meta:[],count:0,cats:{}}; map[n].count++; map[n].cats[p.category||'other']=(map[n].cats[p.category||'other']||0)+1; }); return Object.keys(map).map(function(k){return map[k];}).sort(function(a,b){ if(a.name==='Без клиент')return 1; if(b.name==='Без клиент')return -1; return a.name.localeCompare(b.name,'bg'); }); } function rPasswords(){ var cm={hosting:'hosting',google:'seo',social:'social',dns:'design',wordpress:'web',design:'design',other:'web'}; var box=document.getElementById('pwl'); var search=(PW.search||'').toLowerCase(); if(!PW.client){ var clients=pwClients().filter(function(c){ var hay=(c.name+' '+(c.meta||[]).join(' ')).toLowerCase(); return !search||hay.indexOf(search)!==-1; }); if(!clients.length){ box.innerHTML=emp('◎','Няма намерени клиенти','Промени търсенето или добави нов достъп.','+ Добави достъп',"openPassModal()"); return; } box.innerHTML='<div class="pwgrid">'+clients.map(function(c){ var cats=Object.keys(c.cats).map(function(k){return '<span class="pwchip">'+esc(pwCatLabel(k))+': '+c.cats[k]+'</span>';}).join(''); return '<div class="pwclient" onclick="pwSelectClient('+jsArg(c.name)+')"><div class="pwcn">'+esc(c.name)+'</div>'+ '<div class="pwcm">'+(c.meta&&c.meta.length?esc(c.meta.join(' · ')):'Достъпи без избран клиент')+'</div>'+ '<div class="pwcstat"><span class="pwchip">'+c.count+' достъп'+(c.count===1?'':'а')+'</span>'+cats+'</div></div>'; }).join('')+'</div>'; return; } var list=D.passwords.filter(function(p){return pwClientName(p)===PW.client;}); box.innerHTML='<div class="pwhead"><div><div class="pwtitle">'+esc(PW.client)+'</div><div class="pwsub">'+list.length+' запазени достъпа</div></div><div style="display:flex;gap:8px"><button class="btn" onclick="PW.client=\'\';rPasswords()">← Клиенти</button><button class="btn p" onclick="openPassModal(PW.client)">+ Добави</button></div></div>'+ (list.length?list.map(function(p){ return '<div class="pwr"><div style="flex:1"><div class="pws">'+esc(p.site)+'</div><div class="pwu">'+esc(p.username||'')+'</div></div><span class="tag '+(cm[p.category]||'web')+'">'+esc(pwCatLabel(p.category))+'</span><div class="pwm" onclick="revP(this,'+jsArg(p.pass||'')+')" title="Кликни">••••••••</div><button class="btn" onclick="cpTx('+jsArg(p.pass||'')+')">Копирай</button><button class="obtn" onclick="openPassModal(PW.client,'+jsArg(p.id)+')">Редактирай</button><button class="obtn" style="color:var(--red)" onclick="dr(\'passwords\','+jsArg(p.id)+')">✕</button></div>'; }).join(''):emp('◎','Няма достъпи за този клиент','','+ Добави достъп',"openPassModal(PW.client)")); } function revP(el,p){el.dataset.s==='1'?(el.textContent='••••••••',el.dataset.s=''):(el.textContent=p,el.dataset.s='1');} function cpTx(t){navigator.clipboard.writeText(t).then(function(){toast('Копирано!','ok');});} function rNotes(){ var el=document.getElementById('ng'); if(!D.notes.length){el.innerHTML='<div style="grid-column:1/-1">'+emp('◻','Нямате бележки','','+ Нова бележка',"modal('note')")+'</div>';return;} el.innerHTML=D.notes.map(function(n){return '<div class="panel"><div class="ph"><div class="pt">'+n.title+(n.urgent==1?'<span style="color:var(--red);margin-left:5px">⚠</span>':'')+'</div><button class="obtn" style="color:var(--red)" onclick="dr(\'notes\',\''+n.id+'\')">✕</button></div><div style="padding:13px 17px;font-size:13px;color:var(--tx2);line-height:1.7">'+(n.body||'')+'</div><div style="padding:9px 17px;border-top:1px solid var(--brd);font-size:10px;color:var(--tx3);display:flex;gap:8px"><span>'+(n.author||'')+'</span>'+(n.author?'<span>·</span>':'')+'<span>'+fdt(n.created_at)+'</span></div></div>';}).join(''); } function rUsers(){ var el=document.getElementById('ul'); if(!D.users.length){el.innerHTML=emp('👤','Нямате потребители','','+ Нов служител',"modal('newUser')");return;} el.innerHTML=D.users.map(function(u){return '<div class="ur"><div class="av '+(u.color||'av-p')+'">'+(u.initials||'?')+'</div><div style="flex:1"><div style="font-size:13px;font-weight:500;color:var(--tx)">'+u.name+'</div><div style="font-size:11px;color:var(--tx3)">'+(u.role||'')+'</div></div><div style="font-size:11px;color:var(--tx3);font-family:var(--mo)">'+u.username+'</div><span style="font-size:10px;padding:2px 8px;border-radius:20px;font-weight:500;'+(u.is_admin==1?'background:var(--adim);color:var(--amb)':'background:var(--gdim);color:var(--grn)')+'">'+( u.is_admin==1?'👑 Админ':'Служител')+'</span><button class="btn" onclick="modal(\'editUser\',\''+u.id+'\')">Редактирай</button>'+(u.is_admin!=1?'<button class="obtn" style="color:var(--red)" onclick="dU(\''+u.id+'\')">✕</button>':'')+'</div>';}).join(''); } // ── PRESENCE ── var PSI={ arrived: {emoji:'🟢', label:'На работа', color:'#22c55e'}, break: {emoji:'☕', label:'На почивка', color:'#f59e0b'}, back: {emoji:'💼', label:'Обратно', color:'#38bdf8'}, left: {emoji:'🔴', label:'Тръгнал', color:'#ef4444'}, toilet: {emoji:'🚽', label:'Тоалетна', color:'#8b5cf6'}, smoke: {emoji:'🚬', label:'На цигара', color:'#6b7280'}, smoke_back:{emoji:'💨', label:'Върна се', color:'#38bdf8'} }; function presenceMeta(x){ if(!CU||CU.is_admin!=1||!x)return ''; var bits=[]; if(x.ip_address)bits.push('IP: '+esc(x.ip_address)); if(x.latitude&&x.longitude){ var lat=Number(x.latitude),lng=Number(x.longitude); var acc=x.accuracy?' · ±'+Math.round(Number(x.accuracy))+'m':''; bits.push('<a href="https://maps.google.com/?q='+lat+','+lng+'" target="_blank" style="color:var(--acc2)">Карта</a>'+acc); }else if(x.location_note){ bits.push(esc(x.location_note)); } return bits.length?'<div style="font-size:10px;color:var(--tx3);margin-top:7px;line-height:1.5">'+bits.join('<br>')+'</div>':''; } function getPresenceGeo(){ return new Promise(function(resolve){ if(!navigator.geolocation){resolve({location_note:'Геолокацията не се поддържа'});return;} navigator.geolocation.getCurrentPosition(function(pos){ resolve({ latitude:pos.coords.latitude, longitude:pos.coords.longitude, accuracy:pos.coords.accuracy, location_note:'' }); },function(err){ var msg=err&&err.code===1?'Локацията е отказана':'Локацията не е налична'; resolve({location_note:msg}); },{enableHighAccuracy:true,timeout:6500,maximumAge:60000}); }); } async function rPresence(){ var el=document.getElementById('pref'); el.innerHTML='<div class="loading">Зарежда</div>'; var data=await af('presence'); var cur=(data&&data.current)?data.current:[]; var log=(data&&data.log)?data.log:[]; var myS=cur.find(function(x){return String(x.user_id)===String(CU&&CU.id);}); var mySI=myS?PSI[myS.status]:null; var myTm=myS?new Date(myS.created_at).toLocaleTimeString('bg-BG',{hour:'2-digit',minute:'2-digit'}):''; var badge=mySI ?'<div style="display:inline-flex;align-items:center;gap:8px;padding:8px 20px;border-radius:30px;background:'+mySI.color+'22;border:1px solid '+mySI.color+'55;font-size:13px;font-weight:600;color:'+mySI.color+';margin-bottom:22px">'+mySI.emoji+' '+mySI.label+' <span style="font-size:10px;font-weight:400;opacity:.7">от '+myTm+'</span></div>' :'<div style="display:inline-flex;align-items:center;gap:8px;padding:8px 20px;border-radius:30px;background:var(--sur2);border:1px solid var(--brd);font-size:13px;color:var(--tx3);margin-bottom:22px">⚪ Не е отбелязано днес</div>'; var LBL={arrived:'Пристигнах',break:'Почивка',back:'Обратно',left:'Тръгвам',toilet:'Тоалетна',smoke:'Цигара',smoke_back:'Обратно'}; var btns=['arrived','break','back','left','toilet','smoke','smoke_back'].map(function(s){ var c=PSI[s];var ia=myS&&myS.status===s; return '<button onclick="logP(\''+s+'\')" style="padding:18px 16px;border-radius:14px;border:2px solid '+c.color+';background:'+(ia?c.color:'transparent')+';color:'+(ia?'#fff':c.color)+';cursor:pointer;font-family:DM Sans,sans-serif;font-size:12px;font-weight:700;display:inline-flex;flex-direction:column;align-items:center;gap:8px;min-width:110px;box-shadow:'+(ia?'0 4px 16px '+c.color+'44':'none')+';outline:none;"><span style="font-size:26px;line-height:1">'+c.emoji+'</span><span>'+LBL[s]+'</span></button>'; }).join(''); var cards=D.users.map(function(u){ var cs=cur.find(function(x){return String(x.user_id)===String(u.id);}); var si=cs?PSI[cs.status]:null; var t=cs?new Date(cs.created_at).toLocaleTimeString('bg-BG',{hour:'2-digit',minute:'2-digit'}):''; var col=u.color||'av-p'; return '<div style="background:var(--sur);border:2px solid '+(si?si.color+'88':'var(--brd)')+';border-radius:14px;padding:18px;text-align:center">' +'<div style="width:48px;height:48px;border-radius:50%;background:'+(ABG[col]||'rgba(108,99,255,.25)')+';color:'+(ACV[col]||'#a78bfa')+';display:flex;align-items:center;justify-content:center;font-size:17px;font-weight:700;margin:0 auto 10px">'+(u.initials||'?')+'</div>' +'<div style="font-size:13px;font-weight:600;color:var(--tx);margin-bottom:2px">'+u.name+'</div>' +'<div style="font-size:11px;color:var(--tx3);margin-bottom:10px">'+(u.role||'')+'</div>' +'<div style="display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:5px 12px;border-radius:20px;background:'+(si?si.color+'18':'var(--sur2)')+';color:'+(si?si.color:'var(--tx3)')+';border:1px solid '+(si?si.color+'33':'var(--brd)')+'">'+( si?si.emoji:'⚪')+' '+(si?si.label:'Не е влязъл')+'</div>' +(t?'<div style="font-size:10px;color:var(--tx3);margin-top:6px;font-family:var(--mo)">от '+t+'</div>':'') +presenceMeta(cs) +'</div>'; }).join(''); var td=new Date().toLocaleDateString('bg-BG',{weekday:'long',day:'numeric',month:'long'}); var logR=log.length?log.map(function(l){ var si=PSI[l.status];var t=new Date(l.created_at).toLocaleTimeString('bg-BG',{hour:'2-digit',minute:'2-digit'}); var col=l.color||'av-p'; return '<div style="padding:10px 16px;border-bottom:1px solid var(--brd);display:flex;align-items:center;gap:12px">' +'<div style="width:28px;height:28px;border-radius:50%;background:'+(ABG[col]||'rgba(108,99,255,.25)')+';color:'+(ACV[col]||'#a78bfa')+';display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0">'+(l.initials||'?')+'</div>' +'<div style="font-size:12px;font-weight:500;color:var(--tx);flex:1">'+l.name+'</div>' +'<div style="font-size:11px;font-weight:600;padding:3px 10px;border-radius:20px;background:'+(si?si.color+'18':'#88888818')+';color:'+(si?si.color:'#888')+'">'+( si?si.emoji:'⚪')+' '+(si?si.label:l.status)+'</div>' +'<div style="font-size:10px;color:var(--tx3);font-family:var(--mo)">'+t+'</div>' +presenceMeta(l) +'</div>'; }).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма записи за днес</div>'; el.innerHTML='<div style="background:var(--sur);border:1px solid var(--brd);border-radius:18px;padding:32px;margin-bottom:20px;text-align:center;box-shadow:var(--shd)"><div style="font-size:20px;font-weight:700;color:var(--tx);margin-bottom:4px">👋 Здравей, '+(CU?CU.name:'')+' !</div><div style="font-size:13px;color:var(--tx3);margin-bottom:16px">Отбележи своето присъствие</div>'+badge+'<div style="display:flex;gap:14px;justify-content:center;flex-wrap:wrap">'+btns+'</div></div>' +'<div style="font-size:12px;font-weight:600;color:var(--tx);margin-bottom:12px;text-transform:uppercase;letter-spacing:.06em">Статус на екипа</div>' +'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(175px,1fr));gap:12px;margin-bottom:20px">'+cards+'</div>' +'<div class="panel"><div class="ph"><div class="pt">📋 Журнал — '+td+'</div><span style="font-size:11px;color:var(--tx3)">'+log.length+' записа</span></div>'+logR+'</div>'; } async function logP(status){ if(!CU||!CU.id){toast('Не сте влезли','err');return;} var geo=await getPresenceGeo(); var r=await af('presence','POST',Object.assign({user_id:CU.id,status:status},geo)); if(r!==null){ var M={arrived:'✓ Добре дошъл!',break:'☕ Приятна почивка!',back:'💼 Добре дошъл обратно!',left:'👋 Лека вечер!'}; toast(M[status]||'OK','ok'); await rPresence(); }else toast('Грешка при запис','err'); } // ── CALENDAR ── function rCalendar(){ var el=document.getElementById('calf'); var td=new Date(); if(!window._cal)window._cal={year:td.getFullYear(),month:td.getMonth(),sel:td.toISOString().slice(0,10)}; var st=window._cal; var dim=new Date(st.year,st.month+1,0).getDate(); var fd2=new Date(st.year,st.month,1).getDay(); var off=fd2===0?6:fd2-1; var MN=['Януари','Февруари','Март','Април','Май','Юни','Юли','Август','Септември','Октомври','Ноември','Декември']; var DL=['Пн','Вт','Ср','Чт','Пт','Сб','Нд']; var tds=td.toISOString().slice(0,10); var days=DL.map(function(d){return '<div class="call">'+d+'</div>';}).join(''); for(var i=0;i<off;i++)days+='<div class="calday om"></div>'; for(var d=1;d<=dim;d++){ var ds=st.year+'-'+String(st.month+1).padStart(2,'0')+'-'+String(d).padStart(2,'0'); var dts=D.tasks.filter(function(t){return t.due_date&&t.due_date.slice(0,10)===ds;}); var ev=dts.slice(0,2).map(function(t){return '<div class="calev">'+t.text.slice(0,12)+'</div>';}).join(''); days+='<div class="calday'+(ds===tds?' today':'')+(ds===st.sel?' sel':'')+'" onclick="window._cal.sel=\''+ds+'\';rCalendar()"><div class="caldn">'+d+'</div>'+ev+(dts.length>2?'<div style="font-size:9px;color:var(--tx3)">+'+(dts.length-2)+'</div>':'')+'</div>'; } var rem=(off+dim)%7;if(rem)for(var d2=1;d2<=7-rem;d2++)days+='<div class="calday om"></div>'; var sT=D.tasks.filter(function(t){return t.due_date&&t.due_date.slice(0,10)===st.sel;}); var sDD=new Date(st.sel+'T12:00:00').toLocaleDateString('bg-BG',{weekday:'long',day:'numeric',month:'long'}); var up=D.tasks.filter(function(t){return t.due_date&&t.done!=1&&t.due_date>=tds;}).sort(function(a,b){return a.due_date.localeCompare(b.due_date);}).slice(0,8); el.innerHTML='<div class="calg"><div><div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px"><div style="display:flex;align-items:center;gap:12px"><button class="btn" onclick="window._cal.month--;if(window._cal.month<0){window._cal.month=11;window._cal.year--;}rCalendar()">‹</button><div style="font-size:16px;font-weight:600;color:var(--tx)">'+MN[st.month]+' '+st.year+'</div><button class="btn" onclick="window._cal.month++;if(window._cal.month>11){window._cal.month=0;window._cal.year++;}rCalendar()">›</button></div><button class="btn p" onclick="modal(\'calTask\',\''+st.sel+'\')">+ Задача с дата</button></div><div class="cald">'+days+'</div></div>' +'<div><div class="panel"><div class="ph"><div class="pt">'+sDD+'</div><button class="pa" onclick="modal(\'calTask\',\''+st.sel+'\')">+ Задача</button></div>'+(sT.length?sT.map(tHTML).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма задачи за деня</div>')+'</div>' +'<div class="panel"><div class="ph"><div class="pt">📋 Предстоящи</div></div>'+(up.length?up.map(function(t){return '<div class="ti" onclick="togCalT(\''+t.id+'\')"><div class="tck"></div><div style="flex:1"><div class="ttx">'+t.text+'</div><div style="font-size:10px;color:var(--tx3);margin-top:2px">'+new Date(t.due_date+'T12:00:00').toLocaleDateString('bg-BG',{day:'numeric',month:'short'})+' · '+(t.assignee||'—')+'</div></div>'+(t.urgent==1?'<span style="font-size:10px;color:var(--red)">⚠</span>':'')+'</div>';}).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма предстоящи</div>')+'</div></div></div>'; } async function togCalT(id){ var t=D.tasks.find(function(x){return x.id===id;});if(!t)return; t.done=t.done==1?0:1; await af('tasks','POST',{id:t.id,text:t.text,assignee:t.assignee,urgent:t.urgent,done:t.done,due_date:t.due_date||null}); rCalendar();toast(t.done==1?'✓ Изпълнена':'Върната','ok'); } // ── TARGETS NAV HELPERS ── function tgPrevDay(){ var d=new Date(_tgDate+'T12:00:00'); d.setDate(d.getDate()-1); _tgDate=d.toISOString().slice(0,10); rTargets(); } function tgNextDay(){ var d=new Date(_tgDate+'T12:00:00'); d.setDate(d.getDate()+1); _tgDate=d.toISOString().slice(0,10); rTargets(); } function tgToday(){ _tgDate=new Date().toISOString().slice(0,10); rTargets(); } // ── WORK REPORT HELPERS ── function wrUpdate(){ var f=document.getElementById('wrFrom'); var t=document.getElementById('wrTo'); if(f)window._wrFrom=f.value; if(t)window._wrTo=t.value; rWorkReport(); } function wrThisMonth(){ var d=new Date(); window._wrFrom=d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-01'; window._wrTo=d.toISOString().slice(0,10); rWorkReport(); } function wrLast7(){ var d=new Date();d.setDate(d.getDate()-7); window._wrFrom=d.toISOString().slice(0,10); window._wrTo=new Date().toISOString().slice(0,10); rWorkReport(); } // ── TARGETS ── var _tgDate = new Date().toISOString().slice(0,10); async function rTargets(){ var el=document.getElementById('tgf'); el.innerHTML='<div class="loading">Зарежда</div>'; var data=await af('targets',null,null,'&date='+_tgDate); var targets=data||[]; var isAdmin=CU&&CU.is_admin==1; var today=new Date().toISOString().slice(0,10); var dateStr=new Date(_tgDate+'T12:00:00').toLocaleDateString('bg-BG',{weekday:'long',day:'numeric',month:'long'}); // Date nav var nav2='<div class="panel" style="margin-bottom:16px"><div class="ph"><div class="pt">🎯 Дневни таргети</div>' +'<div style="display:flex;gap:8px;align-items:center">' +'<button class="btn" onclick="tgPrevDay()">‹</button>' +'<span style="font-size:13px;font-weight:500;color:var(--tx)">'+dateStr+'</span>' +'<button class="btn" onclick="tgNextDay()">›</button>' +'<button class="btn" onclick="tgToday()">Днес</button>' +(isAdmin?'<button class="btn p" onclick="openTgModal()">+ Нов таргет</button>':'') +'</div></div></div>'; if(!targets.length){ el.innerHTML=nav2+'<div style="padding:50px 20px;text-align:center">' +'<div style="font-size:40px;margin-bottom:12px">🎯</div>' +'<div style="font-size:15px;font-weight:500;color:var(--tx2);margin-bottom:6px">Няма таргети за '+dateStr+'</div>' +'<div style="font-size:12px;color:var(--tx3);margin-bottom:16px">Администраторът може да добави нови таргети</div>' +(isAdmin?'<button class="btn p" onclick="openTgModal()">+ Добави таргет</button>':'') +'</div>'; return; } // Group by user var byUser={}; targets.forEach(function(t){ if(!byUser[t.user_id]) byUser[t.user_id]={name:t.user_name,initials:t.initials,color:t.color,items:[]}; byUser[t.user_id].items.push(t); }); var cards=Object.values(byUser).map(function(u){ var items=u.items.map(function(t){ var pct=Math.min(Math.round(t.current_value/t.target_value*100),100); var isMe=String(t.user_id)===String(CU&&CU.id); var isDone=pct>=100; var isLow=pct<50&&t.current_value>0; var barColor=isDone?'#22c55e':pct>=75?'#38bdf8':pct>=50?'#f59e0b':'#ef4444'; // Motivational message var motivMsg='';var motivColor=''; if(isDone){ motivMsg='🏆 Отличен резултат! Таргетът е изпълнен!'; motivColor='#22c55e'; } else if(pct>=75){ motivMsg='⭐ Много добре! Почти там — не се отказвай!'; motivColor='#38bdf8'; } else if(pct>=50){ motivMsg='👍 На половината. Стегни се за да изпълниш плана!'; motivColor='#f59e0b'; } else if(pct>0){ motivMsg='⚠️ Под нормата. Трябва повече усилия — заплатата зависи от резултата!'; motivColor='#ef4444'; } else{ motivMsg='⏳ Все още не е започнато.'; motivColor='#6b7280'; } return '<div style="background:var(--sur2);border-radius:12px;padding:16px;margin-bottom:10px;border:1px solid var(--brd)">' // Header +'<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px">' +'<div><div style="font-size:14px;font-weight:600;color:var(--tx)">'+t.title+'</div>' +(t.description?'<div style="font-size:11px;color:var(--tx3);margin-top:2px">'+t.description+'</div>':'') +'</div>' +(isAdmin?'<button class="obtn" style="color:var(--red)" onclick="delTg('+t.id+')">✕</button>':'') +'</div>' // Progress bar +'<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">' +'<div style="flex:1;height:12px;background:var(--sur);border-radius:6px;overflow:hidden;border:1px solid var(--brd)">' +'<div style="height:100%;width:'+pct+'%;background:'+barColor+';border-radius:6px;transition:width .5s ease"></div>' +'</div>' +'<div style="font-size:14px;font-weight:700;font-family:var(--mo);color:'+barColor+';min-width:40px;text-align:right">'+pct+'%</div>' +'</div>' // Numbers +'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">' +'<div style="font-size:12px;color:var(--tx3)">Изпълнено: <strong style="color:var(--tx);font-family:var(--mo)">'+t.current_value+' '+t.unit+'</strong></div>' +'<div style="font-size:12px;color:var(--tx3)">Таргет: <strong style="color:var(--tx);font-family:var(--mo)">'+t.target_value+' '+t.unit+'</strong></div>' +'</div>' // Motivational msg +'<div style="padding:8px 12px;border-radius:8px;background:'+motivColor+'12;border:1px solid '+motivColor+'33;font-size:12px;color:'+motivColor+';font-weight:500;margin-bottom:'+(isMe&&!isDone?'10':'0')+'px">'+motivMsg+'</div>' // Update button (only for assigned user or admin) +((isMe||isAdmin)&&!isDone?'<div style="display:flex;gap:8px;align-items:center;margin-top:10px">' +'<input type="number" id="tg_inp_'+t.id+'" min="0" max="'+t.target_value+'" value="'+t.current_value+'" style="background:var(--sur);border:1px solid var(--brd);border-radius:8px;padding:7px 10px;color:var(--tx);font-family:var(--fn);font-size:13px;outline:none;width:100px">' +'<span style="font-size:12px;color:var(--tx3)">'+t.unit+'</span>' +'<button class="btn p" onclick="updateTg('+t.id+','+t.target_value+')">Обнови прогрес</button>' +(isDone?'':'<button class="btn" style="border-color:var(--grn);color:var(--grn)" onclick="updateTgFull('+t.id+','+t.target_value+')">✓ Изпълнен!</button>') +'</div>':'') +'</div>'; }).join(''); var col=u.color||'av-p'; var doneCount=u.items.filter(function(t){return Math.round(t.current_value/t.target_value*100)>=100;}).length; return '<div class="panel" style="margin-bottom:14px">' +'<div class="ph"><div style="display:flex;align-items:center;gap:10px">' +'<div class="av '+col+'" style="width:34px;height:34px;font-size:13px">'+(u.initials||'?')+'</div>' +'<div><div style="font-size:14px;font-weight:600;color:var(--tx)">'+u.name+'</div>' +'<div style="font-size:11px;color:var(--tx3)">'+doneCount+'/'+u.items.length+' таргета изпълнени</div></div>' +'</div>' +'<div style="font-size:20px">'+( doneCount===u.items.length?'🏆':doneCount>0?'⚡':'⏳')+'</div>' +'</div>' +'<div style="padding:14px 17px">'+items+'</div>' +'</div>'; }).join(''); el.innerHTML=nav2+cards; } async function updateTg(id, maxVal){ var inp=document.getElementById('tg_inp_'+id); if(!inp)return; var val=parseInt(inp.value)||0; if(val>maxVal)val=maxVal; var status=val>=maxVal?'done':'active'; var r=await af('targets','POST',{id:id,current_value:val,status:status}); if(r!==null){ toast(val>=maxVal?'🏆 Таргетът е изпълнен!':'✓ Прогресът е обновен!','ok'); rTargets(); } else toast('Грешка','err'); } async function updateTgFull(id, maxVal){ var r=await af('targets','POST',{id:id,current_value:maxVal,status:'done'}); if(r!==null){toast('🏆 Таргетът е изпълнен!','ok');rTargets();} else toast('Грешка','err'); } async function delTg(id){ if(!confirm('Изтрий таргета?'))return; await af('targets','DELETE',null,'&id='+id); toast('Изтрито');rTargets(); } function openTgModal(){ if(!D.users.length){toast('Зарежда потребителите...','');return;} var html='<h3>🎯 Нов таргет</h3>' +'<div class="mf"><label>Заглавие</label><input id="tgTi" placeholder="напр. Качване на продукти, Обаждания, Оферти..."></div>' +'<div class="mf"><label>Описание (по избор)</label><input id="tgDe" placeholder="Допълнителни детайли"></div>' +'<div class="mg">' +'<div class="mf"><label>Таргет (брой)</label><input type="number" id="tgTv" placeholder="300" min="1"></div>' +'<div class="mf"><label>Единица</label><input id="tgUn" placeholder="бр., продукта, оферти..." value="бр."></div>' +'</div>' +'<div class="mg">' +'<div class="mf"><label>Служител</label><select id="tgUs">'+D.users.map(function(u){return '<option value="'+u.id+'">'+u.name+(u.role?' ('+u.role+')':'')+'</option>';}).join('')+'</select></div>' +'<div class="mf"><label>Дата</label><input type="date" id="tgDt" value="'+_tgDate+'"></div>' +'</div>' +'<div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="saveTg()">Създай таргет</button></div>'; document.getElementById('mb').innerHTML=html; document.getElementById('mo').classList.add('open'); } async function saveTg(){ var title=gv('tgTi');if(!title){toast('Въведете заглавие','err');return;} var tv=parseInt(gv('tgTv'));if(!tv||tv<1){toast('Въведете таргет стойност','err');return;} var userId=gv('tgUs');if(!userId){toast('Изберете служител','err');return;} var r=await af('targets','POST',{ user_id:userId, created_by:CU.id, title:title, description:gv('tgDe'), target_value:tv, current_value:0, unit:gv('tgUn')||'бр.', date:gv('tgDt')||_tgDate }); if(r){ cmo(); toast('Таргетът е създаден!','ok'); // Send telegram notification rTargets(); } else toast('Грешка','err'); } // ── WORK REPORT ── async function rWorkReport(){ var el=document.getElementById('wrf'); el.innerHTML='<div class="loading">Зарежда</div>'; // Date range picker var today=new Date().toISOString().slice(0,10); var firstOfMonth=today.slice(0,8)+'01'; // Load report var from=window._wrFrom||firstOfMonth; var to=window._wrTo||today; var data=await af('presence_report',null,null,'&from='+from+'&to='+to); if(!data){el.innerHTML='<div style="padding:40px;text-align:center;color:var(--tx3)">Няма данни за избрания период</div>';return;} var users=data.users||[]; var STATUS_LABELS={arrived:'Пристигна',break:'Почивка',back:'Върна се',left:'Тръгна',toilet:'Тоалетна',smoke:'Цигара',smoke_back:'След цигара'}; var STATUS_COLORS={arrived:'#22c55e',break:'#f59e0b',back:'#38bdf8',left:'#ef4444',toilet:'#8b5cf6',smoke:'#6b7280',smoke_back:'#38bdf8'}; // Motivational messages based on hours function motiv(hours, daysWorked){ if(hours<=0) return {msg:'Няма записано присъствие за периода.',color:'#6b7280',icon:'❓'}; var avg=daysWorked>0?hours/daysWorked:0; if(avg>=8) return {msg:'Отлично! Пълен работен ден всеки ден. Заплатата се изчислява на база отработените часове.',color:'#22c55e',icon:'🏆'}; if(avg>=7) return {msg:'Много добре! Почти пълни работни дни. Продължавай така!',color:'#22c55e',icon:'⭐'}; if(avg>=6) return {msg:'Добре. Има място за подобрение — пълният работен ден е 8 часа.',color:'#f59e0b',icon:'👍'}; if(avg>=4) return {msg:'Под средното. Заплатата се влияе от отработените часове.',color:'#f59e0b',icon:'⚠️'}; return {msg:'Недостатъчно присъствие. Моля, спазвайте работното време.',color:'#ef4444',icon:'❌'}; } // Summary cards var totalHours=users.reduce(function(s,u){return s+u.total_hours;},0); var summary='<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px;margin-bottom:20px">' +'<div class="kc"><div class="kl">Период</div><div style="font-size:13px;font-weight:600;color:var(--tx);font-family:var(--mo)">'+from+'<br>'+to+'</div></div>' +'<div class="kc"><div class="kl">Служители</div><div class="kv ac">'+users.length+'</div></div>' +'<div class="kc"><div class="kl">Общо часа</div><div class="kv g">'+totalHours.toFixed(1)+'ч</div></div>' +'</div>'; // Date range picker var picker='<div class="panel" style="margin-bottom:16px"><div class="ph"><div class="pt">📅 Период</div></div>' +'<div style="padding:13px 17px;display:flex;gap:12px;align-items:center;flex-wrap:wrap">' +'<div><label style="font-size:11px;color:var(--tx3);display:block;margin-bottom:4px;text-transform:uppercase;letter-spacing:.06em">От</label>' +'<input type="date" id="wrFrom" value="'+from+'" style="background:var(--sur2);border:1px solid var(--brd);border-radius:8px;padding:7px 10px;color:var(--tx);font-family:var(--fn);font-size:12px;outline:none"></div>' +'<div><label style="font-size:11px;color:var(--tx3);display:block;margin-bottom:4px;text-transform:uppercase;letter-spacing:.06em">До</label>' +'<input type="date" id="wrTo" value="'+to+'" style="background:var(--sur2);border:1px solid var(--brd);border-radius:8px;padding:7px 10px;color:var(--tx);font-family:var(--fn);font-size:12px;outline:none"></div>' +'<button class="btn p" style="margin-top:18px" onclick="wrUpdate()">Обнови</button>' +'<button class="btn" style="margin-top:18px" onclick="wrThisMonth()">Текущ месец</button>' +'<button class="btn" style="margin-top:18px" onclick="wrLast7()">Последните 7 дни</button>' +'</div></div>'; // User cards var cards=users.map(function(u){ var m=motiv(u.total_hours, u.days_worked); var col=u.color||'av-p'; var avgH=u.days_worked>0?(u.total_hours/u.days_worked).toFixed(1):0; // Daily breakdown var days=Object.keys(u.days||{}).sort().map(function(date){ var day=u.days[date]; if(!day.work_minutes&&!day.timeline.length) return ''; var dayName=new Date(date+'T12:00:00').toLocaleDateString('bg-BG',{weekday:'short',day:'numeric',month:'short'}); var timeline=day.timeline.map(function(e){ var sc=STATUS_COLORS[e.status]||'#888'; return '<span style="font-size:10px;background:'+sc+'18;color:'+sc+';padding:2px 6px;border-radius:10px;font-weight:600">'+e.time+' '+(STATUS_LABELS[e.status]||e.status)+'</span>'; }).join(' → '); var h=day.work_hours; var barW=Math.min(h/8*100,100); var barC=h>=8?'#22c55e':h>=6?'#38bdf8':h>=4?'#f59e0b':'#ef4444'; return '<div style="padding:8px 0;border-bottom:1px solid var(--brd)">' +'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:5px">' +'<span style="font-size:11px;font-weight:600;color:var(--tx2)">'+dayName+'</span>' +'<span style="font-size:12px;font-weight:700;font-family:var(--mo);color:'+barC+'">'+h+'ч</span>' +'</div>' +'<div style="height:5px;background:var(--sur2);border-radius:3px;overflow:hidden;margin-bottom:5px">' +'<div style="height:100%;width:'+barW+'%;background:'+barC+';border-radius:3px;transition:width .4s ease"></div>' +'</div>' +'<div style="display:flex;gap:4px;flex-wrap:wrap">'+timeline+'</div>' +(day.toilet_count>0?'<div style="font-size:10px;color:var(--tx3);margin-top:3px">🚽 ×'+day.toilet_count+'</div>':'') +'</div>'; }).join(''); return '<div class="panel" style="margin-bottom:14px">' +'<div class="ph">' +'<div style="display:flex;align-items:center;gap:10px">' +'<div class="av '+col+'" style="width:36px;height:36px;font-size:14px">'+(u.initials||'?')+'</div>' +'<div><div style="font-size:14px;font-weight:600;color:var(--tx)">'+u.name+'</div>' +'<div style="font-size:11px;color:var(--tx3)">'+u.days_worked+' работни дни · средно '+avgH+'ч/ден</div></div>' +'</div>' +'<div style="text-align:right">' +'<div style="font-size:22px;font-weight:700;font-family:var(--mo);color:var(--acc2)">'+u.total_hours+'ч</div>' +'<div style="font-size:10px;color:var(--tx3)">общо за периода</div>' +'</div>' +'</div>' // Motivational message +'<div style="margin:12px 17px;padding:12px 16px;border-radius:10px;background:'+m.color+'12;border:1px solid '+m.color+'33;display:flex;align-items:flex-start;gap:10px">' +'<span style="font-size:18px;flex-shrink:0">'+m.icon+'</span>' +'<div>' +'<div style="font-size:12px;font-weight:600;color:'+m.color+';margin-bottom:3px">Оценка за периода</div>' +'<div style="font-size:12px;color:var(--tx2);line-height:1.6">'+m.msg+'</div>' +'</div>' +'</div>' // Hours bar +'<div style="padding:0 17px 12px">' +'<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--tx3);margin-bottom:5px">' +'<span>Отработени часа</span><span style="font-family:var(--mo)">'+u.total_hours+'ч / '+( u.days_worked*8)+'ч очаквани</span>' +'</div>' +'<div style="height:8px;background:var(--sur2);border-radius:4px;overflow:hidden">' +'<div style="height:100%;width:'+Math.min(u.total_hours/(Math.max(u.days_worked,1)*8)*100,100)+'%;background:var(--grn);border-radius:4px;transition:width .5s ease"></div>' +'</div>' +'</div>' // Daily breakdown toggle +(days?'<details style="padding:0 17px 14px"><summary style="font-size:11px;color:var(--acc2);cursor:pointer;padding:6px 0;font-weight:600">Виж дневен разбивка ▾</summary><div style="margin-top:8px">'+days+'</div></details>':'') +'</div>'; }).join(''); // Salary note var salaryNote='<div class="panel" style="margin-top:4px">' +'<div class="ph"><div class="pt">💡 Информация за заплащането</div></div>' +'<div style="padding:16px 20px;font-size:13px;color:var(--tx2);line-height:1.8">' +'Заплатата на всеки служител се изчислява на база <strong style="color:var(--tx)">реално отработените часове</strong> за месеца, отчетени чрез системата за присъствие.<br><br>' +'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-top:10px">' +'<div style="background:var(--gdim);border-radius:10px;padding:12px 14px"><div style="font-size:11px;font-weight:700;color:var(--grn);margin-bottom:4px">🏆 Отлично (≥8ч/ден)</div><div style="font-size:11px;color:var(--tx2)">Пълна заплата + бонус</div></div>' +'<div style="background:var(--bdim);border-radius:10px;padding:12px 14px"><div style="font-size:11px;font-weight:700;color:var(--blu);margin-bottom:4px">⭐ Добре (7-8ч/ден)</div><div style="font-size:11px;color:var(--tx2)">Пълна заплата</div></div>' +'<div style="background:var(--adim);border-radius:10px;padding:12px 14px"><div style="font-size:11px;font-weight:700;color:var(--amb);margin-bottom:4px">⚠️ Средно (6-7ч/ден)</div><div style="font-size:11px;color:var(--tx2)">Заплата -10%</div></div>' +'<div style="background:var(--rdim);border-radius:10px;padding:12px 14px"><div style="font-size:11px;font-weight:700;color:var(--red);margin-bottom:4px">❌ Под норма (<6ч/ден)</div><div style="font-size:11px;color:var(--tx2)">Заплата пропорционална на часовете</div></div>' +'</div>' +'</div></div>'; if(!users.length){ el.innerHTML=picker+'<div style="padding:40px;text-align:center;color:var(--tx3);font-size:13px">Няма данни за избрания период.<br>Служителите трябва да отбелязват присъствие.</div>'; return; } el.innerHTML=picker+summary+cards+salaryNote; } function rReports(){ var ms={};D.projects.forEach(function(p){if(p.start_date){var m=p.start_date.slice(0,7);ms[m]=(ms[m]||0)+Number(p.total||0);}}); var mk=Object.keys(ms).sort().slice(-6);var mm=Math.max.apply(null,mk.map(function(k){return ms[k];}).concat([1])); var mn2=['','Яну','Фев','Мар','Апр','Май','Юни','Юли','Авг','Сеп','Окт','Ное','Дек']; document.getElementById('rr').innerHTML=mk.length?mk.map(function(k){return '<div class="br"><div class="brl">'+(mn2[parseInt(k.slice(5))]||'')+' '+k.slice(0,4)+'</div><div class="brt"><div class="brf" style="width:'+(ms[k]/mm*100)+'%;background:var(--acc)"></div></div><div class="brv">'+fmt(ms[k])+'</div></div>';}).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Добавете проекти с дати</div>'; var sv={};D.projects.forEach(function(p){(p.tags||[]).forEach(function(t){sv[t]=(sv[t]||0)+1;});});var sm=Math.max.apply(null,Object.values(sv).concat([1])); document.getElementById('rs').innerHTML=Object.keys(sv).length?Object.keys(sv).sort(function(a,b){return sv[b]-sv[a];}).slice(0,6).map(function(t){return '<div class="br"><div class="brl">'+t+'</div><div class="brt"><div class="brf" style="width:'+(sv[t]/sm*100)+'%;background:var(--grn)"></div></div><div class="brv">'+sv[t]+' проект'+(sv[t]===1?'а':'')+'</div></div>';}).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма данни</div>'; var cm={};D.projects.forEach(function(p){var c=p.client||'—';cm[c]=(cm[c]||0)+Number(p.total||0);});var cmm=Math.max.apply(null,Object.values(cm).concat([1])); document.getElementById('rc').innerHTML=Object.keys(cm).length?Object.keys(cm).sort(function(a,b){return cm[b]-cm[a];}).slice(0,6).map(function(c){return '<div class="br"><div class="brl">'+c+'</div><div class="brt"><div class="brf" style="width:'+(cm[c]/cmm*100)+'%;background:var(--blu)"></div></div><div class="brv">'+fmt(cm[c])+'</div></div>';}).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма данни</div>'; var tm={};D.tasks.forEach(function(t){if(t.assignee&&t.done==0)tm[t.assignee]=(tm[t.assignee]||0)+1;});var tmm=Math.max.apply(null,Object.values(tm).concat([1])); document.getElementById('rt').innerHTML=Object.keys(tm).length?Object.keys(tm).map(function(n){return '<div class="br"><div class="brl">'+n+'</div><div class="brt"><div class="brf" style="width:'+(tm[n]/tmm*100)+'%;background:'+(tm[n]/tmm>.8?'var(--red)':tm[n]/tmm>.5?'var(--amb)':'var(--grn)')+'"></div></div><div class="brv">'+tm[n]+' задач'+(tm[n]===1?'а':'и')+'</div></div>';}).join(''):'<div style="padding:20px;text-align:center;color:var(--tx3);font-size:12px">Няма данни</div>'; } // ── MODALS ── var SO='<option>WordPress сайт</option><option>WooCommerce магазин</option><option>Брандинг / Лого</option><option>SEO оптимизация</option><option>Google Ads</option><option>Соц. мрежи</option><option>Хостинг + поддръжка</option><option>Viber маркетинг</option><option>Видео / Рийлсове</option><option>Комплексна оферта</option><option>Друго</option>'; var CO='<option value="av-p">🟣 Лилав</option><option value="av-t">🩵 Тюркоаз</option><option value="av-g">🟢 Зелен</option><option value="av-a">🟡 Жълт</option><option value="av-r">🔴 Червен</option><option value="av-b">🔵 Син</option>'; function tO(){return D.users.length?D.users.map(function(u){return '<option value="'+u.name+'">'+u.name+(u.role?' ('+u.role+')':'')+'</option>';}).join(''):'<option value="">— зареди потребителите —</option>';} function uForm(u){ return '<h3>'+(u?'Редактирай служител':'+ Нов служител')+'</h3>' +'<div class="mg"><div class="mf"><label>Пълно име</label><input id="uN" value="'+(u?esc(u.name||''):'')+'" placeholder="Иван Иванов"></div><div class="mf"><label>Роля</label><input id="uR" value="'+(u?esc(u.role||''):'')+'" placeholder="Dev, Дизайн..."></div></div>' +'<div class="mg"><div class="mf"><label>Потребителско име</label><input id="uU" value="'+(u?esc(u.username||''):'')+'" placeholder="ivan.ivanov"></div><div class="mf"><label>Парола'+(u?' (празно = без промяна)':'')+'</label><input type="password" id="uP" placeholder="мин. 6 символа"></div></div>' +'<div class="mg"><div class="mf"><label>Инициали</label><input id="uI" value="'+(u?esc(u.initials||''):'')+'" maxlength="3" placeholder="ИИ"></div><div class="mf"><label>Цвят</label><select id="uC">'+CO+'</select></div></div>' +'<div class="mf"><label>Роля в системата</label><select id="uA"><option value="0"'+(u&&u.is_admin!=1?' selected':'')+'>👤 Служител</option><option value="1"'+(u&&u.is_admin==1?' selected':'')+'>👑 Администратор</option></select></div>' +'<div class="ma">'+(u?'<button class="btn d" onclick="dU(\''+u.id+'\')">Изтрий</button>':'')+'<button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="'+(u?'sU(\''+u.id+'\')':'sU()')+'">Запази</button></div>'; } var ML={ project:function(p){ return '<h3>'+(p?'Редактирай проект':'+ Нов проект')+'</h3>' +'<div class="mf"><label>Название</label><input id="mN" value="'+(p?esc(p.name):'')+'" placeholder="Клиент — услуга"></div>' +'<div class="mg"><div class="mf"><label>Клиент</label><input id="mCl" value="'+(p?esc(p.client||''):'')+'" placeholder="Иван Иванов"></div><div class="mf"><label>Телефон/Email</label><input id="mPh" value="'+(p?esc(p.phone||''):'')+'" placeholder="08xx xxx xxx"></div></div>' +'<div class="mg"><div class="mf"><label>Вид услуга</label><select id="mSv">'+SO+'</select></div><div class="mf"><label>Статус</label><select id="mSt"><option value="new"'+(p&&p.status==='new'?' selected':'')+'>Нов</option><option value="active"'+(p&&p.status==='active'?' selected':'')+'>В процес</option><option value="review"'+(p&&p.status==='review'?' selected':'')+'>Ревизия</option><option value="paused"'+(p&&p.status==='paused'?' selected':'')+'>Пауза</option><option value="done"'+(p&&p.status==='done'?' selected':'')+'>Завършен</option></select></div></div>' +'<div class="mg"><div class="mf"><label>Сума (€)</label><input type="number" step="0.01" id="mTo" value="'+(p?p.total||'':'')+'" placeholder="0.00"></div><div class="mf"><label>Получено (€)</label><input type="number" step="0.01" id="mRc" value="'+(p?p.received||'':'')+'" placeholder="0.00"></div></div>' +'<div class="mg"><div class="mf"><label>Начало</label><input type="date" id="mSt2" value="'+(p?p.start_date||'':'')+'"></div><div class="mf"><label>Краен срок</label><input type="date" id="mDl" value="'+(p?p.deadline||'':'')+'"></div></div>' +'<div class="mg"><div class="mf"><label>Напредък %</label><input type="number" min="0" max="100" id="mPr" value="'+(p?p.progress||0:0)+'"></div><div class="mf"><label>Отговорник</label><select id="mAs">'+tO()+'</select></div></div>' +'<div class="mg"><div class="mf"><label>Тагове (запетая)</label><input id="mTg" value="'+(p&&p.tags?p.tags.join(', '):'')+'" placeholder="WordPress, SEO, Дизайн"></div><div class="mf"><label>Абонамент</label><select id="mRr"><option value="">Еднократно</option><option value="мес"'+(p&&p.recurring==='мес'?' selected':'')+'>Месечно</option><option value="год"'+(p&&p.recurring==='год'?' selected':'')+'>Годишно</option></select></div></div>' +'<div class="mf"><label>Бележки</label><textarea rows="2" id="mNt">'+(p?esc(p.notes||''):'')+'</textarea></div>' +'<div class="ma">'+(p?'<button class="btn d" onclick="dPrj(\''+p.id+'\')">Изтрий</button>':'')+'<button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="'+(p?'sPrj(\''+p.id+'\')':'sPrj()')+'">Запази</button></div>'; }, task:function(){return '<h3>+ Нова задача</h3><div class="mf"><label>Описание</label><input id="tTx" placeholder="Какво трябва да се направи?"></div><div class="mg"><div class="mf"><label>Отговорник</label><select id="tAs">'+tO()+'</select></div><div class="mf"><label>Приоритет</label><select id="tUr"><option value="0">Нормален</option><option value="1">Спешен ⚠</option></select></div></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="sTask()">Добави</button></div>';}, calTask:function(ds){return '<h3>+ Задача с дата</h3><div class="mf"><label>Описание</label><input id="ctTx" placeholder="Какво трябва да се направи?"></div><div class="mg"><div class="mf"><label>Дата</label><input type="date" id="ctDt" value="'+(ds||new Date().toISOString().slice(0,10))+'"></div><div class="mf"><label>Час (по избор)</label><input type="time" id="ctTm"></div></div><div class="mg"><div class="mf"><label>Отговорник</label><select id="ctAs">'+tO()+'</select></div><div class="mf"><label>Приоритет</label><select id="ctUr"><option value="0">Нормален</option><option value="1">Спешен ⚠</option></select></div></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="sCalTask()">Добави</button></div>';}, note:function(){return '<h3>+ Нова бележка</h3><div class="mf"><label>Заглавие</label><input id="nTi" placeholder="Бележка за проект / клиент"></div><div class="mf"><label>Текст</label><textarea rows="4" id="nBo" placeholder="Детайли, уговорки..."></textarea></div><div class="mg"><div class="mf"><label>Автор</label><select id="nAu">'+tO()+'</select></div><div class="mf"><label>Спешна?</label><select id="nUr"><option value="0">Не</option><option value="1">Да ⚠</option></select></div></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="sNote()">Запази</button></div>';}, client:function(){return '<h3>+ Нов клиент</h3><div class="mg"><div class="mf"><label>Имена</label><input id="cNm" placeholder="Иван Иванов"></div><div class="mf"><label>Фирма</label><input id="cCo" placeholder="Фирма ЕООД"></div></div><div class="mg"><div class="mf"><label>Email</label><input type="email" id="cEm"></div><div class="mf"><label>Телефон</label><input type="tel" id="cPh"></div></div><div class="mf"><label>ЕИК</label><input id="cEk" placeholder="123456789"></div><div class="mf"><label>Бележки</label><textarea rows="2" id="cNt"></textarea></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="sCli()">Запази</button></div>';}, hosting:function(){return '<h3>+ Нов хостинг</h3><div class="mg"><div class="mf"><label>Домейн</label><input id="hDo" placeholder="example.com"></div><div class="mf"><label>Клиент</label><input id="hCl"></div></div><div class="mg"><div class="mf"><label>Сървър</label><input id="hSr" placeholder="srv1.webtrixia.com"></div><div class="mf"><label>Тип</label><select id="hTy"><option>cPanel</option><option>Extern</option><option>VPS</option><option>Shared</option></select></div></div><div class="mg"><div class="mf"><label>Диск</label><input id="hDi" placeholder="2/10GB"></div><div class="mf"><label>План</label><select id="hPl"><option>Starter</option><option>Business</option><option>Pro</option><option>Shared</option></select></div></div><div class="mf"><label>SSL изтича</label><input type="date" id="hSsl"></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="sHost()">Запази</button></div>';}, password:function(){ var p=PW.editId?D.passwords.find(function(x){return x.id===PW.editId;}):null; var cl=(p&&p.client)||PW.formClient||''; var opts=D.clients.map(function(c){var n=esc(c.company||c.name||'');return '<option value="'+n+'"></option>';}).join(''); return '<h3>'+(p?'Редактирай достъп':'+ Нов достъп')+'</h3><div class="mf"><label>Клиент</label><input id="pCl" list="pClientList" value="'+esc(cl)+'" placeholder="Избери или въведи клиент"><datalist id="pClientList">'+opts+'</datalist></div><div class="mf"><label>Сайт / Услуга</label><input id="pSi" value="'+(p?esc(p.site||''):'')+'" placeholder="example.com — cPanel"></div><div class="mg"><div class="mf"><label>Потребител</label><input id="pUs" value="'+(p?esc(p.username||''):'')+'"></div><div class="mf"><label>Парола</label><input type="text" id="pPw" value="'+(p?esc(p.pass||''):'')+'"></div></div><div class="mf"><label>Категория</label><select id="pCa"><option value="hosting"'+(p&&p.category==='hosting'?' selected':'')+'>Хостинг</option><option value="wordpress"'+(p&&p.category==='wordpress'?' selected':'')+'>WordPress</option><option value="google"'+(p&&p.category==='google'?' selected':'')+'>Google</option><option value="social"'+(p&&p.category==='social'?' selected':'')+'>Социални мрежи</option><option value="dns"'+(p&&p.category==='dns'?' selected':'')+'>DNS</option><option value="design"'+(p&&p.category==='design'?' selected':'')+'>Дизайн</option><option value="other"'+(!p||p.category==='other'?' selected':'')+'>Друго</option></select></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="sPass()">Запази</button></div>'; }, offer:function(){return '<h3>+ Запиши оферта</h3><div class="mg"><div class="mf"><label>Номер</label><input id="oNm" placeholder="WT-2025-001"></div><div class="mf"><label>Клиент</label><input id="oCl"></div></div><div class="mf"><label>Услуга</label><select id="oSv">'+SO+'</select></div><div class="mg"><div class="mf"><label>Сума (€)</label><input type="number" step="0.01" id="oTo" placeholder="0.00"></div><div class="mf"><label>Статус</label><select id="oSt"><option>Чакаща</option><option>Приета</option><option>Отказана</option></select></div></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn s" onclick="sOff();window.open(\'offer.html\',\'_blank\')">Запази + Оферта</button><button class="btn p" onclick="sOff()">Запази</button></div>';}, contract:function(){return '<h3>+ Нов договор</h3><div class="mg"><div class="mf"><label>Клиент</label><input id="coC"></div><div class="mf"><label>Описание</label><input id="coN" placeholder="Уебсайт, Маркетинг..."></div></div><div class="mf"><label>Дата на подписване</label><input type="date" id="coDt"></div><div class="ma"><button class="btn" onclick="cmo()">Отказ</button><button class="btn p" onclick="sCon()">Запази</button></div>';} }; async function modal(type,id){ if(!D.users.length)D.users=await af('users')||[]; var h=''; if(type==='editProject'&&id){var p=D.projects.find(function(x){return x.id===id;});h=ML.project(p);} else if(type==='project')h=ML.project(null); else if(type==='newUser')h=uForm(null); else if(type==='editUser'&&id){var u=D.users.find(function(x){return String(x.id)===String(id);});h=uForm(u);} else if(type==='calTask')h=ML.calTask(id||null); else if(ML[type])h=ML[type](); document.getElementById('mb').innerHTML=h; document.getElementById('mo').classList.add('open'); if(type==='editUser'&&id){ setTimeout(function(){ var u=D.users.find(function(x){return String(x.id)===String(id);}); if(u){var s=document.getElementById('uC');if(s)s.value=u.color||'av-p';} },10); } } function cmo(){document.getElementById('mo').classList.remove('open');} // ── SAVE ── async function sPrj(eid){ var n=gv('mN');if(!n){toast('Въведете название','err');return;} var d={id:eid||undefined,name:n,client:gv('mCl'),phone:gv('mPh'),status:gv('mSt'),total:gv('mTo')||0,received:gv('mRc')||0,start:gv('mSt2')||null,deadline:gv('mDl')||null,progress:parseInt(gv('mPr'))||0,assignee:gv('mAs'),tags:gv('mTg').split(',').map(function(t){return t.trim();}).filter(Boolean),recurring:gv('mRr'),notes:gv('mNt')}; var r=await af('projects','POST',d); if(r){await lA(gv('mAs'),(eid?'обнови':'добави')+' проект: '+n);cmo();await loadAll();toast(eid?'Обновен!':'Добавен!','ok');} else toast('Грешка при запис','err'); } async function dPrj(id){if(!confirm('Изтрий проекта?'))return;await af('projects','DELETE',null,'&id='+id);cmo();await loadAll();toast('Изтрито');} async function sTask(){ var tx=gv('tTx');if(!tx){toast('Въведете описание','err');return;} var r=await af('tasks','POST',{text:tx,assignee:gv('tAs'),urgent:gv('tUr'),done:0}); if(r){cmo();D.tasks=await af('tasks')||[];rTasks();rDash();toast('Добавена!','ok');} } async function sCalTask(){ var tx=gv('ctTx');if(!tx){toast('Въведете описание','err');return;} var dt=gv('ctDt');if(!dt){toast('Изберете дата','err');return;} var tm=gv('ctTm'); var r=await af('tasks','POST',{text:tx,assignee:gv('ctAs'),urgent:gv('ctUr'),done:0,due_date:tm?dt+'T'+tm:dt}); if(r){cmo();D.tasks=await af('tasks')||[];rCalendar();toast('Добавена!','ok');} } async function sNote(){ var ti=gv('nTi');if(!ti){toast('Въведете заглавие','err');return;} var r=await af('notes','POST',{title:ti,body:gv('nBo'),author:gv('nAu'),urgent:gv('nUr')}); if(r){cmo();D.notes=await af('notes')||[];rNotes();rDash();toast('Запазена!','ok');} } async function sCli(){ var n=gv('cNm');if(!n){toast('Въведете име','err');return;} var r=await af('clients','POST',{name:n,company:gv('cCo'),email:gv('cEm'),phone:gv('cPh'),eik:gv('cEk'),notes:gv('cNt')}); if(r){cmo();D.clients=await af('clients')||[];rCli();toast('Добавен!','ok');} } async function sHost(){ var do2=gv('hDo');if(!do2){toast('Въведете домейн','err');return;} var r=await af('hosting','POST',{domain:do2,client:gv('hCl'),server:gv('hSr'),type:gv('hTy'),disk:gv('hDi'),plan:gv('hPl'),ssl:gv('hSsl')||null}); if(r){cmo();D.hosting=await af('hosting')||[];rHosting();toast('Добавен!','ok');} } async function sPass(){ var si=gv('pSi');if(!si){toast('Въведете сайт','err');return;} var client=gv('pCl'); var r=await af('passwords','POST',{id:PW.editId||undefined,site:si,user:gv('pUs'),pass:gv('pPw'),cat:gv('pCa'),client:client}); if(r){PW.client=client||'Без клиент';PW.editId='';cmo();D.passwords=await af('passwords')||[];rPasswords();toast('Записано!','ok');} } async function sOff(){ var r=await af('offers','POST',{num:gv('oNm'),client:gv('oCl'),service:gv('oSv'),total:gv('oTo')||0,status:gv('oSt')}); if(r){cmo();D.offers=await af('offers')||[];rOffers();toast('Записана!','ok');} } async function sCon(){ var r=await af('contracts','POST',{client:gv('coC'),name:gv('coN'),sign_date:gv('coDt')||null}); if(r){cmo();D.contracts=await af('contracts')||[];rContracts();toast('Добавен!','ok');} } async function sU(eid){ var n=gv('uN');if(!n){toast('Въведете име','err');return;} var u=gv('uU');if(!u){toast('Въведете потребителско име','err');return;} var p=gv('uP');if(!eid&&p.length<6){toast('Паролата трябва да е поне 6 символа','err');return;} var d={id:eid||undefined,name:n,role:gv('uR'),username:u,initials:gv('uI')||n.slice(0,2).toUpperCase(),color:gv('uC')||'av-p',is_admin:gv('uA'),online:1}; if(p)d.password=p; var r=await af('users','POST',d); if(r){cmo();D.users=await af('users')||[];rUsers();rTeam();toast(eid?'Обновен!':'Добавен!','ok');} else toast('Грешка — потребителското име може да е заето','err'); } async function dU(id){if(!confirm('Изтрий потребителя?'))return;await af('users','DELETE',null,'&id='+id);D.users=await af('users')||[];rUsers();rTeam();cmo();toast('Изтрито');} async function dr(tbl,id){ if(!confirm('Изтрий?'))return; await af(tbl,'DELETE',null,'&id='+id); D[tbl]=await af(tbl)||[]; var m={notes:rNotes,hosting:rHosting,passwords:rPasswords,contracts:rContracts,offers:rOffers}; if(m[tbl])m[tbl](); toast('Изтрито'); } async function lA(who,what){ var u=D.users.find(function(m){return m.name===who;})||D.users[0]; await af('activity','POST',{who:who||'Система',what:what,color:(u&&u.color)||'av-p',initials:(u&&u.initials)||'??'}); D.activity=await af('activity')||[]; } function chipS(el){el.closest('.chips').querySelectorAll('.chip').forEach(function(c){c.classList.remove('active');});el.classList.add('active');} function toast(msg,type){ var t=document.getElementById('toast');t.textContent=msg;t.className='toast show '+(type||''); setTimeout(function(){t.className='toast';},2500); } </script> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 8.2.31 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings