import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, doc, setDoc, onSnapshot } from 'firebase/firestore'; // --- Firebase and Date Utility Functions --- // Global variables provided by the environment const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null; // Helper to get the start of the week (Monday) for any given date const getWeekStart = (date) => { const day = date.getDay(); const diff = date.getDate() - day + (day === 0 ? -6 : 1); // Adjust when day is Sunday const weekStart = new Date(date.setDate(diff)); weekStart.setHours(0, 0, 0, 0); return weekStart; }; // Helper to format date as YYYY-MM-DD string for document ID const formatDate = (date) => { return date.toISOString().split('T')[0]; }; // Helper to add days to a date const addDays = (date, days) => { const newDate = new Date(date); newDate.setDate(date.getDate() + days); return newDate; }; // --- Constants --- const DAY_NAMES = ['Isnin', 'Selasa', 'Rabu', 'Khamis', 'Jumaat', 'Sabtu', 'Ahad']; const TIME_SLOTS = ['Pagi', 'Siang', 'Malam']; const MOODS = { 'Happy': { label: 'Gembira', color: 'bg-green-500', icon: '😄', darkColor: 'bg-green-600' }, 'Not Bad': { label: 'Biasa Saja', color: 'bg-yellow-400', icon: '🙂', darkColor: 'bg-yellow-500' }, 'Stressed': { label: 'Tertekan', color: 'bg-orange-500', icon: '😟', darkColor: 'bg-orange-600' }, 'Sad': { label: 'Sedih', color: 'bg-blue-500', icon: '😢', darkColor: 'bg-blue-600' }, 'Angry': { label: 'Marah', color: 'bg-red-500', icon: '😡', darkColor: 'bg-red-600' }, }; const getDefaultWeekData = (weekStartDateString) => { const records = {}; DAY_NAMES.forEach(day => { records[day] = {}; TIME_SLOTS.forEach(slot => { records[day][slot] = { mood: null, notes: '' }; }); }); return { weekStart: weekStartDateString, records }; }; // --- Components --- // Mood Selection Modal const MoodSelector = ({ day, slot, currentRecord, onClose, onSave }) => { const [selectedMood, setSelectedMood] = useState(currentRecord?.mood || null); const [notes, setNotes] = useState(currentRecord?.notes || ''); const handleSave = () => { onSave(day, slot, selectedMood, notes); onClose(); }; return (

Pilih Mood Anda: {day}, {slot}

{Object.entries(MOODS).map(([moodKey, moodData]) => ( ))}
); }; // Main App Component const App = () => { const [db, setDb] = useState(null); const [auth, setAuth] = useState(null); const [userId, setUserId] = useState(null); const [currentWeekStart, setCurrentWeekStart] = useState(getWeekStart(new Date())); const [weekData, setWeekData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(''); const [modal, setModal] = useState({ isOpen: false, day: null, slot: null }); // 1. Firebase Initialization and Authentication useEffect(() => { try { const app = initializeApp(firebaseConfig); const firestore = getFirestore(app); const authInstance = getAuth(app); setDb(firestore); setAuth(authInstance); const unsubscribe = onAuthStateChanged(authInstance, async (user) => { if (!user) { // Sign in anonymously if no user is signed in await signInAnonymously(authInstance); } // Use custom token if available, otherwise rely on anonymous sign-in if (initialAuthToken) { await signInWithCustomToken(authInstance, initialAuthToken); } setUserId(authInstance.currentUser?.uid || 'anonymous'); setIsLoading(false); }); return () => unsubscribe(); } catch (e) { console.error("Firebase initialization failed:", e); setError("Gagal memuatkan Firebase. Sila semak konfigurasi."); setIsLoading(false); } }, []); const weekStartDateString = useMemo(() => formatDate(currentWeekStart), [currentWeekStart]); // 2. Firestore Data Listener useEffect(() => { if (!db || !userId) return; const docRef = doc(db, `artifacts/${appId}/users/${userId}/weekly_mood_tracker`, weekStartDateString ); // Listen for real-time updates const unsubscribe = onSnapshot(docRef, (docSnap) => { if (docSnap.exists()) { setWeekData(docSnap.data()); } else { // Initialize default data if document doesn't exist setWeekData(getDefaultWeekData(weekStartDateString)); } }, (error) => { console.error("Error fetching Firestore data:", error); setError("Gagal memuatkan data mood mingguan."); }); return () => unsubscribe(); // Clean up listener }, [db, userId, weekStartDateString]); // 3. Save function const saveWeekData = useCallback(async (newRecords) => { if (!db || !userId) return; const docRef = doc(db, `artifacts/${appId}/users/${userId}/weekly_mood_tracker`, weekStartDateString ); try { const dataToSave = { ...weekData, records: newRecords }; // Using setDoc with merge: true to ensure the document exists and is updated await setDoc(docRef, dataToSave, { merge: true }); // console.log("Data saved successfully!"); } catch (e) { console.error("Error saving document:", e); setError("Gagal menyimpan rekod mood."); } }, [db, userId, weekStartDateString, weekData]); // 4. Mood update logic from Modal const handleMoodSave = useCallback((day, slot, mood, notes) => { const newRecords = { ...weekData.records, [day]: { ...weekData.records[day], [slot]: { mood, notes } } }; setWeekData({ ...weekData, records: newRecords }); // Optimistic UI update saveWeekData(newRecords); }, [weekData, saveWeekData]); // 5. Navigation logic const navigateWeek = (direction) => { const newDate = addDays(currentWeekStart, direction * 7); setCurrentWeekStart(newDate); }; if (isLoading) { return (
Memuatkan Penjejak Mood...
); } if (error) { return (

Ralat!

{error}

ID Pengguna: {userId}

); } const weekEndDate = addDays(currentWeekStart, 6); const formattedWeekRange = `${currentWeekStart.toLocaleDateString('ms-MY', { day: 'numeric', month: 'short' })} - ${weekEndDate.toLocaleDateString('ms-MY', { day: 'numeric', month: 'short', year: 'numeric' })}`; const today = formatDate(new Date()); return (

Jurnal Mood Mingguan

Jejaki perasaan anda sepanjang hari.

ID Pengguna: {userId}

{/* Week Navigation */}

Minggu:

{formattedWeekRange}

{/* Mood Grid */}
{/* Header Row */}
Hari
{TIME_SLOTS.map(slot => (
{slot}
))}
{/* Data Rows */} {weekData && DAY_NAMES.map((day, dayIndex) => { const dayDate = formatDate(addDays(currentWeekStart, dayIndex)); const isToday = dayDate === today && weekStartDateString === formatDate(getWeekStart(new Date())); return (
{/* Day Name */}
{day} {isToday && (Hari Ini)}
{/* Time Slots */} {TIME_SLOTS.map(slot => { const record = weekData.records?.[day]?.[slot] || { mood: null, notes: '' }; const moodKey = record.mood; const moodData = MOODS[moodKey]; return (
setModal({ isOpen: true, day, slot })} title={`Klik untuk merekod mood ${day}, ${slot}`} >
{moodData ? (
{moodData.icon} {moodData.label}
) : ( Rekod Mood )}
{/* Notes Indicator and Tooltip */} {record.notes && ( <>

Catatan:

{record.notes}

)}
); })}
); })}
{/* Legend */}

Panduan Warna (Mood Indicator)

{Object.entries(MOODS).map(([key, data]) => (
{data.label}
))}
{/* Mood Selector Modal */} {modal.isOpen && weekData && ( setModal({ isOpen: false, day: null, slot: null })} onSave={handleMoodSave} /> )}
); }; export default App;