This commit is contained in:
202310715066 NABILA SUWANDIRA 2026-01-14 12:40:48 +07:00
parent 6fdd418c8b
commit 92be013d7c
4 changed files with 294 additions and 102 deletions

View File

@ -11,7 +11,7 @@
<script src="https://unpkg.com/recharts/shadcn-recharts.js"></script> <script src="https://unpkg.com/recharts/shadcn-recharts.js"></script>
<script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/lucide@latest"></script>
<style> <style>
body { margin: 0; padding: 0; overflow-x: hidden; } body { margin: 0; padding: 0; overflow-x: hidden; background: transparent !important; }
.pb-safe { padding-bottom: env(safe-area-inset-bottom, 20px); } .pb-safe { padding-bottom: env(safe-area-inset-bottom, 20px); }
.animate-fade-in { animation: fadeIn 0.3s ease-out; } .animate-fade-in { animation: fadeIn 0.3s ease-out; }
.scrollbar-hide::-webkit-scrollbar { display: none; } .scrollbar-hide::-webkit-scrollbar { display: none; }
@ -28,8 +28,8 @@
} }
</style> </style>
</head> </head>
<body> <body class="bg-transparent">
<div id="root"></div> <div id="root" class="bg-transparent"></div>
<script type="text/babel"> <script type="text/babel">
// Shim for Lucide Icons in browser // Shim for Lucide Icons in browser
@ -77,7 +77,7 @@
// Mocking Recharts for Browser (Simple version if library not fully loaded) // Mocking Recharts for Browser (Simple version if library not fully loaded)
const { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } = window.Recharts || { const { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } = window.Recharts || {
ResponsiveContainer: ({children}) => <div style={{width:'100%', height:'100%'}}>{children}</div>, ResponsiveContainer: ({children}) => <div style={{width:'100%', height:'100%'}}>{children}</div>,
AreaChart: () => <div className="bg-slate-100 w-full h-full flex items-center justify-center text-xs text-slate-400">Chart Placeholder</div>, AreaChart: () => <div className="bg-white/20 w-full h-full flex items-center justify-center text-xs text-white/40">Chart Placeholder</div>,
Area: () => null, XAxis: () => null, YAxis: () => null, CartesianGrid: () => null, Tooltip: () => null Area: () => null, XAxis: () => null, YAxis: () => null, CartesianGrid: () => null, Tooltip: () => null
}; };
@ -89,7 +89,7 @@
moderate: '#DD6B20',// Orange moderate: '#DD6B20',// Orange
severe: '#E53E3E', // Merah severe: '#E53E3E', // Merah
primary: '#4F6DAD', // Default Biru primary: '#4F6DAD', // Default Biru
bg: '#F5F5F7' bg: 'transparent'
}; };
// --- STATE MANAGEMENT --- // --- STATE MANAGEMENT ---
@ -282,10 +282,10 @@
}; };
const getSeverityInfo = (score) => { const getSeverityInfo = (score) => {
if (score >= 10) return { color: COLORS.severe, bg: 'bg-red-50', text: 'text-red-700', fill: 'bg-red-500', label: 'Berat' }; if (score >= 10) return { color: COLORS.severe, bg: 'bg-red-50/80', text: 'text-red-700', fill: 'bg-red-500', label: 'Berat' };
if (score >= 7) return { color: COLORS.moderate, bg: 'bg-orange-50', text: 'text-orange-700', fill: 'bg-orange-500', label: 'Sedang' }; if (score >= 7) return { color: COLORS.moderate, bg: 'bg-orange-50/80', text: 'text-orange-700', fill: 'bg-orange-500', label: 'Sedang' };
if (score >= 4) return { color: COLORS.mild, bg: 'bg-yellow-50', text: 'text-yellow-700', fill: 'bg-yellow-500', label: 'Ringan' }; if (score >= 4) return { color: COLORS.mild, bg: 'bg-yellow-50/80', text: 'text-yellow-700', fill: 'bg-yellow-500', label: 'Ringan' };
return { color: COLORS.normal, bg: 'bg-green-50', text: 'text-green-700', fill: 'bg-green-500', label: 'Normal' }; return { color: COLORS.normal, bg: 'bg-green-50/80', text: 'text-green-700', fill: 'bg-green-500', label: 'Normal' };
}; };
const getChartData = () => { const getChartData = () => {
@ -377,29 +377,29 @@
const renderJournal = () => ( const renderJournal = () => (
<div className="flex flex-col h-[calc(100vh-170px)] animate-fade-in"> <div className="flex flex-col h-[calc(100vh-170px)] animate-fade-in">
<div className="bg-white p-1 rounded-xl flex shadow-sm border border-slate-100 mb-4 shrink-0"> <div className="bg-white/70 backdrop-blur-sm p-1 rounded-xl flex shadow-sm border border-white/20 mb-4 shrink-0">
<button onClick={() => setJournalMode('free')} className={`flex-1 py-3 text-sm font-bold rounded-lg transition-all ${journalMode === 'free' ? 'bg-[#4F6DAD] text-white shadow-md' : 'text-slate-400 hover:bg-slate-50'}`}>Jurnal Bebas</button> <button onClick={() => setJournalMode('free')} className={`flex-1 py-3 text-sm font-bold rounded-lg transition-all ${journalMode === 'free' ? 'bg-[#4F6DAD] text-white shadow-md' : 'text-slate-400 hover:bg-slate-50/50'}`}>Jurnal Bebas</button>
<button onClick={() => setJournalMode('reflection')} className={`flex-1 py-3 text-sm font-bold rounded-lg transition-all ${journalMode === 'reflection' ? 'bg-[#4F6DAD] text-white shadow-md' : 'text-slate-400 hover:bg-slate-50'}`}>Refleksi Harian</button> <button onClick={() => setJournalMode('reflection')} className={`flex-1 py-3 text-sm font-bold rounded-lg transition-all ${journalMode === 'reflection' ? 'bg-[#4F6DAD] text-white shadow-md' : 'text-slate-400 hover:bg-slate-50/50'}`}>Refleksi Harian</button>
</div> </div>
<div className="bg-white p-6 rounded-3xl shadow-sm border border-slate-100 relative flex-1 flex flex-col"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-white/20 relative flex-1 flex flex-col">
{journalMode === 'free' ? ( {journalMode === 'free' ? (
<> <>
<textarea <textarea
value={journalText} value={journalText}
onChange={(e) => setJournalText(e.target.value)} onChange={(e) => setJournalText(e.target.value)}
placeholder="Tuliskan perasaanmu..." placeholder="Tuliskan perasaanmu..."
className="w-full flex-1 bg-[#F5F5F7] p-4 rounded-2xl border border-slate-200 outline-none resize-none text-slate-700 focus:ring-2 focus:ring-[#4F6DAD] focus:border-transparent transition-all mb-4" className="w-full flex-1 bg-white/50 p-4 rounded-2xl border border-white/30 outline-none resize-none text-slate-700 focus:ring-2 focus:ring-[#4F6DAD] transition-all mb-4"
></textarea> ></textarea>
<div className="shrink-0"> <div className="shrink-0">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<input type="text" value={tagInput} onChange={(e) => setTagInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAddTag()} placeholder="+ Tag..." className="text-xs p-2 rounded-lg bg-[#F5F5F7] border border-slate-200 outline-none w-36" /> <input type="text" value={tagInput} onChange={(e) => setTagInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAddTag()} placeholder="+ Tag..." className="text-xs p-2 rounded-lg bg-white/50 border border-white/30 outline-none w-36" />
<button onClick={handleAddTag} disabled={!tagInput.trim()} className="p-2 bg-slate-100 rounded-lg text-slate-500 hover:bg-[#4F6DAD] hover:text-white disabled:opacity-50 transition-colors"><Plus className="w-3 h-3" /></button> <button onClick={handleAddTag} disabled={!tagInput.trim()} className="p-2 bg-white/50 rounded-lg text-slate-500 hover:bg-[#4F6DAD] hover:text-white disabled:opacity-50 transition-colors"><Plus className="w-3 h-3" /></button>
</div> </div>
<div className="flex gap-2 mb-4 overflow-x-auto pb-2 scrollbar-hide"> <div className="flex gap-2 mb-4 overflow-x-auto pb-2 scrollbar-hide">
{availableTags.map(tag => ( {availableTags.map(tag => (
<button key={tag} onClick={() => setJournalTags(prev => prev.includes(tag) ? prev.filter(t=>t!==tag) : [...prev, tag])} className={`px-3 py-1 rounded-full text-xs font-bold transition-colors whitespace-nowrap ${journalTags.includes(tag) ? 'bg-[#4F6DAD] text-white' : 'bg-slate-100 text-slate-400'}`}>{tag}</button> <button key={tag} onClick={() => setJournalTags(prev => prev.includes(tag) ? prev.filter(t=>t!==tag) : [...prev, tag])} className={`px-3 py-1 rounded-full text-xs font-bold transition-colors whitespace-nowrap ${journalTags.includes(tag) ? 'bg-[#4F6DAD] text-white' : 'bg-white/50 text-slate-400'}`}>{tag}</button>
))} ))}
</div> </div>
</div> </div>
@ -411,27 +411,27 @@
q: 'Satu hal kecil yang membuatmu tersenyum hari ini?', q: 'Satu hal kecil yang membuatmu tersenyum hari ini?',
key: 'q1', key: 'q1',
icon: Smile, icon: Smile,
color: 'text-yellow-600 bg-yellow-100', color: 'text-yellow-600 bg-yellow-100/80',
ph: 'Misal: Kopi pagi yang enak, sapaan teman...' ph: 'Misal: Kopi pagi yang enak, sapaan teman...'
}, },
{ {
q: 'Tantangan terbesar hari ini & solusinya?', q: 'Tantangan terbesar hari ini & solusinya?',
key: 'q2', key: 'q2',
icon: Activity, icon: Activity,
color: 'text-red-600 bg-red-100', color: 'text-red-600 bg-red-100/80',
ph: 'Misal: Macet total, solusinya dengar podcast...' ph: 'Misal: Macet total, solusinya dengar podcast...'
}, },
{ {
q: 'Satu hal yang ingin kamu perbaiki besok?', q: 'Satu hal yang ingin kamu perbaiki besok?',
key: 'q3', key: 'q3',
icon: TrendingUp, icon: TrendingUp,
color: 'text-blue-600 bg-blue-100', color: 'text-blue-600 bg-blue-100/80',
ph: 'Misal: Tidur lebih awal, kurangi sosmed...' ph: 'Misal: Tidur lebih awal, kurangi sosmed...'
} }
].map((item, idx) => ( ].map((item, idx) => (
<div key={idx} className="group relative"> <div key={idx} className="group relative">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<div className={`p-1.5 rounded-lg ${item.color}`}> <div className={`p-1.5 rounded-lg ${item.color} backdrop-blur-sm`}>
<item.icon className="w-4 h-4"/> <item.icon className="w-4 h-4"/>
</div> </div>
<label className="text-xs font-bold text-slate-700 uppercase tracking-wide">{item.q}</label> <label className="text-xs font-bold text-slate-700 uppercase tracking-wide">{item.q}</label>
@ -439,7 +439,7 @@
<textarea <textarea
value={reflectionAnswers[item.key]} value={reflectionAnswers[item.key]}
onChange={(e) => setReflectionAnswers({...reflectionAnswers, [item.key]: e.target.value})} onChange={(e) => setReflectionAnswers({...reflectionAnswers, [item.key]: e.target.value})}
className="w-full p-4 bg-[#F5F5F7] rounded-2xl border border-slate-200 outline-none focus:ring-2 focus:ring-[#4F6DAD] focus:bg-white focus:border-transparent transition-all resize-none text-slate-700 text-sm placeholder:text-slate-400" className="w-full p-4 bg-white/50 rounded-2xl border border-white/30 outline-none focus:ring-2 focus:ring-[#4F6DAD] focus:bg-white/80 transition-all resize-none text-slate-700 text-sm placeholder:text-slate-400"
placeholder={item.ph} placeholder={item.ph}
rows={3} rows={3}
/> />
@ -448,7 +448,7 @@
</div> </div>
)} )}
<div className="shrink-0 mt-3 flex flex-col gap-3 pt-3 border-t border-slate-50"> <div className="shrink-0 mt-3 flex flex-col gap-3 pt-3 border-t border-white/20">
{autoSaveTime && <div className="flex items-center justify-end gap-1.5 text-[10px] text-slate-400 font-medium animate-fade-in"><CheckCircle className="w-3 h-3 text-green-500" /> Disimpan otomatis {autoSaveTime}</div>} {autoSaveTime && <div className="flex items-center justify-end gap-1.5 text-[10px] text-slate-400 font-medium animate-fade-in"><CheckCircle className="w-3 h-3 text-green-500" /> Disimpan otomatis {autoSaveTime}</div>}
<button onClick={handleSaveJournal} className="w-full bg-[#4F6DAD] text-white py-4 rounded-2xl font-bold text-lg hover:shadow-lg hover:bg-[#3E5C9A] active:scale-[0.98] transition-all flex items-center justify-center gap-2 shadow-md"><Save className="w-5 h-5"/> Simpan Jurnal</button> <button onClick={handleSaveJournal} className="w-full bg-[#4F6DAD] text-white py-4 rounded-2xl font-bold text-lg hover:shadow-lg hover:bg-[#3E5C9A] active:scale-[0.98] transition-all flex items-center justify-center gap-2 shadow-md"><Save className="w-5 h-5"/> Simpan Jurnal</button>
</div> </div>
@ -456,12 +456,12 @@
{showAssessmentAlert && ( {showAssessmentAlert && (
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm flex items-center justify-center z-50 p-6 animate-fade-in"> <div className="fixed inset-0 bg-black/20 backdrop-blur-sm flex items-center justify-center z-50 p-6 animate-fade-in">
<div className="bg-white p-6 rounded-3xl shadow-2xl max-w-sm w-full text-center"> <div className="bg-white/90 backdrop-blur-md p-6 rounded-3xl shadow-2xl max-w-sm w-full text-center">
<div className="w-16 h-16 bg-yellow-100 rounded-full flex items-center justify-center mx-auto mb-4"><Activity className="w-8 h-8 text-yellow-600"/></div> <div className="w-16 h-16 bg-yellow-100 rounded-full flex items-center justify-center mx-auto mb-4"><Activity className="w-8 h-8 text-yellow-600"/></div>
<h3 className="text-xl font-bold text-slate-800 mb-2">Lupa Sesuatu?</h3> <h3 className="text-xl font-bold text-slate-800 mb-2">Lupa Sesuatu?</h3>
<p className="text-slate-500 mb-6">Anda belum menilai mood hari ini.</p> <p className="text-slate-500 mb-6">Anda belum menilai mood hari ini.</p>
<div className="flex gap-3"> <div className="flex gap-3">
<button onClick={() => setShowAssessmentAlert(false)} className="flex-1 py-3 text-slate-400 font-bold hover:bg-slate-50 rounded-xl">Nanti Saja</button> <button onClick={() => setShowAssessmentAlert(false)} className="flex-1 py-3 text-slate-400 font-bold hover:bg-white/50 rounded-xl">Nanti Saja</button>
<button onClick={() => {setShowAssessmentAlert(false); setActiveTab('assessment')}} className="flex-1 py-3 bg-[#4F6DAD] text-white font-bold rounded-xl shadow-lg">Ya, Lanjut</button> <button onClick={() => {setShowAssessmentAlert(false); setActiveTab('assessment')}} className="flex-1 py-3 bg-[#4F6DAD] text-white font-bold rounded-xl shadow-lg">Ya, Lanjut</button>
</div> </div>
</div> </div>
@ -477,16 +477,16 @@
return ( return (
<div className="space-y-6 animate-fade-in pb-20"> <div className="space-y-6 animate-fade-in pb-20">
<div className="bg-white p-6 rounded-3xl shadow-sm border border-slate-100 sticky top-20 z-10"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-white/20 sticky top-20 z-10">
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<h2 className="text-lg font-bold text-slate-700">Skor Mental</h2> <h2 className="text-lg font-bold text-slate-700">Skor Mental</h2>
<span className={`px-3 py-1 rounded-lg text-xs font-bold ${risk.bg} ${risk.text}`}>{total} - {risk.label}</span> <span className={`px-3 py-1 rounded-lg text-xs font-bold ${risk.bg} ${risk.text}`}>{total} - {risk.label}</span>
</div> </div>
<div className="w-full bg-slate-100 h-2 rounded-full mt-3 overflow-hidden"> <div className="w-full bg-white/20 h-2 rounded-full mt-3 overflow-hidden">
<div className={`h-full transition-all duration-500 ${total >= 10 ? 'bg-red-500' : total >= 7 ? 'bg-orange-500' : total >= 4 ? 'bg-yellow-500' : 'bg-[#38A169]'}`} style={{width: `${(total/12)*100}%`}}></div> <div className={`h-full transition-all duration-500 ${total >= 10 ? 'bg-red-500' : total >= 7 ? 'bg-orange-500' : total >= 4 ? 'bg-yellow-500' : 'bg-[#38A169]'}`} style={{width: `${(total/12)*100}%`}}></div>
</div> </div>
</div> </div>
<div className="bg-white p-6 rounded-3xl shadow-sm border border-slate-100 space-y-8"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-white/20 space-y-8">
{indicators.map((item) => { {indicators.map((item) => {
const val = assessmentInputs[item.id]; const val = assessmentInputs[item.id];
const getSeverity = (v) => { const getSeverity = (v) => {
@ -506,13 +506,13 @@
</div> </div>
<div className="relative h-10 flex items-center"> <div className="relative h-10 flex items-center">
<div className="absolute w-full h-3 bg-slate-200 rounded-full"></div> <div className="absolute w-full h-3 bg-white/30 rounded-full"></div>
<div <div
className="absolute h-3 rounded-full transition-all duration-300" className="absolute h-3 rounded-full transition-all duration-300"
style={{ style={{
width: `${percent}%`, width: `${percent}%`,
background: `linear-gradient(to right, ${sev.hex}, ${sev.hex})` backgroundColor: sev.hex
}} }}
></div> ></div>
@ -554,15 +554,15 @@
<div className="space-y-6 animate-fade-in pb-20"> <div className="space-y-6 animate-fade-in pb-20">
{!activeGame && ( {!activeGame && (
<div className="grid grid-cols-1 gap-4"> <div className="grid grid-cols-1 gap-4">
<button onClick={() => { setActiveGame('simon'); startSimonGame(); }} className="bg-white p-5 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-all text-left flex gap-4 items-center"> <button onClick={() => { setActiveGame('simon'); startSimonGame(); }} className="bg-white/80 backdrop-blur-md p-5 rounded-2xl border border-white/20 shadow-sm hover:shadow-md transition-all text-left flex gap-4 items-center">
<div className="p-3 bg-purple-100 text-purple-600 rounded-xl"><BrainCircuit className="w-6 h-6"/></div> <div className="p-3 bg-purple-100 text-purple-600 rounded-xl"><BrainCircuit className="w-6 h-6"/></div>
<div><h3 className="font-bold text-slate-800">Tes Memori</h3><p className="text-xs text-slate-400">Ingat urutan pola grid 3x3</p></div> <div><h3 className="font-bold text-slate-800">Tes Memori</h3><p className="text-xs text-slate-400">Ingat urutan pola grid 3x3</p></div>
</button> </button>
<button onClick={() => { setActiveGame('reaction'); setReactionState('intro'); }} className="bg-white p-5 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-all text-left flex gap-4 items-center"> <button onClick={() => { setActiveGame('reaction'); setReactionState('intro'); }} className="bg-white/80 backdrop-blur-md p-5 rounded-2xl border border-white/20 shadow-sm hover:shadow-md transition-all text-left flex gap-4 items-center">
<div className="p-3 bg-yellow-100 text-yellow-600 rounded-xl"><Zap className="w-6 h-6"/></div> <div className="p-3 bg-yellow-100 text-yellow-600 rounded-xl"><Zap className="w-6 h-6"/></div>
<div><h3 className="font-bold text-slate-800">Kecepatan Reaksi</h3><p className="text-xs text-slate-400">Tes refleks visual</p></div> <div><h3 className="font-bold text-slate-800">Kecepatan Reaksi</h3><p className="text-xs text-slate-400">Tes refleks visual</p></div>
</button> </button>
<button onClick={() => { setActiveGame('logic'); setLogicState('setup'); }} className="bg-white p-5 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-all text-left flex gap-4 items-center"> <button onClick={() => { setActiveGame('logic'); setLogicState('setup'); }} className="bg-white/80 backdrop-blur-md p-5 rounded-2xl border border-white/20 shadow-sm hover:shadow-md transition-all text-left flex gap-4 items-center">
<div className="p-3 bg-blue-100 text-blue-600 rounded-xl"><Search className="w-6 h-6"/></div> <div className="p-3 bg-blue-100 text-blue-600 rounded-xl"><Search className="w-6 h-6"/></div>
<div><h3 className="font-bold text-slate-800">Tes Logika</h3><p className="text-xs text-slate-400">Asah pola pikir</p></div> <div><h3 className="font-bold text-slate-800">Tes Logika</h3><p className="text-xs text-slate-400">Asah pola pikir</p></div>
</button> </button>
@ -571,10 +571,10 @@
{/* GAME: REACTION */} {/* GAME: REACTION */}
{activeGame === 'reaction' && ( {activeGame === 'reaction' && (
<div className="bg-white p-6 rounded-3xl shadow-lg text-center h-[400px] flex flex-col justify-center relative"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-lg text-center h-[400px] flex flex-col justify-center relative border border-white/20">
<button onClick={() => setActiveGame(null)} className="absolute top-4 right-4 p-2 bg-slate-100 rounded-full"><X className="w-4 h-4"/></button> <button onClick={() => setActiveGame(null)} className="absolute top-4 right-4 p-2 bg-white/50 rounded-full"><X className="w-4 h-4"/></button>
<h3 className="font-bold text-xl mb-4 text-[#4F6DAD]">Tes Reaksi</h3> <h3 className="font-bold text-xl mb-4 text-[#4F6DAD]">Tes Reaksi</h3>
<div onClick={handleReactionClick} className={`flex-1 rounded-2xl flex flex-col items-center justify-center cursor-pointer transition-all select-none ${reactionState === 'intro' ? 'bg-slate-100' : reactionState === 'waiting' ? 'bg-red-500' : reactionState === 'ready' ? 'bg-green-500' : reactionState === 'too-soon' ? 'bg-orange-500' : 'bg-[#4F6DAD]'}`}> <div onClick={handleReactionClick} className={`flex-1 rounded-2xl flex flex-col items-center justify-center cursor-pointer transition-all select-none ${reactionState === 'intro' ? 'bg-white/30' : reactionState === 'waiting' ? 'bg-red-500' : reactionState === 'ready' ? 'bg-green-500' : reactionState === 'too-soon' ? 'bg-orange-500' : 'bg-[#4F6DAD]'}`}>
{reactionState === 'intro' && <button onClick={(e) => { e.stopPropagation(); startReactionGame(); }} className="bg-[#4F6DAD] text-white px-8 py-3 rounded-full font-bold shadow-lg">Mulai Tes</button>} {reactionState === 'intro' && <button onClick={(e) => { e.stopPropagation(); startReactionGame(); }} className="bg-[#4F6DAD] text-white px-8 py-3 rounded-full font-bold shadow-lg">Mulai Tes</button>}
{reactionState === 'waiting' && <p className="text-white font-bold text-2xl animate-pulse">Tunggu Hijau...</p>} {reactionState === 'waiting' && <p className="text-white font-bold text-2xl animate-pulse">Tunggu Hijau...</p>}
{reactionState === 'ready' && <p className="text-white font-bold text-3xl">TEKAN!</p>} {reactionState === 'ready' && <p className="text-white font-bold text-3xl">TEKAN!</p>}
@ -587,8 +587,8 @@
{/* GAME: MEMORY TEST (SIMON 3x3) */} {/* GAME: MEMORY TEST (SIMON 3x3) */}
{activeGame === 'simon' && ( {activeGame === 'simon' && (
<div className="bg-white p-6 rounded-3xl shadow-lg text-center relative"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-lg text-center relative border border-white/20">
<button onClick={() => setActiveGame(null)} className="absolute top-4 right-4 p-2 bg-slate-100 rounded-full"><X className="w-4 h-4"/></button> <button onClick={() => setActiveGame(null)} className="absolute top-4 right-4 p-2 bg-white/50 rounded-full"><X className="w-4 h-4"/></button>
<div className="mb-6"> <div className="mb-6">
<h3 className="font-bold text-xl text-[#4F6DAD]">Tes Memori</h3> <h3 className="font-bold text-xl text-[#4F6DAD]">Tes Memori</h3>
<p className="text-sm text-slate-500">Skor: {simonScore}</p> <p className="text-sm text-slate-500">Skor: {simonScore}</p>
@ -608,8 +608,8 @@
onClick={() => handleSimonClick(i)} onClick={() => handleSimonClick(i)}
className={`h-20 rounded-xl transition-all duration-150 shadow-sm border-b-4 active:border-b-0 active:translate-y-1 ${ className={`h-20 rounded-xl transition-all duration-150 shadow-sm border-b-4 active:border-b-0 active:translate-y-1 ${
simonFlash === i simonFlash === i
? 'bg-[#4F6DAD] border-[#3b5488] brightness-110 scale-105' // Active Blue ? 'bg-[#4F6DAD] border-[#3b5488] brightness-110 scale-105'
: 'bg-slate-200 border-slate-300 hover:bg-slate-300' // Default Gray : 'bg-white/30 border-white/40 hover:bg-white/50'
}`} }`}
></button> ></button>
))} ))}
@ -622,8 +622,8 @@
{/* GAME: LOGIC */} {/* GAME: LOGIC */}
{activeGame === 'logic' && ( {activeGame === 'logic' && (
<div className="bg-white p-6 rounded-3xl shadow-lg relative min-h-[400px] flex flex-col"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-lg relative min-h-[400px] flex flex-col border border-white/20">
<button onClick={() => setActiveGame(null)} className="absolute top-4 right-4 p-2 bg-slate-100 rounded-full"><X className="w-4 h-4"/></button> <button onClick={() => setActiveGame(null)} className="absolute top-4 right-4 p-2 bg-white/50 rounded-full"><X className="w-4 h-4"/></button>
{logicState === 'setup' && ( {logicState === 'setup' && (
<div className="flex flex-col justify-center flex-1"> <div className="flex flex-col justify-center flex-1">
@ -631,7 +631,7 @@
<div className="mb-6"> <div className="mb-6">
<label className="text-xs font-bold text-slate-400 uppercase mb-2 block">Tingkat Kesulitan</label> <label className="text-xs font-bold text-slate-400 uppercase mb-2 block">Tingkat Kesulitan</label>
<div className="flex bg-slate-100 p-1 rounded-xl"> <div className="flex bg-white/30 p-1 rounded-xl">
{['mudah', 'sedang', 'susah'].map(lvl => ( {['mudah', 'sedang', 'susah'].map(lvl => (
<button key={lvl} onClick={() => setLogicSetup({...logicSetup, difficulty: lvl})} className={`flex-1 py-2 rounded-lg text-xs font-bold capitalize transition-all ${logicSetup.difficulty === lvl ? 'bg-white shadow text-[#4F6DAD]' : 'text-slate-400'}`}>{lvl}</button> <button key={lvl} onClick={() => setLogicSetup({...logicSetup, difficulty: lvl})} className={`flex-1 py-2 rounded-lg text-xs font-bold capitalize transition-all ${logicSetup.difficulty === lvl ? 'bg-white shadow text-[#4F6DAD]' : 'text-slate-400'}`}>{lvl}</button>
))} ))}
@ -642,7 +642,7 @@
<label className="text-xs font-bold text-slate-400 uppercase mb-2 block">Jumlah Soal</label> <label className="text-xs font-bold text-slate-400 uppercase mb-2 block">Jumlah Soal</label>
<div className="flex gap-4"> <div className="flex gap-4">
{[5, 10].map(count => ( {[5, 10].map(count => (
<button key={count} onClick={() => setLogicSetup({...logicSetup, count})} className={`flex-1 py-3 border-2 rounded-xl font-bold transition-all ${logicSetup.count === count ? 'border-[#4F6DAD] text-[#4F6DAD] bg-indigo-50' : 'border-slate-200 text-slate-400'}`}>{count} Soal</button> <button key={count} onClick={() => setLogicSetup({...logicSetup, count})} className={`flex-1 py-3 border-2 rounded-xl font-bold transition-all ${logicSetup.count === count ? 'border-[#4F6DAD] text-[#4F6DAD] bg-white/50' : 'border-white/30 text-slate-400'}`}>{count} Soal</button>
))} ))}
</div> </div>
</div> </div>
@ -655,14 +655,14 @@
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<span className="text-xs font-bold text-slate-400">Soal {logicCurrentIdx + 1}/{logicQuestions.length}</span> <span className="text-xs font-bold text-slate-400">Soal {logicCurrentIdx + 1}/{logicQuestions.length}</span>
<span className="text-xs font-bold bg-indigo-50 text-[#4F6DAD] px-2 py-1 rounded capitalize">{logicSetup.difficulty}</span> <span className="text-xs font-bold bg-white/50 text-[#4F6DAD] px-2 py-1 rounded capitalize">{logicSetup.difficulty}</span>
</div> </div>
<h4 className="font-bold text-lg text-slate-800 mb-8 leading-relaxed">{logicQuestions[logicCurrentIdx]?.q}</h4> <h4 className="font-bold text-lg text-slate-800 mb-8 leading-relaxed">{logicQuestions[logicCurrentIdx]?.q}</h4>
<div className="space-y-3 flex-1"> <div className="space-y-3 flex-1">
{logicQuestions[logicCurrentIdx]?.options.map((opt, idx) => ( {logicQuestions[logicCurrentIdx]?.options.map((opt, idx) => (
<button key={idx} onClick={() => handleLogicAnswer(idx)} className="w-full text-left p-4 rounded-xl border border-slate-200 hover:bg-indigo-50 hover:border-[#4F6DAD] transition-all font-medium text-slate-600 active:scale-[0.98]"> <button key={idx} onClick={() => handleLogicAnswer(idx)} className="w-full text-left p-4 rounded-xl border border-white/30 bg-white/20 hover:bg-white/50 hover:border-[#4F6DAD] transition-all font-medium text-slate-600 active:scale-[0.98]">
{opt} {opt}
</button> </button>
))} ))}
@ -672,7 +672,7 @@
{logicState === 'finished' && ( {logicState === 'finished' && (
<div className="text-center flex flex-col justify-center flex-1"> <div className="text-center flex flex-col justify-center flex-1">
<div className="w-20 h-20 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-4 text-[#4F6DAD] text-4xl font-bold">{Math.round((logicScore/logicQuestions.length)*100)}%</div> <div className="w-20 h-20 bg-indigo-100/80 rounded-full flex items-center justify-center mx-auto mb-4 text-[#4F6DAD] text-4xl font-bold">{Math.round((logicScore/logicQuestions.length)*100)}%</div>
<h3 className="font-bold text-xl text-slate-800 mb-2">Tes Selesai!</h3> <h3 className="font-bold text-xl text-slate-800 mb-2">Tes Selesai!</h3>
<p className="text-slate-500 mb-8">Anda menjawab {logicScore} benar dari {logicQuestions.length} soal.</p> <p className="text-slate-500 mb-8">Anda menjawab {logicScore} benar dari {logicQuestions.length} soal.</p>
<button onClick={() => setLogicState('setup')} className="bg-[#4F6DAD] text-white px-8 py-3 rounded-xl font-bold">Ulangi Tes</button> <button onClick={() => setLogicState('setup')} className="bg-[#4F6DAD] text-white px-8 py-3 rounded-xl font-bold">Ulangi Tes</button>
@ -692,26 +692,26 @@
return ( return (
<div className="space-y-6 animate-fade-in pb-20"> <div className="space-y-6 animate-fade-in pb-20">
<div className="bg-white p-6 rounded-3xl shadow-sm border border-slate-100 flex items-center justify-between"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-white/20 flex items-center justify-between">
<div className="flex items-center gap-4"><div className="w-14 h-14 bg-[#4F6DAD] rounded-full flex items-center justify-center text-white text-xl font-bold">U</div><div><h2 className="font-bold text-lg text-slate-800">{userProfile.name}</h2><p className="text-xs text-slate-400">Bergabung Jan 2024</p></div></div> <div className="flex items-center gap-4"><div className="w-14 h-14 bg-[#4F6DAD] rounded-full flex items-center justify-center text-white text-xl font-bold">U</div><div><h2 className="font-bold text-lg text-slate-800">{userProfile.name}</h2><p className="text-xs text-slate-400">Bergabung Jan 2024</p></div></div>
<div className="text-center"><div className="flex items-center gap-1 text-orange-500 font-bold text-xl">{userProfile.streak} <span className="text-2xl">🔥</span></div><p className="text-[10px] text-slate-400 uppercase font-bold tracking-wider">Streak</p></div> <div className="text-center"><div className="flex items-center gap-1 text-orange-500 font-bold text-xl">{userProfile.streak} <span className="text-2xl">🔥</span></div><p className="text-[10px] text-slate-400 uppercase font-bold tracking-wider">Streak</p></div>
</div> </div>
<button onClick={() => setShowHelpModal(true)} className="w-full bg-red-50 border border-red-100 text-red-600 py-3 rounded-xl font-bold flex items-center justify-center gap-2 hover:bg-red-100 transition-colors"> <button onClick={() => setShowHelpModal(true)} className="w-full bg-red-50/80 backdrop-blur-sm border border-red-100 text-red-600 py-3 rounded-xl font-bold flex items-center justify-center gap-2 hover:bg-red-100/80 transition-colors">
<Phone className="w-4 h-4" /> BUTUH BANTUAN DARURAT? <Phone className="w-4 h-4" /> BUTUH BANTUAN DARURAT?
</button> </button>
{/* 1. LENCANA PENCAPAIAN */} {/* 1. LENCANA PENCAPAIAN */}
<div> <div>
<h3 className="font-bold text-slate-700 mb-3 flex items-center gap-2"><Award className="w-5 h-5 text-[#4F6DAD]"/> Lencana</h3> <h3 className="font-bold text-slate-700 mb-3 flex items-center gap-2"><Award className="w-5 h-5 text-[#4F6DAD]"/> Lencana</h3>
<div className="bg-white p-4 rounded-3xl shadow-sm border border-slate-100"><div className="grid grid-cols-3 gap-4 max-h-64 overflow-y-auto pr-1">{BADGES_DB.map(badge => (<div key={badge.id} onClick={() => setModalBadge(badge)} className={`aspect-square rounded-2xl flex flex-col items-center justify-center text-center p-2 cursor-pointer transition-all ${userProfile.badges.includes(badge.id) ? 'bg-[#F5F5F7] hover:bg-slate-200' : 'opacity-40 grayscale bg-slate-50'}`}><span className="text-3xl mb-1">{badge.icon}</span><span className="text-[10px] font-bold text-slate-600 leading-tight">{badge.name}</span></div>))}</div></div> <div className="bg-white/80 backdrop-blur-md p-4 rounded-3xl shadow-sm border border-white/20"><div className="grid grid-cols-3 gap-4 max-h-64 overflow-y-auto pr-1">{BADGES_DB.map(badge => (<div key={badge.id} onClick={() => setModalBadge(badge)} className={`aspect-square rounded-2xl flex flex-col items-center justify-center text-center p-2 cursor-pointer transition-all ${userProfile.badges.includes(badge.id) ? 'bg-white/50 hover:bg-white/80' : 'opacity-40 grayscale bg-white/20'}`}><span className="text-3xl mb-1">{badge.icon}</span><span className="text-[10px] font-bold text-slate-600 leading-tight">{badge.name}</span></div>))}</div></div>
</div> </div>
{/* 2. LAPORAN KESEHATAN */} {/* 2. LAPORAN KESEHATAN */}
<div className="bg-white p-6 rounded-3xl shadow-sm border border-slate-100"> <div className="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-white/20">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<h3 className="font-bold text-slate-700">Laporan Kesehatan</h3> <h3 className="font-bold text-slate-700">Laporan Kesehatan</h3>
<select value={reportRange} onChange={(e) => setReportRange(e.target.value)} className="bg-[#F5F5F7] text-xs font-bold text-slate-600 py-2 px-3 rounded-lg border-none outline-none"> <select value={reportRange} onChange={(e) => setReportRange(e.target.value)} className="bg-white/50 text-xs font-bold text-slate-600 py-2 px-3 rounded-lg border-none outline-none">
<option value="mingguan">Mingguan</option> <option value="mingguan">Mingguan</option>
<option value="bulanan">Bulanan</option> <option value="bulanan">Bulanan</option>
<option value="tahunan">Tahunan</option> <option value="tahunan">Tahunan</option>
@ -727,9 +727,9 @@
<stop offset="95%" stopColor={severityInfo.color} stopOpacity={0}/> <stop offset="95%" stopColor={severityInfo.color} stopOpacity={0}/>
</linearGradient> </linearGradient>
</defs> </defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f0f0f0"/> <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="rgba(255,255,255,0.1)"/>
<XAxis dataKey="name" tick={{fontSize:10}} axisLine={false} tickLine={false}/> <XAxis dataKey="name" tick={{fontSize:10, fill:'#64748b'}} axisLine={false} tickLine={false}/>
<Tooltip contentStyle={{borderRadius:'10px', border:'none'}}/> <Tooltip contentStyle={{borderRadius:'10px', border:'none', backgroundColor:'rgba(255,255,255,0.9)'}}/>
<Area type="monotone" dataKey="score" stroke={severityInfo.color} strokeWidth={3} fill="url(#colorScore)"/> <Area type="monotone" dataKey="score" stroke={severityInfo.color} strokeWidth={3} fill="url(#colorScore)"/>
</AreaChart> </AreaChart>
</ResponsiveContainer> </ResponsiveContainer>
@ -741,46 +741,44 @@
<div> <div>
<h4 className={`text-xs font-bold ${severityInfo.text} uppercase mb-1`}>AI Insight: {severityInfo.label}</h4> <h4 className={`text-xs font-bold ${severityInfo.text} uppercase mb-1`}>AI Insight: {severityInfo.label}</h4>
<p className={`text-xs ${severityInfo.text} leading-relaxed opacity-90`}> <p className={`text-xs ${severityInfo.text} leading-relaxed opacity-90`}>
{avgScore >= 10 ? "Tingkat stres sangat tinggi. Segera hubungi bantuan profesional atau orang terdekat." : {avgScore >= 10 ? "Tingkat stres sangat tinggi. Segera hubungi bantuan profesional." :
avgScore >= 7 ? "Terdeteksi beban emosi sedang. Luangkan waktu untuk hobi atau meditasi." : avgScore >= 7 ? "Terdeteksi beban emosi sedang. Luangkan waktu untuk hobi." :
avgScore >= 4 ? "Ada sedikit gejolak. Tetap jaga pola tidur dan aktivitas fisik." : avgScore >= 4 ? "Ada sedikit gejolak. Tetap jaga pola tidur." :
"Kondisi mental Anda stabil dan sehat. Teruskan kebiasaan baik ini!"} "Kondisi mental Anda stabil. Teruskan kebiasaan baik ini!"}
</p> </p>
</div> </div>
</div> </div>
) : ( ) : (
<div className="bg-slate-50 p-4 rounded-xl flex gap-3 items-center text-slate-400"> <div className="bg-white/30 p-4 rounded-xl flex gap-3 items-center text-slate-400">
<BrainCircuit className="w-5 h-5"/> <BrainCircuit className="w-5 h-5"/>
<p className="text-xs">Isi penilaian untuk melihat analisis AI.</p> <p className="text-xs">Isi penilaian untuk melihat analisis AI.</p>
</div> </div>
)} )}
</div> </div>
{/* 3. RIWAYAT JURNAL TERBARU (DIBAWAH) */} {/* 3. RIWAYAT JURNAL TERBARU */}
<div> <div>
<h3 className="font-bold text-slate-700 mb-3 flex items-center gap-2"><BookOpen className="w-5 h-5 text-[#4F6DAD]"/> Riwayat Jurnal Terbaru</h3> <h3 className="font-bold text-slate-700 mb-3 flex items-center gap-2"><BookOpen className="w-5 h-5 text-[#4F6DAD]"/> Riwayat Jurnal Terbaru</h3>
{journals.length > 0 ? ( {journals.length > 0 ? (
<div className="space-y-3"> <div className="space-y-3">
{journals.slice(0, 3).map(j => ( {journals.slice(0, 3).map(j => (
<div key={j.id} className="bg-white p-4 rounded-2xl shadow-sm border border-slate-100"> <div key={j.id} className="bg-white/50 backdrop-blur-md p-4 rounded-2xl shadow-sm border border-white/20 text-sm">
<div className="flex justify-between mb-1"> <span className="text-[#4F6DAD] font-bold">{j.date}</span>
<span className="text-xs font-bold text-[#4F6DAD]">{j.date}</span> <p className="line-clamp-1 text-slate-600">{j.content.text}</p>
</div>
<p className="text-sm text-slate-600 line-clamp-1">{j.content.text}</p>
</div> </div>
))} ))}
<button onClick={() => setViewMode('history_full')} className="w-full py-3 text-[#4F6DAD] font-bold text-xs bg-indigo-50 rounded-xl hover:bg-indigo-100">Lihat Semua</button> <button onClick={() => setViewMode('history_full')} className="w-full py-3 text-[#4F6DAD] font-bold text-xs bg-white/50 rounded-xl hover:bg-white/80 border border-white/30">Lihat Semua</button>
</div> </div>
) : ( ) : (
<div className="bg-white p-6 rounded-2xl border border-dashed border-slate-200 text-center text-slate-400 text-sm"> <div className="bg-white/30 p-6 rounded-2xl border border-dashed border-white/40 text-center text-slate-400 text-sm">
Belum ada jurnal yang disimpan. Belum ada jurnal yang disimpan.
</div> </div>
)} )}
</div> </div>
{/* 4. HAPUS AKUN (PALING BAWAH) */} {/* 4. HAPUS AKUN */}
<div className="pt-4"> <div className="pt-4">
<button onClick={handleDeleteAccount} className="w-full py-4 text-red-500 font-bold text-sm bg-red-50 rounded-2xl border border-red-100 hover:bg-red-100 transition-colors flex items-center justify-center gap-2"> <button onClick={handleDeleteAccount} className="w-full py-4 text-red-500 font-bold text-sm bg-red-50/50 backdrop-blur-sm rounded-2xl border border-red-100 hover:bg-red-100/50 transition-colors flex items-center justify-center gap-2">
<Trash2 className="w-4 h-4"/> Hapus Akun Permanen <Trash2 className="w-4 h-4"/> Hapus Akun Permanen
</button> </button>
</div> </div>
@ -798,21 +796,21 @@
return ( return (
<div className="animate-slide-up pb-20"> <div className="animate-slide-up pb-20">
<div className="sticky top-0 bg-[#F5F5F7] pt-4 pb-2 z-10 flex items-center gap-3 mb-4"><button onClick={() => setViewMode('main')} className="p-2 bg-white rounded-full shadow-sm"><X className="w-5 h-5"/></button><h2 className="text-xl font-bold text-slate-800">Riwayat Jurnal</h2></div> <div className="sticky top-0 bg-transparent pt-4 pb-2 z-10 flex items-center gap-3 mb-4"><button onClick={() => setViewMode('main')} className="p-2 bg-white/80 backdrop-blur-md rounded-full shadow-sm"><X className="w-5 h-5"/></button><h2 className="text-xl font-bold text-slate-800">Riwayat Jurnal</h2></div>
<div className="relative mb-6"><Search className="absolute left-4 top-3.5 w-5 h-5 text-slate-400" /><input type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Cari jurnal atau tag..." className="w-full pl-12 pr-4 py-3 rounded-xl border border-slate-200 focus:border-[#4F6DAD] outline-none shadow-sm"/></div> <div className="relative mb-6"><Search className="absolute left-4 top-3.5 w-5 h-5 text-slate-400" /><input type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Cari jurnal atau tag..." className="w-full pl-12 pr-4 py-3 rounded-xl border border-white/30 bg-white/50 backdrop-blur-md focus:border-[#4F6DAD] outline-none shadow-sm"/></div>
<div className="space-y-4"> <div className="space-y-4">
{filteredJournals.length > 0 ? filteredJournals.map(j => ( {filteredJournals.length > 0 ? filteredJournals.map(j => (
<div key={j.id} className="bg-white p-5 rounded-2xl shadow-sm border border-slate-100"> <div key={j.id} className="bg-white/80 backdrop-blur-md p-5 rounded-2xl shadow-sm border border-white/20">
<div className="flex justify-between items-start mb-2"><span className="text-xs font-bold text-[#4F6DAD] bg-indigo-50 px-2 py-1 rounded-md">{j.date}</span></div> <div className="flex justify-between items-start mb-2"><span className="text-xs font-bold text-[#4F6DAD] bg-white/50 px-2 py-1 rounded-md border border-white/30">{j.date}</span></div>
<div className="space-y-4"> <div className="space-y-4">
<p className="text-slate-700 text-sm leading-relaxed border-b border-slate-100 pb-3">{j.content.text}</p> <p className="text-slate-700 text-sm leading-relaxed border-b border-white/20 pb-3">{j.content.text}</p>
<div className="bg-slate-50 p-4 rounded-xl space-y-3"> <div className="bg-white/30 p-4 rounded-xl space-y-3 border border-white/20">
<div className="flex items-center gap-2 mb-1"><div className="w-1.5 h-1.5 rounded-full bg-[#4F6DAD]"></div><span className="text-xs font-bold text-slate-500 uppercase">Hal Kecil</span></div><p className="text-sm text-slate-600 pl-3.5 italic">"{j.content.reflection.q1}"</p> <div className="flex items-center gap-2 mb-1"><div className="w-1.5 h-1.5 rounded-full bg-[#4F6DAD]"></div><span className="text-xs font-bold text-slate-500 uppercase">Hal Kecil</span></div><p className="text-sm text-slate-600 pl-3.5 italic">"{j.content.reflection.q1}"</p>
<div className="flex items-center gap-2 mb-1 mt-3"><div className="w-1.5 h-1.5 rounded-full bg-red-400"></div><span className="text-xs font-bold text-slate-500 uppercase">Tantangan</span></div><p className="text-sm text-slate-600 pl-3.5 italic">"{j.content.reflection.q2}"</p> <div className="flex items-center gap-2 mb-1 mt-3"><div className="w-1.5 h-1.5 rounded-full bg-red-400"></div><span className="text-xs font-bold text-slate-500 uppercase">Tantangan</span></div><p className="text-sm text-slate-600 pl-3.5 italic">"{j.content.reflection.q2}"</p>
<div className="flex items-center gap-2 mb-1 mt-3"><div className="w-1.5 h-1.5 rounded-full bg-blue-400"></div><span className="text-xs font-bold text-slate-500 uppercase">Perbaikan</span></div><p className="text-sm text-slate-600 pl-3.5 italic">"{j.content.reflection.q3}"</p> <div className="flex items-center gap-2 mb-1 mt-3"><div className="w-1.5 h-1.5 rounded-full bg-blue-400"></div><span className="text-xs font-bold text-slate-500 uppercase">Perbaikan</span></div><p className="text-sm text-slate-600 pl-3.5 italic">"{j.content.reflection.q3}"</p>
</div> </div>
</div> </div>
{j.tags && <div className="flex gap-2 mt-3">{j.tags.map(t => <span key={t} onClick={() => handleTagClick(t)} className="text-[10px] text-slate-500 bg-slate-100 px-2 py-1 rounded-full cursor-pointer hover:bg-[#4F6DAD] hover:text-white transition-colors">{t}</span>)}</div>} {j.tags && <div className="flex gap-2 mt-3">{j.tags.map(t => <span key={t} onClick={() => handleTagClick(t)} className="text-[10px] text-slate-500 bg-white/50 px-2 py-1 rounded-full border border-white/20 cursor-pointer hover:bg-[#4F6DAD] hover:text-white transition-colors">{t}</span>)}</div>}
</div> </div>
)) : <div className="text-center py-10 text-slate-400"><p>Tidak ditemukan.</p></div>} )) : <div className="text-center py-10 text-slate-400"><p>Tidak ditemukan.</p></div>}
</div> </div>
@ -820,13 +818,13 @@
); );
}; };
if (viewMode === 'history_full') return <div className="min-h-screen bg-[#F5F5F7] font-sans text-[#2D3748] max-w-md mx-auto p-6 relative">{renderHistoryFull()}</div>; if (viewMode === 'history_full') return <div className="min-h-screen bg-transparent font-sans text-[#2D3748] max-w-md mx-auto p-6 relative">{renderHistoryFull()}</div>;
return ( return (
<div className="min-h-screen bg-[#F5F5F7] font-sans text-[#2D3748] max-w-md mx-auto relative shadow-2xl overflow-hidden"> <div className="min-h-screen bg-transparent font-sans text-[#2D3748] max-w-md mx-auto relative overflow-hidden">
<header className="bg-white px-6 pt-8 pb-4 shadow-sm flex justify-between items-center sticky top-0 z-20"> <header className="bg-white/80 backdrop-blur-md px-6 pt-8 pb-4 shadow-sm flex justify-between items-center sticky top-0 z-20 border-b border-white/20">
<div className="flex items-center gap-2"><div className="bg-[#4F6DAD] p-2 rounded-lg text-white"><Activity className="w-5 h-5" /></div><h1 className="text-lg font-extrabold text-[#4F6DAD]">PsyJournal</h1></div> <div className="flex items-center gap-2"><div className="bg-[#4F6DAD] p-2 rounded-lg text-white"><Activity className="w-5 h-5" /></div><h1 className="text-lg font-extrabold text-[#4F6DAD]">PsyJournal</h1></div>
<div className="w-8 h-8 rounded-full bg-slate-100 border border-slate-200 overflow-hidden"><User className="w-full h-full p-1 text-slate-400"/></div> <div className="w-8 h-8 rounded-full bg-white/50 border border-white/30 overflow-hidden"><User className="w-full h-full p-1 text-slate-400"/></div>
</header> </header>
<main className="p-4"> <main className="p-4">
{activeTab === 'journal' && renderJournal()} {activeTab === 'journal' && renderJournal()}
@ -834,18 +832,18 @@
{activeTab === 'cognitive' && renderCognitive()} {activeTab === 'cognitive' && renderCognitive()}
{activeTab === 'profile' && renderProfile()} {activeTab === 'profile' && renderProfile()}
</main> </main>
<nav className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white border-t border-slate-200 px-6 py-3 flex justify-between items-center z-30 pb-safe"> <nav className="fixed bottom-0 left-0 right-0 max-w-md mx-auto bg-white/80 backdrop-blur-md border-t border-white/20 px-6 py-3 flex justify-between items-center z-30 pb-safe">
{[{ id: 'journal', icon: BookOpen, label: 'Jurnal' }, { id: 'assessment', icon: Activity, label: 'Penilaian' }, { id: 'cognitive', icon: BrainCircuit, label: 'Tes' }, { id: 'profile', icon: User, label: 'Profil' }].map((item) => ( {[{ id: 'journal', icon: BookOpen, label: 'Jurnal' }, { id: 'assessment', icon: Activity, label: 'Penilaian' }, { id: 'cognitive', icon: BrainCircuit, label: 'Tes' }, { id: 'profile', icon: User, label: 'Profil' }].map((item) => (
<button key={item.id} onClick={() => setActiveTab(item.id)} className={`flex flex-col items-center gap-1 transition-all ${activeTab === item.id ? 'text-[#4F6DAD] -translate-y-1' : 'text-slate-300'}`}><item.icon className={`w-6 h-6 ${activeTab === item.id ? 'fill-current' : ''}`} /><span className="text-[10px] font-bold">{item.label}</span></button> <button key={item.id} onClick={() => setActiveTab(item.id)} className={`flex flex-col items-center gap-1 transition-all ${activeTab === item.id ? 'text-[#4F6DAD] -translate-y-1' : 'text-slate-400'}`}><item.icon className={`w-6 h-6 ${activeTab === item.id ? 'fill-current' : ''}`} /><span className="text-[10px] font-bold">{item.label}</span></button>
))} ))}
</nav> </nav>
{modalBadge && ( {modalBadge && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm p-6 animate-fade-in"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm p-6 animate-fade-in">
<div className="bg-white rounded-3xl p-8 w-full max-w-sm text-center relative shadow-2xl flex flex-col items-center"> <div className="bg-white/90 backdrop-blur-md rounded-3xl p-8 w-full max-w-sm text-center relative shadow-2xl flex flex-col items-center border border-white/30">
<button onClick={() => setModalBadge(null)} className="absolute top-4 right-4 text-slate-400"><X/></button> <button onClick={() => setModalBadge(null)} className="absolute top-4 right-4 text-slate-400"><X/></button>
<div className="text-7xl mb-4 animate-bounce mt-4">{modalBadge.icon}</div> <div className="text-7xl mb-4 animate-bounce mt-4">{modalBadge.icon}</div>
<h3 className="text-2xl font-bold text-[#4F6DAD] mb-1">{modalBadge.name}</h3> <h3 className="text-2xl font-bold text-[#4F6DAD] mb-1">{modalBadge.name}</h3>
<div className="bg-slate-50 px-4 py-1.5 rounded-full mb-6 border border-slate-100 shadow-sm mt-2"><span className="text-xs text-slate-500 font-bold flex items-center gap-1"><Clock className="w-3 h-3"/> Dicapai: {modalBadge.date}</span></div> <div className="bg-white/50 px-4 py-1.5 rounded-full mb-6 border border-white/30 shadow-sm mt-2"><span className="text-xs text-slate-500 font-bold flex items-center gap-1"><Clock className="w-3 h-3"/> Dicapai: {modalBadge.date}</span></div>
<p className="text-slate-600 text-sm mb-8 px-4 text-center leading-relaxed">{modalBadge.desc}</p> <p className="text-slate-600 text-sm mb-8 px-4 text-center leading-relaxed">{modalBadge.desc}</p>
<button onClick={() => setModalBadge(null)} className="w-full bg-[#4F6DAD] text-white py-3 rounded-xl font-bold flex items-center justify-center gap-2"><ArrowRight className="w-4 h-4"/> Tutup</button> <button onClick={() => setModalBadge(null)} className="w-full bg-[#4F6DAD] text-white py-3 rounded-xl font-bold flex items-center justify-center gap-2"><ArrowRight className="w-4 h-4"/> Tutup</button>
</div> </div>
@ -854,21 +852,21 @@
{showHelpModal && ( {showHelpModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-6 animate-fade-in"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-6 animate-fade-in">
<div className="bg-white rounded-3xl p-6 w-full max-w-sm shadow-2xl relative"> <div className="bg-white/90 backdrop-blur-md rounded-3xl p-6 w-full max-w-sm shadow-2xl relative border border-white/30">
<button onClick={() => setShowHelpModal(false)} className="absolute top-4 right-4 text-slate-400 hover:text-slate-600"><X className="w-5 h-5"/></button> <button onClick={() => setShowHelpModal(false)} className="absolute top-4 right-4 text-slate-400 hover:text-slate-600"><X className="w-5 h-5"/></button>
<div className="flex items-center gap-2 mb-6 text-red-600"> <div className="flex items-center gap-2 mb-6 text-red-600">
<AlertTriangle className="w-6 h-6"/> <AlertTriangle className="w-6 h-6"/>
<h3 className="text-xl font-bold">Bantuan Darurat</h3> <h3 className="text-xl font-bold">Bantuan Darurat</h3>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<a href="tel:119" className="flex items-center gap-4 p-4 bg-red-50 text-red-700 rounded-2xl border border-red-100 hover:bg-red-100 transition-colors"> <a href="tel:119" className="flex items-center gap-4 p-4 bg-red-50/80 text-red-700 rounded-2xl border border-red-100 hover:bg-red-100/80 transition-colors">
<div className="bg-red-200 p-3 rounded-full"><Phone className="w-6 h-6"/></div> <div className="bg-red-200 p-3 rounded-full"><Phone className="w-6 h-6"/></div>
<div> <div>
<div className="font-bold text-lg">Panggil Ambulans</div> <div className="font-bold text-lg">Panggil Ambulans</div>
<div className="text-xs opacity-70">Nomor Darurat: 119</div> <div className="text-xs opacity-70">Nomor Darurat: 119</div>
</div> </div>
</a> </a>
<a href="https://www.intothelightid.org/tentang-bunuh-diri/layanan-konseling-psikolog-psikiater/" target="_blank" rel="noopener noreferrer" className="flex items-center gap-4 p-4 bg-blue-50 text-blue-700 rounded-2xl border border-blue-100 hover:bg-blue-100 transition-colors"> <a href="https://www.intothelightid.org/tentang-bunuh-diri/layanan-konseling-psikolog-psikiater/" target="_blank" rel="noopener noreferrer" className="flex items-center gap-4 p-4 bg-blue-50/80 text-blue-700 rounded-2xl border border-blue-100 hover:bg-blue-100/80 transition-colors">
<div className="bg-blue-200 p-3 rounded-full"><Globe className="w-6 h-6"/></div> <div className="bg-blue-200 p-3 rounded-full"><Globe className="w-6 h-6"/></div>
<div> <div>
<div className="font-bold text-lg">Layanan Profesional</div> <div className="font-bold text-lg">Layanan Profesional</div>
@ -876,9 +874,6 @@
</div> </div>
</a> </a>
</div> </div>
<p className="text-center text-xs text-slate-400 mt-6 px-4">
Jangan ragu untuk meminta bantuan. Anda tidak sendirian.
</p>
</div> </div>
</div> </div>
)} )}

View File

@ -0,0 +1,56 @@
package com.example.ppb_kelompok2
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
private const val DATABASE_NAME = "UserDB"
private const val DATABASE_VERSION = 1
private const val TABLE_USERS = "users"
private const val COLUMN_ID = "id"
private const val COLUMN_USERNAME = "username"
private const val COLUMN_PASSWORD = "password"
}
override fun onCreate(db: SQLiteDatabase?) {
val createTable = ("CREATE TABLE " + TABLE_USERS + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_USERNAME + " TEXT,"
+ COLUMN_PASSWORD + " TEXT" + ")")
db?.execSQL(createTable)
// Insert a default user for testing
val values = ContentValues().apply {
put(COLUMN_USERNAME, "admin")
put(COLUMN_PASSWORD, "admin123")
}
db?.insert(TABLE_USERS, null, values)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db?.execSQL("DROP TABLE IF EXISTS $TABLE_USERS")
onCreate(db)
}
fun checkUser(username: String, password: String): Boolean {
val db = this.readableDatabase
val query = "SELECT * FROM $TABLE_USERS WHERE $COLUMN_USERNAME = ? AND $COLUMN_PASSWORD = ?"
val cursor = db.rawQuery(query, arrayOf(username, password))
val exists = cursor.count > 0
cursor.close()
return exists
}
fun registerUser(username: String, password: String): Long {
val db = this.writableDatabase
val values = ContentValues().apply {
put(COLUMN_USERNAME, username)
put(COLUMN_PASSWORD, password)
}
return db.insert(TABLE_USERS, null, values)
}
}

View File

@ -6,26 +6,163 @@ import android.view.ViewGroup
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.background
import androidx.compose.material3.Surface import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import com.example.ppb_kelompok2.ui.theme.DarkBlue
import com.example.ppb_kelompok2.ui.theme.LightBlue
import com.example.ppb_kelompok2.ui.theme.PPB_Kelompok2Theme
import com.example.ppb_kelompok2.ui.theme.White
//jnfbshbs
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
val dbHelper = DatabaseHelper(this)
setContent { setContent {
Surface(modifier = Modifier.fillMaxSize()) { PPB_Kelompok2Theme {
WebViewContainer() var isLoggedIn by remember { mutableStateOf(false) }
// Latar belakang gradasi untuk seluruh aplikasi
Box(
modifier = Modifier
.fillMaxSize()
.background(
brush = Brush.verticalGradient(
colors = listOf(LightBlue, DarkBlue, White)
)
)
) {
if (isLoggedIn) {
WebViewContainer()
} else {
LoginRegisterScreen(dbHelper) {
isLoggedIn = true
}
}
}
} }
} }
} }
} // }
@Composable
fun LoginRegisterScreen(dbHelper: DatabaseHelper, onLoginSuccess: () -> Unit) {
var isLoginMode by remember { mutableStateOf(true) }
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val context = LocalContext.current
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// Judul Aplikasi
Text(
text = "PsyJournal",
style = MaterialTheme.typography.displayMedium,
color = White,
fontWeight = FontWeight.ExtraBold,
modifier = Modifier.padding(bottom = 32.dp)
)
Card(
modifier = Modifier
.padding(24.dp)
.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 12.dp),
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.9f))
) {
Column(
modifier = Modifier
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = if (isLoginMode) "Login" else "Register",
style = MaterialTheme.typography.headlineLarge,
color = DarkBlue,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(24.dp))
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("Username") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = {
if (username.isEmpty() || password.isEmpty()) {
Toast.makeText(context, "Harap isi semua bidang", Toast.LENGTH_SHORT).show()
return@Button
}
if (isLoginMode) {
if (dbHelper.checkUser(username, password)) {
Toast.makeText(context, "Login Berhasil!", Toast.LENGTH_SHORT).show()
onLoginSuccess()
} else {
Toast.makeText(context, "Username atau Password salah", Toast.LENGTH_SHORT).show()
}
} else {
val result = dbHelper.registerUser(username, password)
if (result != -1L) {
Toast.makeText(context, "Registrasi Berhasil! Silakan Login", Toast.LENGTH_SHORT).show()
isLoginMode = true
} else {
Toast.makeText(context, "Registrasi Gagal", Toast.LENGTH_SHORT).show()
}
}
},
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(containerColor = DarkBlue)
) {
Text(if (isLoginMode) "MASUK" else "DAFTAR", color = Color.White)
}
TextButton(onClick = { isLoginMode = !isLoginMode }) {
Text(
text = if (isLoginMode) "Belum punya akun? Daftar di sini" else "Sudah punya akun? Login di sini",
color = Color.Gray,
fontSize = 14.sp
)
}
}
}
}
}
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
@Composable @Composable
@ -38,7 +175,6 @@ fun WebViewContainer() {
ViewGroup.LayoutParams.MATCH_PARENT ViewGroup.LayoutParams.MATCH_PARENT
) )
// Konfigurasi WebView agar React berjalan lancar
settings.apply { settings.apply {
javaScriptEnabled = true javaScriptEnabled = true
domStorageEnabled = true domStorageEnabled = true
@ -51,8 +187,8 @@ fun WebViewContainer() {
} }
webViewClient = WebViewClient() webViewClient = WebViewClient()
// Membuat latar belakang WebView transparan agar gradasi di bawahnya terlihat
// Memuat file index.html dari folder assets setBackgroundColor(android.graphics.Color.TRANSPARENT)
loadUrl("file:///android_asset/index.html") loadUrl("file:///android_asset/index.html")
} }
}, },

View File

@ -9,3 +9,8 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4) val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71) val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260) val Pink40 = Color(0xFF7D5260)
// Gradient Colors
val LightBlue = Color(0xFFADD8E6)
val DarkBlue = Color(0xFF00008B)
val White = Color(0xFFFFFFFF)