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 (
{/* 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 && (
<>
>
)}
);
})}
);
})}
{/* Legend */}
Panduan Warna (Mood Indicator)
{Object.entries(MOODS).map(([key, data]) => (
))}
{/* Mood Selector Modal */}
{modal.isOpen && weekData && (
setModal({ isOpen: false, day: null, slot: null })}
onSave={handleMoodSave}
/>
)}
);
};
export default App;