import React, { useState, useEffect, createContext, useContext, useRef } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, doc, setDoc, onSnapshot } from 'firebase/firestore'; // --- Firebase Initialization and Context --- // Global variables provided by the Canvas 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; // Corrected: Use initialAuthToken directly // Create a context for Firebase and user data const FirebaseContext = createContext(null); // Firebase Provider component to initialize Firebase and manage auth const FirebaseProvider = ({ children }) => { const [db, setDb] = useState(null); const [auth, setAuth] = useState(null); const [userId, setUserId] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const initFirebase = async () => { try { const app = initializeApp(firebaseConfig); const firestore = getFirestore(app); const firebaseAuth = getAuth(app); setDb(firestore); setAuth(firebaseAuth); // Listen for authentication state changes const unsubscribe = onAuthStateChanged(firebaseAuth, async (user) => { if (user) { setUserId(user.uid); setLoading(false); } else { // Sign in anonymously if no token, or use the provided token try { if (initialAuthToken) { await signInWithCustomToken(firebaseAuth, initialAuthToken); } else { await signInAnonymously(firebaseAuth); } } catch (e) { console.error("Error signing in:", e); setError("Failed to authenticate. Please try again."); setLoading(false); } } }); return () => unsubscribe(); // Cleanup auth listener } catch (e) { console.error("Error initializing Firebase:", e); setError("Failed to initialize Firebase. Check console for details."); setLoading(false); } }; initFirebase(); }, [initialAuthToken, firebaseConfig]); // Added dependencies to useEffect if (loading) { return (
Loading dashboard...
); } if (error) { return (
{error}
); } return ( {children} ); }; // --- Competency Data (Hardcoded for demonstration) --- const competenciesData = [ { id: 'safety-sanitation', name: 'Safety and Sanitation', description: 'Mastering bacteriology, basic first aid, and state sanitation regulations.' }, { id: 'haircutting-shaving', name: 'Haircutting and Shaving', description: 'Proficiency in haircutting, styling, razor techniques, and beard trimming.' }, { id: 'chemical-services', name: 'Chemical Services', description: 'Skills in shampooing, conditioning, waving, relaxing, coloring, and lightening.' }, { id: 'additional-services', name: 'Additional Services', description: 'Expertise in skin care, scalp treatments, and hairpiece services.' }, { id: 'barbering-business', name: 'The Barbering Business', description: 'Understanding business ethics, merchandising, bookkeeping, and Michigan barber laws.' }, { id: 'client-management', name: 'Client Management', description: 'Knowledge of anatomy, client evaluation, and service recommendations.' }, { id: 'orientation', name: 'Orientation', description: 'Understanding the history and implements of the barber profession.' }, ]; // --- CompetencyItem Component --- const CompetencyItem = ({ competency, userProgress, onUpdateProgress }) => { const isCompleted = userProgress[competency.id]?.completed || false; const [notes, setNotes] = useState(userProgress[competency.id]?.notes || ''); const [submittedAssignments, setSubmittedAssignments] = useState(userProgress[competency.id]?.submittedAssignments || []); const fileInputRef = useRef(null); // Update local states when userProgress changes from parent useEffect(() => { setNotes(userProgress[competency.id]?.notes || ''); setSubmittedAssignments(userProgress[competency.id]?.submittedAssignments || []); }, [userProgress, competency.id]); const handleNotesChange = (e) => { setNotes(e.target.value); }; const handleSaveNotes = () => { onUpdateProgress(competency.id, isCompleted, notes, submittedAssignments); }; const handleToggleComplete = () => { onUpdateProgress(competency.id, !isCompleted, notes, submittedAssignments); }; const handleFileChange = (e) => { const file = e.target.files[0]; if (file) { // In a real app, you would upload the file to Firebase Storage here // For this example, we'll just store the file name and a simulated URL const newAssignment = { name: file.name, url: `simulated-url/${competency.id}/${file.name}`, // Placeholder URL uploadedAt: new Date().toISOString(), }; const updatedAssignments = [...submittedAssignments, newAssignment]; setSubmittedAssignments(updatedAssignments); onUpdateProgress(competency.id, isCompleted, notes, updatedAssignments); fileInputRef.current.value = ''; // Clear the input after selection } }; return (

{competency.name}

{competency.description}

Notes save automatically when you click outside the box.

{/* Assignment Submission Section */}

Assignment Submissions

{submittedAssignments.length > 0 ? (
    {submittedAssignments.map((assignment, index) => (
  • {assignment.name} ({new Date(assignment.uploadedAt).toLocaleDateString()})
  • ))}
) : (

No assignments submitted yet for this competency.

)}
); }; // --- Dashboard Component --- const Dashboard = () => { const { db, userId } = useContext(FirebaseContext); const [userProgress, setUserProgress] = useState({}); const [loadingProgress, setLoadingProgress] = useState(true); const [progressError, setProgressError] = useState(null); const [hoursToAdd, setHoursToAdd] = useState(''); const REQUIRED_HOURS = 1800; // Calculate completion percentage for competencies const completedCount = Object.values(userProgress).filter(p => p.completed).length; const totalCompetencies = competenciesData.length; const completionPercentage = totalCompetencies > 0 ? (completedCount / totalCompetencies) * 100 : 0; // Get total hours logged const currentTotalHours = userProgress.totalHoursLogged || 0; const hoursRemaining = Math.max(0, REQUIRED_HOURS - currentTotalHours); const hoursCompletionPercentage = (currentTotalHours / REQUIRED_HOURS) * 100; useEffect(() => { if (!db || !userId) { console.log("Firestore or userId not available yet."); return; } const userProgressDocRef = doc(db, `artifacts/${appId}/users/${userId}/progress/myProgress`); console.log(`Listening to user progress at: artifacts/${appId}/users/${userId}/progress/myProgress`); const unsubscribe = onSnapshot(userProgressDocRef, (docSnap) => { if (docSnap.exists()) { setUserProgress(docSnap.data()); } else { setUserProgress({}); // No progress yet } setLoadingProgress(false); }, (error) => { console.error("Error fetching user progress:", error); setProgressError("Failed to load your progress. Please try again."); setLoadingProgress(false); } ); return () => unsubscribe(); // Clean up the listener on unmount }, [db, userId]); const handleUpdateCompetencyProgress = async (competencyId, isCompleted, notes, submittedAssignments) => { if (!db || !userId) { console.error("Firestore or userId not available for update."); return; } const userProgressDocRef = doc(db, `artifacts/${appId}/users/${userId}/progress/myProgress`); const updatedProgress = { ...userProgress, [competencyId]: { completed: isCompleted, date: isCompleted ? new Date().toISOString() : (userProgress[competencyId]?.date || null), // Keep old date if unchecking notes: notes, submittedAssignments: submittedAssignments, // Store submitted assignments }, }; try { await setDoc(userProgressDocRef, updatedProgress, { merge: true }); console.log(`Competency ${competencyId} updated.`); } catch (e) { console.error("Error updating competency progress:", e); setProgressError("Failed to update competency progress. Please try again."); } }; const handleHoursChange = (e) => { const value = e.target.value; // Allow only numbers and empty string if (/^\d*$/.test(value) || value === '') { setHoursToAdd(value); } }; const handleLogHours = async () => { const hours = parseInt(hoursToAdd, 10); if (isNaN(hours) || hours <= 0) { // In a real app, you'd show a user-friendly message here instead of console.error console.error("Please enter a valid positive number of hours."); return; } if (!db || !userId) { console.error("Firestore or userId not available for hours update."); return; } const userProgressDocRef = doc(db, `artifacts/${appId}/users/${userId}/progress/myProgress`); const newTotalHours = currentTotalHours + hours; try { await setDoc(userProgressDocRef, { totalHoursLogged: newTotalHours }, { merge: true }); setHoursToAdd(''); // Clear input after logging console.log(`Logged ${hours} hours. New total: ${newTotalHours}`); } catch (e) { console.error("Error logging hours:", e); setProgressError("Failed to log hours. Please try again."); } }; if (loadingProgress) { return (
Loading your progress...
); } if (progressError) { return (
{progressError}
); } return (

Michigan Barber Student Dashboard

{/* User ID Display */}
Your User ID: {userId}

Share this ID with others to identify yourself in collaborative apps.

{/* Competency Progress Bar */}

Competency Completion

{completionPercentage.toFixed(0)}% Competencies Complete ({completedCount}/{totalCompetencies})
{/* Hours Tracking Section */}

Training Hours Progress

{currentTotalHours} out of {REQUIRED_HOURS} hours completed. ({hoursRemaining} hours remaining)
{hoursCompletionPercentage.toFixed(0)}% Hours Complete
{/* Competencies List */}
{competenciesData.map((competency) => ( ))}
{/* Resources Section */}
); }; // --- Main App Component --- const App = () => { return ( ); }; export default App;