// WeeklyReview.jsx const WeeklyReviewPage = ({ trades }) => { const { useState, useEffect } = React; const STORAGE_KEY = 'tj_weekly_reviews_v1'; const getWeekKey = (date) => { const d = new Date(date + 'T00:00:00'); const day = d.getDay(); const mon = new Date(d); mon.setDate(d.getDate() - (day === 0 ? 6 : day - 1)); return mon.toISOString().slice(0, 10); }; const getWeekLabel = (key) => { const start = new Date(key + 'T00:00:00'); const end = new Date(start); end.setDate(start.getDate() + 6); return `${start.toLocaleDateString('en-US',{month:'short',day:'numeric'})} – ${end.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'})}`; }; const loadReviews = () => { try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; } catch { return {}; } }; const [reviews, setReviews] = useState(loadReviews); const [selectedWeek, setSelectedWeek] = useState(getWeekKey(new Date().toISOString().slice(0,10))); const [draft, setDraft] = useState(''); const [saved, setSaved] = useState(false); useEffect(() => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(reviews)); } catch {} }, [reviews]); // Get all weeks that have trades const tradeWeeks = [...new Set(trades.filter(t=>t.status==='closed').map(t => getWeekKey(t.date)))].sort((a,b)=>b.localeCompare(a)); const allWeeks = [...new Set([getWeekKey(new Date().toISOString().slice(0,10)), ...tradeWeeks])].sort((a,b)=>b.localeCompare(a)); // Week trades const weekTrades = trades.filter(t => t.status === 'closed' && getWeekKey(t.date) === selectedWeek); const weekStats = computeStats(weekTrades.length > 0 ? weekTrades : []); const weekPnL = weekTrades.reduce((s,t)=>s+(t.pnl||0),0); const weekWins = weekTrades.filter(t=>t.pnl>0).length; const weekLosses = weekTrades.filter(t=>t.pnl<0).length; const bestTrade = weekTrades.length > 0 ? weekTrades.reduce((a,b)=>(b.pnl||0)>(a.pnl||0)?b:a) : null; const worstTrade = weekTrades.length > 0 ? weekTrades.reduce((a,b)=>(b.pnl||0)<(a.pnl||0)?b:a) : null; const currentReview = reviews[selectedWeek] || { what_worked: '', what_didnt: '', lessons: '', goals: '', rating: null }; const [form, setForm] = useState(currentReview); useEffect(() => { const r = reviews[selectedWeek] || { what_worked: '', what_didnt: '', lessons: '', goals: '', rating: null }; setForm(r); setSaved(false); }, [selectedWeek]); const saveReview = () => { setReviews(r => ({ ...r, [selectedWeek]: form })); setSaved(true); setTimeout(() => setSaved(false), 2000); }; const setField = (k, v) => { setForm(f => ({ ...f, [k]: v })); setSaved(false); }; const RATINGS = [ { val: 1, label: '😞', desc: 'Poor' }, { val: 2, label: '😐', desc: 'Below Avg' }, { val: 3, label: 'πŸ™‚', desc: 'Average' }, { val: 4, label: '😊', desc: 'Good' }, { val: 5, label: 'πŸ”₯', desc: 'Excellent' }, ]; const textAreaStyle = { background: 'var(--surface3)', border: '1px solid var(--border)', borderRadius: '8px', color: 'var(--text)', padding: '12px 14px', fontSize: '13px', lineHeight: '1.6', outline: 'none', fontFamily: 'Space Grotesk, sans-serif', width: '100%', boxSizing: 'border-box', resize: 'vertical', minHeight: '90px', transition: 'border-color 0.15s' }; const labelS = { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', display: 'block', marginBottom: '6px' }; return React.createElement('div', { style: { padding: '28px 32px', maxWidth: '1100px' } }, [ // Header React.createElement('div', { key: 'hdr', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' } }, [ React.createElement('div', { key: 'l' }, [ React.createElement('h1', { key: 'h', style: { fontSize: '22px', fontWeight: 700, color: 'var(--text)', margin: 0 } }, 'Weekly Review'), React.createElement('p', { key: 's', style: { fontSize: '13px', color: 'var(--muted)', margin: '4px 0 0' } }, getWeekLabel(selectedWeek)) ]), React.createElement('div', { key: 'nav', style: { display: 'flex', gap: '6px', alignItems: 'center' } }, [ React.createElement('button', { onClick: () => { const idx = allWeeks.indexOf(selectedWeek); if (idx < allWeeks.length-1) setSelectedWeek(allWeeks[idx+1]); }, style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '6px', color: 'var(--text2)', padding: '6px 10px', cursor: 'pointer', fontSize: '14px' } }, '←'), React.createElement('select', { value: selectedWeek, onChange: e => setSelectedWeek(e.target.value), style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '6px', color: 'var(--text)', padding: '6px 12px', fontSize: '12px', fontFamily: 'Space Grotesk, sans-serif', cursor: 'pointer', outline: 'none' } }, allWeeks.map(w => React.createElement('option', { key: w, value: w }, getWeekLabel(w)))), React.createElement('button', { onClick: () => { const idx = allWeeks.indexOf(selectedWeek); if (idx > 0) setSelectedWeek(allWeeks[idx-1]); }, style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '6px', color: 'var(--text2)', padding: '6px 10px', cursor: 'pointer', fontSize: '14px' } }, 'β†’'), ]) ]), React.createElement('div', { key: 'layout', style: { display: 'grid', gridTemplateColumns: '300px 1fr', gap: '20px' } }, [ // Left: Week Stats React.createElement('div', { key: 'stats', style: { display: 'flex', flexDirection: 'column', gap: '12px' } }, [ // P&L summary React.createElement('div', { key: 'pnl', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '18px', borderTop: `2px solid ${weekPnL >= 0 ? '#00d27a' : '#f04060'}` } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '6px' } }, 'Week P&L'), React.createElement(PnLValue, { key: 'v', value: weekTrades.length ? weekPnL : null, size: 'xl' }), React.createElement('div', { key: 'wl', style: { display: 'flex', gap: '12px', marginTop: '10px' } }, [ React.createElement('span', { key: 'w', style: { fontSize: '12px', color: '#00d27a' } }, `${weekWins}W`), React.createElement('span', { key: 'l2', style: { fontSize: '12px', color: '#f04060' } }, `${weekLosses}L`), React.createElement('span', { key: 't', style: { fontSize: '12px', color: 'var(--muted)' } }, `${weekTrades.length} trades`), ]) ]), // Best / Worst bestTrade && React.createElement('div', { key: 'best', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '14px 16px' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '10px', color: 'var(--muted)', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.07em' } }, 'πŸ† Best Trade'), React.createElement('div', { key: 'row', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, [ React.createElement(SymbolCell, { key: 'sym', symbol: bestTrade.symbol }), React.createElement(PnLValue, { key: 'v', value: bestTrade.pnl }) ]) ]), worstTrade && worstTrade !== bestTrade && React.createElement('div', { key: 'worst', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '14px 16px' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '10px', color: 'var(--muted)', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.07em' } }, 'πŸ“‰ Worst Trade'), React.createElement('div', { key: 'row', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, [ React.createElement(SymbolCell, { key: 'sym', symbol: worstTrade.symbol }), React.createElement(PnLValue, { key: 'v', value: worstTrade.pnl }) ]) ]), // Week trades list weekTrades.length > 0 && React.createElement('div', { key: 'tradeslist', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '14px 16px' } }, [ React.createElement('div', { key: 'title', style: { fontSize: '10px', color: 'var(--muted)', marginBottom: '10px', textTransform: 'uppercase', letterSpacing: '0.07em' } }, `Trades This Week (${weekTrades.length})`), React.createElement('div', { key: 'list', style: { display: 'flex', flexDirection: 'column', gap: '6px' } }, weekTrades.map(t => React.createElement('div', { key: t.id, style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '4px 0' } }, [ React.createElement('div', { key: 'l', style: { display: 'flex', alignItems: 'center', gap: '6px' } }, [ React.createElement(SymbolCell, { key: 'sym', symbol: t.symbol }), React.createElement(DirectionBadge, { key: 'dir', direction: t.direction }), ]), React.createElement(PnLValue, { key: 'v', value: t.pnl }) ])) ) ]), ]), // Right: Review form React.createElement('div', { key: 'review', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '24px', display: 'flex', flexDirection: 'column', gap: '18px' } }, [ // Rating React.createElement('div', { key: 'rating' }, [ React.createElement('label', { key: 'l', style: labelS }, 'Week Rating'), React.createElement('div', { key: 'stars', style: { display: 'flex', gap: '8px' } }, RATINGS.map(r => React.createElement('button', { key: r.val, onClick: () => setField('rating', r.val), style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '3px', padding: '8px 12px', borderRadius: '8px', border: `1px solid ${form.rating === r.val ? '#5588ff' : 'var(--border)'}`, background: form.rating === r.val ? 'rgba(85,136,255,0.12)' : 'var(--surface3)', cursor: 'pointer' } }, [ React.createElement('span', { key: 'e', style: { fontSize: '22px' } }, r.label), React.createElement('span', { key: 'd', style: { fontSize: '10px', color: form.rating === r.val ? '#5588ff' : 'var(--muted)' } }, r.desc) ])) ) ]), // What worked React.createElement('div', { key: 'worked' }, [ React.createElement('label', { key: 'l', style: labelS }, 'βœ… What Worked'), React.createElement('textarea', { value: form.what_worked, onChange: e => setField('what_worked', e.target.value), placeholder: 'Which setups performed well? What did you execute correctly?', style: textAreaStyle, onFocus: e => e.target.style.borderColor = '#5588ff', onBlur: e => e.target.style.borderColor = 'var(--border)' }) ]), // What didn't React.createElement('div', { key: 'didnt' }, [ React.createElement('label', { key: 'l', style: labelS }, '❌ What Didn\'t Work'), React.createElement('textarea', { value: form.what_didnt, onChange: e => setField('what_didnt', e.target.value), placeholder: 'Mistakes, bad entries, poor risk management…', style: textAreaStyle, onFocus: e => e.target.style.borderColor = '#5588ff', onBlur: e => e.target.style.borderColor = 'var(--border)' }) ]), // Lessons React.createElement('div', { key: 'lessons' }, [ React.createElement('label', { key: 'l', style: labelS }, 'πŸ“š Lessons Learned'), React.createElement('textarea', { value: form.lessons, onChange: e => setField('lessons', e.target.value), placeholder: 'Key takeaways to remember next week…', style: textAreaStyle, onFocus: e => e.target.style.borderColor = '#5588ff', onBlur: e => e.target.style.borderColor = 'var(--border)' }) ]), // Goals next week React.createElement('div', { key: 'goals' }, [ React.createElement('label', { key: 'l', style: labelS }, '🎯 Goals for Next Week'), React.createElement('textarea', { value: form.goals, onChange: e => setField('goals', e.target.value), placeholder: 'What will you focus on or improve?', style: { ...textAreaStyle, minHeight: '70px' }, onFocus: e => e.target.style.borderColor = '#5588ff', onBlur: e => e.target.style.borderColor = 'var(--border)' }) ]), // Save React.createElement('div', { key: 'save-row', style: { display: 'flex', alignItems: 'center', gap: '12px' } }, [ React.createElement('button', { onClick: saveReview, style: { padding: '10px 24px', background: '#5588ff', border: 'none', borderRadius: '8px', color: '#fff', fontSize: '14px', fontWeight: 600, cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', transition: 'all 0.15s' } }, 'Save Review'), saved && React.createElement('span', { key: 'saved', style: { fontSize: '13px', color: '#00d27a' } }, 'βœ“ Saved') ]) ]) ]) ]); }; Object.assign(window, { WeeklyReviewPage });