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 (
);
}
if (error) {
return (
);
}
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 (
{/* Assignment Submission Section */}
);
};
// --- 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 (
);
}
if (progressError) {
return (
);
}
return (
{/* Hours Tracking Section */}
{/* Competencies List */}
);
};
// --- Main App Component ---
const App = () => {
return (
);
};
export default App;
Loading dashboard...
{error}
{competency.name}
{competency.description}
Notes save automatically when you click outside the box.
Assignment Submissions
{submittedAssignments.length > 0 ? (-
{submittedAssignments.map((assignment, index) => (
- {assignment.name} ({new Date(assignment.uploadedAt).toLocaleDateString()}) ))}
No assignments submitted yet for this competency.
)}Loading your progress...
{progressError}
Michigan Barber Student Dashboard
{/* User ID Display */}
Your User ID: {userId}
{/* Competency Progress Bar */}
Share this ID with others to identify yourself in collaborative apps.
Competency Completion
{completionPercentage.toFixed(0)}% Competencies Complete ({completedCount}/{totalCompetencies})
Training Hours Progress
{currentTotalHours} out of {REQUIRED_HOURS} hours completed.
({hoursRemaining} hours remaining)
{hoursCompletionPercentage.toFixed(0)}% Hours Complete
{competenciesData.map((competency) => (
))}
{/* Resources Section */}