// QReward Customer — Home, My Points, Points History const { useState: uS2, useEffect: uE2, useMemo: uM2 } = React; /* ============ HOME DASHBOARD ============ */ const HomeScreen = ({ lang, cardStatus, onNav, animateKey, refreshKey }) => { const [loading, setLoading] = uS2(true); uE2(() => { setLoading(true); const t = setTimeout(() => setLoading(false), 700); return () => clearTimeout(t); }, [refreshKey]); const hour = 9; const greeting = hour < 12 ? (lang === 'jp' ? 'おはようございます' : 'Good morning') : (lang === 'jp' ? 'こんにちは' : 'Hello'); const showApplyCard = cardStatus === 'notapplied' || cardStatus === 'declined'; const quickActions = [ { id: 'market', icon: 'redeem', label: L(lang, 'marketplace'), color: '', go: () => onNav('tab', 'redeem') }, showApplyCard && { id: 'apply', icon: 'card', label: L(lang, 'applyForCard'), color: 'amber', go: () => onNav('push', 'mycard') }, { id: 'link', icon: 'link', label: L(lang, 'linking'), color: 'purple', go: () => onNav('push', 'linked') }, { id: 'card', icon: 'wallet', label: L(lang, 'myCard'), color: 'green', go: () => onNav('tab', 'card') }, ].filter(Boolean); return (
{/* sticky top bar */}
{greeting},
{lang === 'jp' ? `${DATA.user.nameJp}さん` : DATA.user.name}
{/* 3D card hero */}
onNav('tab', 'points')} />
{/* Quick actions */}
{quickActions.map(a => ( ))}
{/* LifeCard status */}
{lang === 'jp' ? 'LifeCard 申請状況' : 'LifeCard application'}
{/* Featured offers */}
{L(lang, 'featuredOffers')}
{DATA.offers.map(o => ( ))}
{/* Recent activity */}
{L(lang, 'recentActivity')}
{DATA.activity.slice(0, 5).map(a => )}
); }; const ActivityRow = ({ a, lang, showBal = true }) => { const tagMap = { lifecard: { cls: 'lifecard', label: 'LifeCard' }, qualia: { cls: 'qualia', label: 'QUALIA' }, admin: { cls: 'admin', label: lang === 'jp' ? '調整' : 'Admin' }, redeem: { cls: 'redeem', label: lang === 'jp' ? '交換' : 'Redeem' }, }; const tag = tagMap[a.src] || tagMap.redeem; const pos = a.amt > 0; return (
{a.desc}
{tag.label} {a.date}
{pos ? '+' : '−'}{fmt(Math.abs(a.amt))} r
{showBal &&
{lang === 'jp' ? '残高' : 'Bal'} {fmt(a.bal)}
}
); }; /* ============ MY POINTS ============ */ const MyPointsScreen = ({ lang, onNav }) => { const total = DATA.balance; const lcPct = Math.round(DATA.lifecard / total * 100); const qPct = 100 - lcPct; const [anim, setAnim] = uS2(0); uE2(() => { const t = setTimeout(() => setAnim(1), 100); return () => clearTimeout(t); }, []); // pie geometry const r = 42, c = 2 * Math.PI * r; const lcLen = c * (DATA.lifecard / total) * anim; const qLen = c * (DATA.qualia / total) * anim; return (
{L(lang, 'totalBalance')}
{fmt(total)}r
{/* Pie + legend */}
LifeCard · {lcPct}%
{fmt(DATA.lifecard)} r
QUALIA · {qPct}%
{fmt(DATA.qualia)} r
{/* Sources */}
{lang === 'jp' ? 'ポイント元' : 'Sources'}
{DATA.sources.map(s => ( ))}
); }; /* ============ POINTS HISTORY ============ */ const PointsHistoryScreen = ({ lang, onBack, initFilter }) => { const [filter, setFilter] = uS2(initFilter || 'All'); const [query, setQuery] = uS2(''); const filters = ['All', 'LifeCard', 'QUALIA', lang === 'jp' ? '交換' : 'Marketplace', lang === 'jp' ? '調整' : 'Admin adjusted']; const all = [ ...DATA.activity, { id: 'h7', src: 'qualia', type: 'earn', desc: 'QUALIA monthly batch — April', amt: 3210, bal: 25440, date: '2026/04/15 14:18' }, { id: 'h8', src: 'redeem', type: 'redeem', desc: 'MUJI Home Set ×1', amt: -4500, bal: 22230, date: '2026/04/10 11:02' }, { id: 'h9', src: 'lifecard', type: 'earn', desc: 'LifeCard CSV import — April', amt: 980, bal: 26730, date: '2026/04/05 10:18' }, ]; const filtered = all.filter(a => { if (filter === 'LifeCard' && a.src !== 'lifecard') return false; if (filter === 'QUALIA' && a.src !== 'qualia') return false; if ((filter === 'Marketplace' || filter === '交換') && a.src !== 'redeem') return false; if ((filter === 'Admin adjusted' || filter === '調整') && a.src !== 'admin') return false; if (query && !a.desc.toLowerCase().includes(query.toLowerCase())) return false; return true; }); return (
setQuery(e.target.value)} placeholder={lang === 'jp' ? '説明・注文ID・バッチIDで検索' : 'Search description, order ID, batch ID'} />
{filters.map(f => )}
{filtered.length === 0 ?
{lang === 'jp' ? '該当なし' : 'No items match'}
{lang === 'jp' ? 'フィルターをクリアしてください' : 'Try clearing the filter'}
:
{filtered.map(a => )}
} {filtered.length > 0 &&
· {lang === 'jp' ? '履歴の最後' : 'End of history'} ·
}
); }; Object.assign(window, { HomeScreen, MyPointsScreen, PointsHistoryScreen, ActivityRow });