// Chart Growth Hub — Student Trading Journal panel (VIP-only). // Table: journal_entries (student_id, trade_date, pair, direction, entry_price, exit_price, pnl, notes). // RLS restricts every row to student_id = auth.uid() AND my_vip(), so the panel itself just needs to // be shown behind the isVIP gate already used elsewhere in the dashboard. // Exposes window.JournalPanel. (function(){ const { useState, useEffect, useMemo } = React; const fmtDate = d => d ? new Date(d+'T00:00:00').toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'}) : '—'; const money = n => (n==null||n==='') ? '—' : (Number(n)>=0?'+':'') + '$' + Number(n).toFixed(2); function blankForm(){ return { trade_date:new Date().toISOString().slice(0,10), pair:'', direction:'long', entry_price:'', exit_price:'', pnl:'', notes:'' }; } function JournalPanel({client, session}){ const [rows,setRows]=useState(null); const [err,setErr]=useState(""); const [form,setForm]=useState(null); // null = closed, object = add/edit form open const [busy,setBusy]=useState(false); async function load(){ const { data, error } = await client.from('journal_entries').select('*').eq('student_id',session.user.id).order('trade_date',{ascending:false}).order('created_at',{ascending:false}); if(error) setErr(error.message); else setRows(data||[]); } useEffect(()=>{ load(); /* eslint-disable-next-line */ },[]); const stats = useMemo(()=>{ const r=rows||[]; const withPnl=r.filter(x=>x.pnl!=null && x.pnl!==''); const wins=withPnl.filter(x=>Number(x.pnl)>0).length; const total=withPnl.reduce((s,x)=>s+Number(x.pnl),0); return { count:r.length, winRate: withPnl.length? Math.round(wins/withPnl.length*100):null, total }; },[rows]); function openAdd(){ setForm(blankForm()); } function openEdit(r){ setForm({ ...r, entry_price:r.entry_price??'', exit_price:r.exit_price??'', pnl:r.pnl??'' }); } async function save(){ if(!form.pair.trim()){ setErr('Enter the pair/asset.'); return; } setErr(""); setBusy(true); const payload = { student_id: session.user.id, trade_date: form.trade_date, pair: form.pair.trim().toUpperCase(), direction: form.direction, entry_price: form.entry_price===''?null:Number(form.entry_price), exit_price: form.exit_price===''?null:Number(form.exit_price), pnl: form.pnl===''?null:Number(form.pnl), notes: form.notes||null, }; let error; if(form.id){ ({ error } = await client.from('journal_entries').update(payload).eq('id',form.id)); } else { ({ error } = await client.from('journal_entries').insert(payload)); } setBusy(false); if(error){ setErr(error.message); return; } setForm(null); load(); } async function del(r){ if(!confirm('Delete this journal entry?')) return; setRows(rs=>rs.filter(x=>x.id!==r.id)); const { error } = await client.from('journal_entries').delete().eq('id',r.id); if(error){ setErr(error.message); load(); } } if(!rows) return
Log every trade and track your progress over time. Only you can see this.