"); printWin.document.close(); printWin.focus(); setTimeout(function(){ printWin.print(); }, 500); }; return (
); } return (
{sel && party && (
{sel}
{party.type} - UID: {party.uid}
{party.email &&
Email: {party.email}
} {party.mobile &&
Mobile: {party.mobile}
} {party.altContact &&
Alt. Contact: {party.altContact}
} {party.address &&
Address: {party.address}
}
Member Since: {fmtD(firstD || party.dateAdded)}
{lastD &&
Last Txn: {fmtD(lastD)}
}
{["INR", "AED", "Gold"].map((cur) => { if (!pt.some((t) => t.currency === cur)) return null; const b = totBal(cur); const st = statusLabel(b, party.type, sel); return (
{cur}
{fmt(Math.abs(b), cur)}
{st.text}
); })}
Print Statement
setFromDate(e.target.value)} style={{ width: 130, padding: 6, fontSize: 11 }} />
setToDate(e.target.value)} style={{ width: 130, padding: 6, fontSize: 11 }} />
)}
); } async function hashPw(pw){ const enc=new TextEncoder().encode(pw+"lk_salt_2026"); const buf=await crypto.subtle.digest("SHA-256",enc); return Array.from(new Uint8Array(buf)).map(b=>b.toString(16).padStart(2,"0")).join(""); } let fileHandle=null; async function saveToFile(data){ try{ if(!fileHandle){ fileHandle=await window.showSaveFilePicker({suggestedName:"LijoAccountData.json",types:[{description:"JSON Data",accept:{"application/json":[".json"]}}]}); } const w=await fileHandle.createWritable(); await w.write(JSON.stringify(data,null,2)); await w.close(); return true; }catch(e){if(e.name!=="AbortError")console.error("Save:",e);return false;} } async function loadFromFile(){ try{ const[handle]=await window.showOpenFilePicker({types:[{description:"JSON Data",accept:{"application/json":[".json"]}}]}); fileHandle=handle; const file=await handle.getFile(); const text=await file.text(); return JSON.parse(text); }catch(e){if(e.name!=="AbortError")console.error("Load:",e);return null;} } function MainApp(){ const[locked,setLocked]=useState(true); const[setupMode,setSetupMode]=useState(false); const[pw,setPw]=useState(""); const[pw2,setPw2]=useState(""); const[err,setErr]=useState(""); const[attempts,setAttempts]=useState(0); const[appData,setAppData]=useState(null); const[lastSave,setLastSave]=useState(null); const[dirty,setDirty]=useState(false); const[fileName,setFileName]=useState(""); const[storedHash,setStoredHash]=useState(null); const handleSetup=async()=>{ if(pw.length<4){setErr("Password must be at least 4 characters");return;} if(pw!==pw2){setErr("Passwords do not match");return;} const hash=await hashPw(pw); const data={passwordHash:hash,parties:SEED_P.map(p=>({...p})),transactions:SEED_T.map(t=>({...t}))}; const saved=await saveToFile(data); if(saved){setAppData(data);setStoredHash(hash);setLocked(false);setLastSave(new Date());setFileName(fileHandle?fileHandle.name:"LijoAccountData.json");} }; const handleLogin=async()=>{ if(!storedHash){setErr("Please load your data file first");return;} const hash=await hashPw(pw); if(hash===storedHash){setLocked(false);setErr("");setAttempts(0);} else{const a=attempts+1;setAttempts(a);setErr("Wrong password"+(a>=3?" \u2014 "+a+" failed attempts":""));} }; const handleLoad=async()=>{ const data=await loadFromFile(); if(data){ if(data.passwordHash){setStoredHash(data.passwordHash);setAppData(data);setFileName(fileHandle?fileHandle.name:"data.json");setErr("");setPw("");} else{setAppData(data);setSetupMode(true);setFileName(fileHandle?fileHandle.name:"data.json");} } }; const handleDataChange=useCallback(async(parties,txns)=>{ const data={passwordHash:storedHash,parties,transactions:txns}; setAppData(data);setDirty(true); const saved=await saveToFile(data); if(saved){setLastSave(new Date());setDirty(false);} },[storedHash]); const handleSaveAs=async()=>{ if(appData){fileHandle=null;const saved=await saveToFile(appData);if(saved){setLastSave(new Date());setDirty(false);setFileName(fileHandle?fileHandle.name:"");}} }; if(locked){ return(
PAS
myPAS
my Personal Account Statement
{!storedHash&&!setupMode?(
or
Load your existing data file to login, or set up a new account if this is your first time.
):setupMode?(
Create your password
{err&&
{err}
} {setPw(e.target.value);setErr("");}}/> {setPw2(e.target.value);setErr("");}}/>
):(
File loaded: {fileName}
{err&&
{err}
} {setPw(e.target.value);setErr("");}} onKeyDown={e=>e.key==="Enter"&&handleLogin()} autoFocus/>
)}
); } return(
{fileName||"No file"} {lastSave&& \u2014 Saved {lastSave.toLocaleTimeString()}} {dirty&& \u2014 Unsaved changes}
); } function AppInner({initialParties,initialTxns,onDataChange}){ const[tab,setTab]=useState("dashboard"); const[parties,setParties]=useState([]); const[txns,setTxns]=useState([]); const[toast,setToast]=useState(null); useEffect(()=>{setParties(initialParties.map(p=>({...p})));setTxns(initialTxns.map(t=>({...t})));},[]); const saveRef=React.useRef(null); useEffect(()=>{ if(parties.length===0&&txns.length===0)return; if(saveRef.current)clearTimeout(saveRef.current); saveRef.current=setTimeout(()=>{onDataChange(parties,txns);},500); return()=>{if(saveRef.current)clearTimeout(saveRef.current);}; },[parties,txns]); const notify=(m,ok)=>{setToast({m,ok:ok!==false});setTimeout(()=>setToast(null),2500);}; const addParty=(p)=>{const uid=genUID(parties);setParties(v=>[...v,{...p,uid,dateAdded:today()}]);notify("Party added - UID: "+uid);return uid;}; const updParty=(uid,p)=>{setParties(v=>v.map(x=>x.uid===uid?{...x,...p}:x));notify("Party updated");}; const delParty=(uid)=>{setParties(v=>v.filter(x=>x.uid!==uid));notify("Party deleted",false);}; const addTxn=(t)=>{const no=genTXN(txns);setTxns(v=>[...v,{...t,txnNo:no}]);notify("Transaction recorded - "+no);return no;}; const updTxn=(no,t)=>{setTxns(v=>v.map(x=>x.txnNo===no?{...x,...t}:x));notify("Transaction updated");}; const delTxn=(no)=>{setTxns(v=>v.filter(x=>x.txnNo!==no));notify("Transaction deleted",false);}; const reset=()=>{if(confirm("Reset ALL data to original import? This cannot be undone.")){if(confirm("Are you absolutely sure? All your changes will be lost.")){setParties(SEED_P.map(p=>({...p})));setTxns(SEED_T.map(t=>({...t})));notify("Data reset");}}}; const exportXL=()=>{ const wb=XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb,XLSX.utils.json_to_sheet(parties.map(p=>({UID:p.uid,Name:p.name,Type:p.type,Email:p.email,Mobile:p.mobile,Address:p.address,"Date Added":p.dateAdded}))),"Parties"); XLSX.utils.book_append_sheet(wb,XLSX.utils.json_to_sheet(txns.map(t=>({"Txn No":t.txnNo,Date:t.date,Party:t.partyName,Mode:t.mode,Description:t.description,Currency:t.currency,Value:t.value,"Dr/Cr":t.debitCredit}))),"Transactions"); XLSX.writeFile(wb,"Lijo_Account_Statement.xlsx"); notify("Excel exported!"); }; const tabList=[{id:"dashboard",l:"Dashboard"},{id:"input",l:"New Entry"},{id:"parties",l:"Parties"},{id:"transactions",l:"Transactions"},{id:"statement",l:"Statement"}]; return(
{toast&&
{toast.m}
}
PAS
myPAS
my Personal Account Statement
Export ExcelReset
{tabList.map(t=>)}
{tab==="dashboard"&&} {tab==="input"&&} {tab==="parties"&&} {tab==="transactions"&&} {tab==="statement"&&}
); } ReactDOM.render(React.createElement(MainApp), document.getElementById("root"));