fix
This commit is contained in:
parent
6fdd418c8b
commit
92be013d7c
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user