190 lines
13 KiB
HTML
190 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<script src="/auth-guard.js?v=4"></script>
|
||
<script src="/company-list.js"></script>
|
||
<meta charset="UTF-8"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
|
||
<title>Receipts History — SAP B1</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Bebas+Neue&display=swap" rel="stylesheet"/>
|
||
<style>
|
||
:root{--ink:#080e18;--ink2:#0d1626;--ink3:#111f35;--purple:#9f5afd;--blue:#1a6fff;--amber:#f5a623;--green:#0dbb8a;--red:#e53e3e;--teal:#06b6d4;--border:#1c2e48;--border2:#243650;--text:#c8d6e8;--text2:#7a9ab8;--text3:#3d5470;--surface:#0f1d30;--surface2:#142338;--surface3:#0b1520;--radius:10px;--font:'Space Grotesk',sans-serif;--mono:'JetBrains Mono',monospace;--display:'Bebas Neue',sans-serif;--shadow:0 8px 32px rgba(0,0,0,.5)}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font-family:var(--font);background:var(--ink);color:var(--text);min-height:100vh;-webkit-font-smoothing:antialiased}
|
||
nav{position:sticky;top:0;z-index:200;background:rgba(8,14,24,.92);backdrop-filter:blur(20px);border-bottom:1px solid var(--border);padding:0 12px;height:50px;display:flex;align-items:center;justify-content:space-between;gap:6px}
|
||
.nav-logo{background:var(--green);color:#fff;font-family:var(--display);font-size:15px;letter-spacing:2px;padding:3px 8px;border-radius:5px}
|
||
.nav-links{display:flex;gap:2px;overflow-x:auto;flex:1;justify-content:center}
|
||
.nav-link{padding:4px 8px;border-radius:5px;font-size:10px;font-weight:600;color:var(--text2);text-decoration:none;white-space:nowrap;font-family:var(--font)}.nav-link:hover{color:var(--text);background:rgba(255,255,255,.05)}.nav-link.active{color:var(--green);background:rgba(13,187,138,.1)}
|
||
.btn-sm{padding:4px 8px;border-radius:5px;font-size:10px;font-weight:700;border:1px solid var(--border2);color:var(--text3);background:none;cursor:pointer;font-family:var(--font)}.btn-sm:hover{border-color:var(--red);color:var(--red)}
|
||
main{padding:12px;max-width:1100px;margin:0 auto}
|
||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:12px;overflow:hidden}
|
||
.card-hd{background:var(--surface3);padding:10px 14px;font-size:12px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--border)}
|
||
.card-hd .right{margin-left:auto;font-size:11px;color:var(--text3);font-weight:600}
|
||
.card-bd{padding:12px 14px}
|
||
.fg{margin-bottom:10px}
|
||
.fl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-bottom:4px;display:block}
|
||
.fi{width:100%;padding:8px 10px;border:1.5px solid var(--border2);border-radius:7px;font-size:13px;font-family:var(--font);background:var(--surface2);color:var(--text);outline:none;height:36px}.fi:focus{border-color:var(--green);box-shadow:0 0 0 2px rgba(13,187,138,.15)}.fi::placeholder{color:var(--text3)}
|
||
select.fi{cursor:pointer;appearance:none;background:var(--surface2) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%233d5470'/%3E%3C/svg%3E") no-repeat right 10px center;padding-right:26px}
|
||
.fi-mono{font-family:var(--mono);font-size:12px}
|
||
.btn{padding:10px 20px;border-radius:8px;border:none;font-family:var(--font);font-size:13px;font-weight:700;cursor:pointer;transition:all .18s;display:inline-flex;align-items:center;gap:6px;justify-content:center}.btn:disabled{opacity:.4;cursor:not-allowed}
|
||
.empty-msg{text-align:center;padding:24px;color:var(--text3);font-size:12px}
|
||
.sp{width:14px;height:14px;border:2px solid rgba(255,255,255,.25);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;display:inline-block}
|
||
@keyframes spin{to{transform:rotate(360deg)}}
|
||
.toast-c{position:fixed;top:56px;right:10px;z-index:9999;display:flex;flex-direction:column;gap:6px;pointer-events:none}
|
||
.toast{padding:9px 13px;border-radius:8px;font-size:12px;font-weight:600;box-shadow:var(--shadow);max-width:280px}
|
||
.t-ok{background:var(--green);color:#fff}.t-err{background:var(--red);color:#fff}
|
||
/* Receipts table */
|
||
.rh-wrap{overflow-x:auto}
|
||
table.rh{width:100%;border-collapse:separate;border-spacing:0;font-size:11.5px;min-width:820px}
|
||
table.rh th{text-align:left;padding:8px 10px;font-size:9.5px;font-weight:700;text-transform:uppercase;color:var(--text3);border-bottom:2px solid var(--border2);white-space:nowrap}
|
||
table.rh td{padding:9px 10px;border-bottom:1px solid var(--border);vertical-align:middle}
|
||
table.rh tr:hover td{background:rgba(13,187,138,.05)}
|
||
.rh-mono{font-family:var(--mono)}
|
||
.rh-doc{font-family:var(--mono);font-weight:700;color:var(--green)}
|
||
.rh-pwo{font-family:var(--mono);color:var(--teal)}
|
||
.rh-item{font-family:var(--mono);color:var(--text)}
|
||
.rh-desc{color:var(--text2);max-width:280px;white-space:normal}
|
||
.tt-pill{font-size:9px;font-weight:700;padding:1px 6px;border-radius:3px}
|
||
.tt-c{background:rgba(13,187,138,.12);color:var(--green)}
|
||
.tt-r{background:rgba(229,62,62,.12);color:var(--red)}
|
||
</style>
|
||
<link rel="stylesheet" href="/theme.css?v=3"/>
|
||
</head>
|
||
<body>
|
||
<div class="toast-c" id="toast-c"></div>
|
||
<nav>
|
||
<span class="nav-logo">SAP</span>
|
||
<div class="nav-links">
|
||
<a href="/" class="nav-link">Home</a><a href="/production" class="nav-link">Production</a>
|
||
<a href="/issue-production" class="nav-link">Issue</a>
|
||
<a href="/receipt-production" class="nav-link">Receipt</a>
|
||
<a href="/receipts-history" class="nav-link active">Receipts History</a>
|
||
<a href="/close-production" class="nav-link">Close</a>
|
||
</div>
|
||
<button class="btn-sm" id="btn-logout">Logout</button>
|
||
</nav>
|
||
<main id="app"></main>
|
||
|
||
<script>
|
||
'use strict';
|
||
let _tok=null,_user=null;
|
||
const BE='/api';
|
||
(function(){try{_tok=sessionStorage.getItem('portal_token');const u=sessionStorage.getItem('portal_user');if(u)_user=JSON.parse(u);}catch(_e){}})();
|
||
if(!_tok)window.location.href='/';
|
||
document.getElementById('btn-logout').onclick=()=>{sessionStorage.clear();document.cookie='portal_token=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';document.cookie='portal_user=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';;localStorage.removeItem('token');localStorage.removeItem('portal_user');window.location.href='/';};
|
||
|
||
let _co=window.__DEFAULT_COMPANY__;
|
||
let _companies=[{value:_co,label:_co}];
|
||
function coOptions(sel){return _companies.map(c=>`<option value="${esc(c.value)}" ${sel===c.value?'selected':''}>${esc(c.label)}</option>`).join('');}
|
||
let _rows=[];
|
||
let _loading=false;
|
||
let _search='';
|
||
let _page=0,_pageSize=20,_total=null,_rawCount=0;
|
||
|
||
function esc(v){return(v||'').toString().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
||
function q(s){return document.querySelector(s);}
|
||
function mk(c){const d=document.createElement('div');d.className=c;return d;}
|
||
const _MON=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||
function fmtDate(iso){if(!iso)return '—';const d=new Date(iso);if(isNaN(d))return '—';return `${String(d.getUTCDate()).padStart(2,'0')} ${_MON[d.getUTCMonth()]} ${d.getUTCFullYear()}`;}
|
||
function fmtQty(n){return(Math.round((parseFloat(n)||0)*1000)/1000).toString();}
|
||
|
||
async function api(m,p,b=null){
|
||
const h={'Content-Type':'application/json'};if(_tok)h['Authorization']='Bearer '+_tok;
|
||
const o={method:m.toUpperCase(),headers:h};if(b)o.body=JSON.stringify(b);
|
||
let r,d;try{r=await fetch(BE+p,o);d=await r.json();}catch(_e){throw new Error('Cannot reach server');}
|
||
if(r.status===401){window.location.href='/';throw new Error('Session expired');}
|
||
if(!r.ok)throw new Error(d?.message||'HTTP '+r.status);return d;
|
||
}
|
||
function toast(msg,type='ok'){const tc=document.getElementById('toast-c');const el=document.createElement('div');el.className=`toast t-${type}`;el.textContent=(type==='ok'?'OK ':'ERR ')+msg;tc.appendChild(el);setTimeout(()=>el.remove(),4500);}
|
||
|
||
async function load(resetPage){
|
||
if(resetPage)_page=0;
|
||
_loading=true;render();
|
||
const searchQ=_search.trim()?`&search=${encodeURIComponent(_search.trim())}`:'';
|
||
try{
|
||
const r=await api('GET',`/sap/receipts-history?top=${_pageSize}&skip=${_page*_pageSize}&company=${encodeURIComponent(_co)}${searchQ}`);
|
||
_rows=r.data||[];_rawCount=_rows.length;_total=(typeof r.total==='number')?r.total:null;
|
||
}catch(e){toast(e.message,'err');_rows=[];_rawCount=0;_total=null;}
|
||
_loading=false;render();
|
||
}
|
||
|
||
function hasNext(){ if(_total!=null) return (_page+1)*_pageSize<_total; return _rawCount>=_pageSize; }
|
||
|
||
function render(){try{_doRender();}catch(e){console.error(e);document.getElementById('app').innerHTML=`<div style="padding:20px;color:var(--red)">Error: ${e.message}</div>`;}}
|
||
function _doRender(){
|
||
const app=document.getElementById('app');app.innerHTML='';
|
||
|
||
const c1=mk('card');
|
||
c1.innerHTML=`<div class="card-hd"><span>Receipts History</span><span class="right">Posted Receipt from Production documents</span></div>
|
||
<div class="card-bd">
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end">
|
||
<div class="fg" style="margin:0;min-width:150px"><label class="fl">Company</label><select class="fi" id="sel-co">${coOptions(_co)}</select></div>
|
||
<div class="fg" style="margin:0;flex:1;min-width:240px"><label class="fl">Search</label>
|
||
<input class="fi fi-mono" id="rh-search" value="${esc(_search)}" placeholder="PWO No, receipt doc no, item code / description or batch…"/></div>
|
||
<button class="btn" id="rh-search-btn" style="background:var(--green);color:#fff;height:36px">Search</button>
|
||
</div>
|
||
<div style="font-size:10px;color:var(--text3);margin-top:6px">Newest first. Search matches PWO No, receipt document number, item code/description, or batch number.</div>
|
||
</div>`;
|
||
app.appendChild(c1);
|
||
|
||
const c2=mk('card');
|
||
let body;
|
||
if(_loading){
|
||
body=`<div class="empty-msg"><span class="sp" style="border-color:rgba(13,187,138,.25);border-top-color:var(--green)"></span> Loading receipts…</div>`;
|
||
}else if(!_rows.length){
|
||
body=`<div class="empty-msg">No receipts found${_search.trim()?` for "${esc(_search.trim())}"`:''}.</div>`;
|
||
}else{
|
||
const trs=_rows.map(r=>`<tr>
|
||
<td class="rh-doc">${esc(r.receiptDocNum)}</td>
|
||
<td class="rh-mono" style="white-space:nowrap">${fmtDate(r.docDate)}</td>
|
||
<td class="rh-pwo">${esc(r.pwoNum||r.pwoAbsEntry||'-')}</td>
|
||
<td class="rh-item">${esc(r.itemCode)}</td>
|
||
<td class="rh-desc">${esc(r.description||'-')}</td>
|
||
<td class="rh-mono" style="text-align:right">${fmtQty(r.quantity)}</td>
|
||
<td><span class="tt-pill ${r.transType==='R'?'tt-r':'tt-c'}">${r.transType==='R'?'REJECT':'COMPLETE'}</span></td>
|
||
<td class="rh-mono">${esc(r.warehouse||'-')}</td>
|
||
<td class="rh-mono" style="color:var(--amber)">${esc(r.batch||'-')}</td>
|
||
</tr>`).join('');
|
||
body=`<div class="rh-wrap"><table class="rh">
|
||
<thead><tr>
|
||
<th>Receipt Doc No</th><th>Date</th><th>PWO No</th><th>Item</th><th>Description</th>
|
||
<th style="text-align:right">Qty</th><th>Type</th><th>Whs</th><th>Batch</th>
|
||
</tr></thead>
|
||
<tbody>${trs}</tbody>
|
||
</table></div>`;
|
||
}
|
||
const totalPages=_total!=null?Math.max(1,Math.ceil(_total/_pageSize)):null;
|
||
const pager=_loading?'':`<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:12px">
|
||
<button class="btn" id="rh-prev" ${_page===0?'disabled':''} style="padding:6px 14px;font-size:11px;background:var(--surface2);color:var(--text2);border:1px solid var(--border2)${_page===0?';opacity:.45;cursor:not-allowed':''}">‹ Prev</button>
|
||
<span style="font-size:11px;color:var(--text3)">${_total!=null?`Page ${_page+1} of ${totalPages} · ${_total} line${_total===1?'':'s'}`:`Page ${_page+1}`}</span>
|
||
<button class="btn" id="rh-next" ${hasNext()?'':'disabled'} style="padding:6px 14px;font-size:11px;background:var(--surface2);color:var(--text2);border:1px solid var(--border2)${hasNext()?'':';opacity:.45;cursor:not-allowed'}">Next ›</button>
|
||
</div>`;
|
||
c2.innerHTML=`<div class="card-hd"><span>Receipts</span><span class="right">${_rows.length} on this page</span></div><div class="card-bd">${body}${pager}</div>`;
|
||
app.appendChild(c2);
|
||
|
||
setTimeout(()=>{
|
||
q('#sel-co')&&(q('#sel-co').onchange=function(){_co=this.value;load(true);});
|
||
q('#rh-search')&&(q('#rh-search').oninput=function(){_search=this.value;});
|
||
q('#rh-search')&&(q('#rh-search').onkeydown=function(e){if(e.key==='Enter'){e.preventDefault();load(true);}});
|
||
q('#rh-search-btn')&&(q('#rh-search-btn').onclick=()=>load(true));
|
||
q('#rh-prev')&&(q('#rh-prev').onclick=()=>{if(_page>0){_page--;load();}});
|
||
q('#rh-next')&&(q('#rh-next').onclick=()=>{if(hasNext()){_page++;load();}});
|
||
},0);
|
||
}
|
||
|
||
async function init(){
|
||
try{
|
||
const hash=window.location.hash;
|
||
const coMatch=hash.match(/company=([^&]+)/);
|
||
if(coMatch)_co=decodeURIComponent(coMatch[1]);
|
||
try{const list=await window.fetchCompanyList();if(list.length)_companies=list;}catch(_e){}
|
||
render();
|
||
load();
|
||
}catch(e){console.error('Init error:',e);render();}
|
||
}
|
||
init();
|
||
</script>
|
||
<script src="/sidebar.js?v=31"></script>
|
||
</body>
|
||
</html>
|