const { useState, useEffect } = React; // Defensive normalization helper function normalizeToArray(x) { console.log('[normalizeToArray] Input:', typeof x, x); if (!x) return []; if (Array.isArray(x)) return x; if (typeof x === 'object') { if (Array.isArray(x.data)) return x.data; if (Array.isArray(x.assignments)) return x.assignments; return [x]; } return []; } // STEP 4: Error Boundary Component class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null, errorInfo: null }; } static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error('[ErrorBoundary] Caught error:', error, errorInfo); this.setState({ error, errorInfo }); } render() { if (this.state.hasError) { return (
âš ī¸

Something went wrong

The application encountered an unexpected error.

Show Technical Details

Error:

                                {this.state.error && this.state.error.toString()}
                            

Stack Trace:

                                {this.state.errorInfo && this.state.errorInfo.componentStack}
                            
); } return this.props.children; } } // Toast Notification Component function Toast({ message, type = 'success', onClose }) { useEffect(() => { const timer = setTimeout(onClose, 3000); return () => clearTimeout(timer); }, [onClose]); const styles = { position: 'fixed', top: '20px', right: '20px', padding: '15px 25px', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', zIndex: 10000, display: 'flex', alignItems: 'center', gap: '10px', animation: 'slideIn 0.3s ease-out', background: type === 'success' ? '#27ae60' : type === 'error' ? '#e74c3c' : '#3498db', color: 'white', fontWeight: '500', }; return (
{type === 'success' ? '✅' : type === 'error' ? '❌' : 'â„šī¸'} {message}
); } // API Helper with enhanced error handling and logging const api = { baseUrl: IWM_DATA.apiUrl, nonce: IWM_DATA.nonce, async fetch(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const headers = { 'Content-Type': 'application/json', 'X-WP-Nonce': this.nonce, ...options.headers, }; console.log(`[IWM API] ${options.method || 'GET'} ${endpoint}`, { url, headers: { ...headers, 'X-WP-Nonce': '***' }, body: options.body ? JSON.parse(options.body) : undefined }); try { const response = await fetch(url, { ...options, headers }); const data = await response.json(); console.log(`[IWM API] Response ${response.status}:`, data); // Handle all HTTP status codes properly if (response.status >= 200 && response.status < 300) { // Success (2xx) return { success: true, data, status: response.status }; } else if (response.status >= 400 && response.status < 500) { // Client error (4xx) console.error(`[IWM API] Client Error ${response.status}:`, data); return { success: false, error: data.message || `Client error: ${response.status}`, status: response.status, data }; } else if (response.status >= 500) { // Server error (5xx) console.error(`[IWM API] Server Error ${response.status}:`, data); return { success: false, error: data.message || `Server error: ${response.status}`, status: response.status, data }; } return { success: true, data, status: response.status }; } catch (error) { console.error('[IWM API] Network Error:', error); return { success: false, error: error.message || 'Network error occurred', status: 0 }; } }, async get(endpoint) { return this.fetch(endpoint); }, async post(endpoint, data) { return this.fetch(endpoint, { method: 'POST', body: JSON.stringify(data), }); }, async put(endpoint, data) { return this.fetch(endpoint, { method: 'PUT', body: JSON.stringify(data), }); }, async delete(endpoint) { return this.fetch(endpoint, { method: 'DELETE' }); }, }; // Modal Component function Modal({ isOpen, onClose, title, children, footer }) { if (!isOpen) return null; return (
e.stopPropagation()}>

{title}

{children}
{footer && (
{footer}
)}
); } // Dashboard Component function Dashboard() { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [toast, setToast] = useState(null); useEffect(() => { loadStats(); }, []); const loadStats = async () => { console.log('[Dashboard] Loading stats...'); try { const result = await api.get('/dashboard/stats'); if (result.success) { console.log('[Dashboard] Stats loaded:', result.data); setStats(result.data); } else { console.error('[Dashboard] Failed to load stats:', result.error); setToast({ type: 'error', message: result.error || 'Failed to load statistics' }); } } catch (error) { console.error('[Dashboard] Error loading stats:', error); setToast({ type: 'error', message: 'Network error loading statistics' }); } finally { setLoading(false); } }; if (loading) { return (

Loading dashboard...

); } return ( <>
📋

{stats?.total_assignments || 0}

Total Assignments

📋

{stats?.assigned_tasks || 0}

Assigned Tasks

⏰

{stats?.pending_tasks || 0}

Pending Tasks

✅

{stats?.completed_today || 0}

Completed Today

{toast && ( setToast(null)} /> )} ); } // Projects Component function Projects() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [selectedProject, setSelectedProject] = useState(null); const [toast, setToast] = useState(null); useEffect(() => { loadProjects(); }, []); const loadProjects = async () => { console.log('[Projects] Loading projects list...'); try { const result = await api.get('/projects'); if (result.success) { console.log('[Projects] Loaded projects:', result.data); setProjects(result.data); } else { console.error('[Projects] Failed to load projects:', result.error); setToast({ type: 'error', message: result.error || 'Failed to load projects' }); } } catch (error) { console.error('[Projects] Error loading projects:', error); setToast({ type: 'error', message: 'Network error loading projects' }); } finally { setLoading(false); } }; const createProject = () => { setSelectedProject(null); setShowModal(true); }; // Handler for successful project save - OPTION A: Update state directly const handleProjectSaved = (savedProject, isNew) => { console.log('[Projects] Project saved:', savedProject, 'isNew:', isNew); if (isNew) { // OPTION A (Preferred): Prepend new project to state setProjects(prevProjects => [savedProject, ...prevProjects]); console.log('[Projects] New project added to state'); setToast({ type: 'success', message: '✅ Project created successfully!' }); } else { // Update existing project in state setProjects(prevProjects => prevProjects.map(p => p.id === savedProject.id ? savedProject : p) ); console.log('[Projects] Project updated in state'); setToast({ type: 'success', message: '✅ Project updated successfully!' }); } setShowModal(false); }; // Fallback handler if state update fails const handleProjectSaveFallback = async () => { console.log('[Projects] Using fallback: re-fetching projects'); setShowModal(false); await loadProjects(); setToast({ type: 'success', message: '✅ Project saved successfully!' }); }; if (loading) { return (

Loading projects...

); } return (

My Projects

{projects.length === 0 ? (
📁

No projects yet

Create your first project to get started

) : (
{projects.map(project => ( ))}
)} {showModal && ( setShowModal(false)} onSave={handleProjectSaved} onSaveFallback={handleProjectSaveFallback} /> )} {toast && ( setToast(null)} /> )}
); } // Project Card Component function ProjectCard({ project, onSelect }) { return (
onSelect(project)}>

{project.title}

{project.status}

{project.description || 'No description'}

{project.start_date && `Started: ${new Date(project.start_date).toLocaleDateString()}`}
{project.budget && `Budget: $${parseFloat(project.budget).toFixed(2)}`}
); } // Project Modal Component function ProjectModal({ project, onClose, onSave, onSaveFallback }) { const [formData, setFormData] = useState({ title: project?.title || '', description: project?.description || '', start_date: project?.start_date || '', end_date: project?.end_date || '', budget: project?.budget || '', }); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const handleChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); setError(null); // Clear error on input change }; const handleSubmit = async (e) => { e.preventDefault(); // Validation if (!formData.title.trim()) { setError('Project title is required'); return; } setSaving(true); setError(null); console.log('[ProjectModal] Submitting project:', formData); try { let result; const isNew = !project; if (project) { console.log('[ProjectModal] Updating project ID:', project.id); result = await api.put(`/projects/${project.id}`, formData); } else { console.log('[ProjectModal] Creating new project'); result = await api.post('/projects', formData); } console.log('[ProjectModal] API Result:', result); if (result.success) { // Extract the created/updated project from response const savedProject = result.data; console.log('[ProjectModal] Success! Saved project:', savedProject); // OPTION A (Preferred): Call onSave with the saved project data if (savedProject && savedProject.id) { onSave(savedProject, isNew); } else { // OPTION B (Fallback): If response doesn't include project data, re-fetch console.warn('[ProjectModal] No project data in response, using fallback'); onSaveFallback(); } } else { // Handle API errors (4xx, 5xx) console.error('[ProjectModal] API Error:', result.error); setError(result.error || 'Failed to save project. Please try again.'); // Show detailed error for debugging if (result.status === 403) { setError('Permission denied. You may not have access to create/edit projects.'); } else if (result.status === 401) { setError('Authentication failed. Please refresh the page and try again.'); } else if (result.status >= 500) { setError('Server error occurred. Please try again later.'); } } } catch (error) { // Handle unexpected errors console.error('[ProjectModal] Unexpected error:', error); setError('An unexpected error occurred. Please check console for details.'); // Last resort fallback: reload page if (window.confirm('An error occurred. Would you like to reload the page?')) { window.location.reload(); } } finally { setSaving(false); } }; return ( } >
{error && (
❌ {error}
)}