TransHub / client /src /pages /Profile.tsx
linguabot's picture
Upload folder using huggingface_hub
b6d916a verified
import React, { useState, useEffect, useCallback } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { api } from '../services/api';
import {
UsersIcon,
DocumentTextIcon,
ChartBarIcon,
CogIcon,
UserGroupIcon,
AcademicCapIcon,
ShieldCheckIcon
} from '@heroicons/react/24/outline';
interface User {
name: string;
email: string;
role?: string;
displayName?: string;
online?: boolean;
}
interface SystemStats {
totalUsers: number;
practiceExamples: number;
totalSubmissions: number;
activeSessions: number;
}
interface PracticeExample {
_id: string;
title: string;
content: string;
sourceLanguage: string;
sourceCulture: string;
culturalElements: any[];
difficulty: string;
createdAt: string;
}
interface TutorialTask {
_id: string;
title: string;
content: string;
sourceLanguage: string;
sourceCulture: string;
weekNumber: number;
difficulty: string;
culturalElements: any[];
translationBrief?: string;
createdAt: string;
}
interface WeeklyPractice {
_id: string;
title: string;
content: string;
sourceLanguage: string;
sourceCulture: string;
weekNumber: number;
difficulty: string;
culturalElements: any[];
translationBrief?: string;
createdAt: string;
}
const Manage: React.FC = () => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [viewMode, setViewMode] = useState<'admin' | 'student' | 'auto'>(() => {
try { return (localStorage.getItem('viewMode') as any) || 'auto'; } catch { return 'auto'; }
});
const [stats, setStats] = useState<SystemStats | null>(null);
const [statsLoading, setStatsLoading] = useState(true);
const [examples, setExamples] = useState<PracticeExample[]>([]);
const [examplesLoading, setExamplesLoading] = useState(false);
const [tutorialTasks, setTutorialTasks] = useState<TutorialTask[]>([]);
const [tutorialTasksLoading, setTutorialTasksLoading] = useState(false);
const [weeklyPractice, setWeeklyPractice] = useState<WeeklyPractice[]>([]);
const [weeklyPracticeLoading, setWeeklyPracticeLoading] = useState(false);
const [users, setUsers] = useState<User[]>([]);
const [usersLoading, setUsersLoading] = useState(false);
const [loginSummary, setLoginSummary] = useState<any[]>([]);
const [loadingSummary, setLoadingSummary] = useState(false);
const [summaryRole, setSummaryRole] = useState<string>('all');
const [summaryRange, setSummaryRange] = useState<number>(7*24*60*60*1000);
const [showAllSummary, setShowAllSummary] = useState<boolean>(false);
const [allLoginSummary, setAllLoginSummary] = useState<any[]>([]);
const fetchLoginSummary = useCallback(async (rangeMs: number, role: string) => {
setLoadingSummary(true);
try {
const resp = await api.get(`/api/auth/admin/login-summary?sinceMs=${rangeMs}&role=${role}`);
const sessions = resp.data?.sessions || [];
setAllLoginSummary(sessions);
const filtered = role === 'all' ? sessions : sessions.filter((s: any) => s.role === role);
setLoginSummary(filtered);
} catch (e) {
setLoginSummary([]);
} finally {
setLoadingSummary(false);
}
}, [setLoadingSummary, setLoginSummary]);
useEffect(() => {
const handler = (e: any) => {
const mode = e?.detail;
if (mode === 'admin' || mode === 'student') setViewMode(mode);
};
window.addEventListener('view-mode-change', handler as any);
return () => window.removeEventListener('view-mode-change', handler as any);
}, []);
const [showAddUser, setShowAddUser] = useState(false);
const [showAddExample, setShowAddExample] = useState(false);
const [showAddTutorialTask, setShowAddTutorialTask] = useState(false);
const [showAddWeeklyPractice, setShowAddWeeklyPractice] = useState(false);
const [showAddTranslationBrief, setShowAddTranslationBrief] = useState(false);
const [editingUser, setEditingUser] = useState<User | null>(null);
const [editingExample, setEditingExample] = useState<PracticeExample | null>(null);
const [editingTutorialTask, setEditingTutorialTask] = useState<TutorialTask | null>(null);
const [editingWeeklyPractice, setEditingWeeklyPractice] = useState<WeeklyPractice | null>(null);
const [newUser, setNewUser] = useState({ name: '', displayName: '', email: '', role: 'student' });
const [newExample, setNewExample] = useState({
title: '',
content: '',
sourceLanguage: 'English',
sourceCulture: 'American',
difficulty: 'intermediate'
});
const [newTutorialTask, setNewTutorialTask] = useState({
title: '',
content: '',
sourceLanguage: 'English',
sourceCulture: 'American',
weekNumber: 1,
difficulty: 'intermediate'
});
const [newWeeklyPractice, setNewWeeklyPractice] = useState({
title: '',
content: '',
sourceLanguage: 'English',
sourceCulture: 'American',
weekNumber: 1,
difficulty: 'intermediate'
});
const [newTranslationBrief, setNewTranslationBrief] = useState({
weekNumber: 1,
translationBrief: '',
type: 'tutorial' // 'tutorial' or 'weekly-practice'
});
const navigate = useNavigate();
useEffect(() => {
const userData = localStorage.getItem('user');
if (userData) {
const user = JSON.parse(userData);
setUser(user);
// Redirect non-admin users to dashboard
if (user.role !== 'admin') {
navigate('/dashboard');
return;
}
} else {
navigate('/login');
return;
}
setLoading(false);
}, [navigate]);
const fetchAdminStats = useCallback(async () => {
try {
setStatsLoading(true);
const response = await api.get('/api/auth/admin/stats');
setStats(response.data.stats);
} catch (error) {
console.error('Failed to fetch admin stats:', error);
} finally {
setStatsLoading(false);
}
}, []);
const fetchPracticeExamples = useCallback(async () => {
try {
setExamplesLoading(true);
const response = await api.get('/api/auth/admin/practice-examples');
setExamples(response.data.examples);
} catch (error) {
console.error('Failed to fetch practice examples:', error);
} finally {
setExamplesLoading(false);
}
}, []);
const fetchUsers = useCallback(async () => {
try {
setUsersLoading(true);
const response = await api.get('/api/auth/admin/users');
setUsers(response.data.users);
} catch (error) {
console.error('Failed to fetch users:', error);
} finally {
setUsersLoading(false);
}
}, []);
const fetchTutorialTasks = useCallback(async () => {
try {
setTutorialTasksLoading(true);
const response = await api.get('/api/auth/admin/tutorial-tasks');
setTutorialTasks(response.data.tutorialTasks);
} catch (error) {
console.error('Failed to fetch tutorial tasks:', error);
} finally {
setTutorialTasksLoading(false);
}
}, []);
const fetchWeeklyPractice = useCallback(async () => {
try {
setWeeklyPracticeLoading(true);
const response = await api.get('/api/auth/admin/weekly-practice');
setWeeklyPractice(response.data.weeklyPractice);
} catch (error) {
console.error('Failed to fetch weekly practice:', error);
} finally {
setWeeklyPracticeLoading(false);
}
}, []);
useEffect(() => {
if (user?.role === 'admin') {
fetchAdminStats();
fetchPracticeExamples();
fetchTutorialTasks();
fetchWeeklyPractice();
fetchUsers();
(async () => {
try {
setLoadingSummary(true);
const resp = await api.get(`/api/auth/admin/login-summary?sinceMs=${summaryRange}&role=${summaryRole}`);
setLoginSummary(resp.data?.sessions || []);
} catch (e) {
setLoginSummary([]);
} finally {
setLoadingSummary(false);
}
})();
}
}, [user, fetchAdminStats, fetchPracticeExamples, fetchTutorialTasks, fetchWeeklyPractice, fetchUsers, summaryRange, summaryRole]);
const addUser = async () => {
try {
const response = await api.post('/api/auth/admin/users', newUser);
setNewUser({ name: '', displayName: '', email: '', role: 'student' });
setShowAddUser(false);
await fetchUsers();
alert('User added successfully!');
} catch (error) {
console.error('Failed to add user:', error);
alert('Failed to add user');
}
};
const updateUser = async (email: string, updates: Partial<User>) => {
try {
await api.put(`/api/auth/admin/users/${email}`, updates);
setEditingUser(null);
await fetchUsers();
// If updating the currently logged-in user, refresh localStorage so greetings use displayName immediately
try {
const cur = localStorage.getItem('user');
if (cur) {
const curObj = JSON.parse(cur);
if (curObj?.email === email) {
const next = { ...curObj, ...(updates.name ? { name: updates.name } : {}), ...(updates.displayName !== undefined ? { displayName: updates.displayName } : {}) };
localStorage.setItem('user', JSON.stringify(next));
}
}
} catch {}
alert('User updated successfully!');
} catch (error) {
console.error('Failed to update user:', error);
alert('Failed to update user');
}
};
const deleteUser = async (email: string) => {
if (!window.confirm('Are you sure you want to delete this user?')) return;
try {
await api.delete(`/api/auth/admin/users/${email}`);
await fetchUsers();
alert('User deleted successfully!');
} catch (error) {
console.error('Failed to delete user:', error);
alert('Failed to delete user');
}
};
const addExample = async () => {
try {
await api.post('/api/auth/admin/practice-examples', newExample);
setNewExample({
title: '',
content: '',
sourceLanguage: 'English',
sourceCulture: 'American',
difficulty: 'intermediate'
});
setShowAddExample(false);
await fetchPracticeExamples();
alert('Example added successfully!');
} catch (error) {
console.error('Failed to add example:', error);
alert('Failed to add example');
}
};
const updateExample = async (id: string, updates: Partial<PracticeExample>) => {
try {
await api.put(`/api/auth/admin/practice-examples/${id}`, updates);
setEditingExample(null);
await fetchPracticeExamples();
alert('Example updated successfully!');
} catch (error) {
console.error('Failed to update example:', error);
alert('Failed to update example');
}
};
const deleteExample = async (id: string) => {
if (!window.confirm('Are you sure you want to delete this example?')) return;
try {
await api.delete(`/api/auth/admin/practice-examples/${id}`);
await fetchPracticeExamples();
alert('Example deleted successfully!');
} catch (error) {
console.error('Failed to delete example:', error);
alert('Failed to delete example');
}
};
// Tutorial Tasks CRUD
const addTutorialTask = async () => {
try {
await api.post('/api/auth/admin/tutorial-tasks', newTutorialTask);
setNewTutorialTask({
title: '',
content: '',
sourceLanguage: 'English',
sourceCulture: 'American',
weekNumber: 1,
difficulty: 'intermediate'
});
setShowAddTutorialTask(false);
await fetchTutorialTasks();
alert('Tutorial task added successfully!');
} catch (error) {
console.error('Failed to add tutorial task:', error);
alert('Failed to add tutorial task');
}
};
const updateTutorialTask = async (id: string, updates: Partial<TutorialTask>) => {
try {
await api.put(`/api/auth/admin/tutorial-tasks/${id}`, updates);
setEditingTutorialTask(null);
await fetchTutorialTasks();
alert('Tutorial task updated successfully!');
} catch (error) {
console.error('Failed to update tutorial task:', error);
alert('Failed to update tutorial task');
}
};
const deleteTutorialTask = async (id: string) => {
if (!window.confirm('Are you sure you want to delete this tutorial task?')) return;
try {
await api.delete(`/api/auth/admin/tutorial-tasks/${id}`);
await fetchTutorialTasks();
alert('Tutorial task deleted successfully!');
} catch (error) {
console.error('Failed to delete tutorial task:', error);
alert('Failed to delete tutorial task');
}
};
// Weekly Practice CRUD
const addWeeklyPractice = async () => {
try {
await api.post('/api/auth/admin/weekly-practice', newWeeklyPractice);
setNewWeeklyPractice({
title: '',
content: '',
sourceLanguage: 'English',
sourceCulture: 'American',
weekNumber: 1,
difficulty: 'intermediate'
});
setShowAddWeeklyPractice(false);
await fetchWeeklyPractice();
alert('Weekly practice added successfully!');
} catch (error) {
console.error('Failed to add weekly practice:', error);
alert('Failed to add weekly practice');
}
};
const updateWeeklyPractice = async (id: string, updates: Partial<WeeklyPractice>) => {
try {
await api.put(`/api/auth/admin/weekly-practice/${id}`, updates);
setEditingWeeklyPractice(null);
await fetchWeeklyPractice();
alert('Weekly practice updated successfully!');
} catch (error) {
console.error('Failed to update weekly practice:', error);
alert('Failed to update weekly practice');
}
};
const deleteWeeklyPractice = async (id: string) => {
if (!window.confirm('Are you sure you want to delete this weekly practice?')) return;
try {
await api.delete(`/api/auth/admin/weekly-practice/${id}`);
await fetchWeeklyPractice();
alert('Weekly practice deleted successfully!');
} catch (error) {
console.error('Failed to delete weekly practice:', error);
alert('Failed to delete weekly practice');
}
};
const addTranslationBrief = async () => {
try {
await api.post('/api/auth/admin/translation-brief', newTranslationBrief);
setShowAddTranslationBrief(false);
setNewTranslationBrief({
weekNumber: 1,
translationBrief: '',
type: 'tutorial'
});
alert('Translation brief added successfully!');
} catch (error) {
console.error('Failed to add translation brief:', error);
alert('Failed to add translation brief');
}
};
const handleLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
window.location.href = '/';
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600"></div>
</div>
);
}
if (!user || user.role !== 'admin') {
return null; // Will redirect in useEffect
}
return (
<div className="px-4 sm:px-6 lg:px-8">
<div className="mb-8">
<div className="flex justify-between items-center">
<div>
<div className="flex items-center">
<img src="/icons/manage.svg" alt="Manage" className="h-8 w-8 mr-3" />
<h1 className="text-2xl font-bold text-gray-900">Manage</h1>
</div>
<p className="mt-2 text-gray-600">Admin panel for system management</p>
</div>
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-500">
Admin • {user.email}
</span>
<div className="flex items-center bg-gray-100 rounded-md p-1 text-xs">
<button
onClick={() => {
try { localStorage.setItem('viewMode', 'admin'); } catch {}
window.dispatchEvent(new CustomEvent('view-mode-change', { detail: 'admin' } as any));
setViewMode('admin');
}}
className={`px-2 py-1 rounded-sm font-medium ${viewMode === 'admin' ? 'bg-white text-gray-900 ring-1 ring-gray-300' : 'text-gray-700 hover:bg-white'}`}
>
Admin view
</button>
<button
onClick={() => {
try { localStorage.setItem('viewMode', 'student'); } catch {}
window.dispatchEvent(new CustomEvent('view-mode-change', { detail: 'student' } as any));
setViewMode('student');
}}
className={`px-2 py-1 rounded-sm font-medium ${viewMode === 'student' ? 'bg-white text-gray-900 ring-1 ring-gray-300' : 'text-gray-700 hover:bg-white'}`}
>
Student view
</button>
</div>
<button
onClick={handleLogout}
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Logout
</button>
</div>
</div>
</div>
{/* Login Summary removed as requested */}
{/* Admin Management Sections */}
<div className="grid grid-cols-1 gap-6 mb-8">
{/* User Management */}
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center mb-4">
<img src="/icons/user management.png" alt="User Management" className="mr-3" style={{ width: '2.4rem', height: '2.4rem' }} />
<h2 className="text-lg font-medium text-gray-900">User Management</h2>
</div>
<p className="text-gray-600 mb-4">
Manage student accounts, roles, and permissions.
</p>
<div className="space-y-2 mb-4">
<div className="flex items-center text-sm text-gray-500">
<div className="w-2 h-2 bg-purple-600 rounded-full mr-3"></div>
{usersLoading ? 'Loading users...' : `${users.length} registered users`}
</div>
<div className="flex items-center text-sm text-gray-500">
<div className="w-2 h-2 bg-purple-600 rounded-full mr-3"></div>
{users.filter(u => u.role === 'admin').length} admin users
</div>
<div className="flex items-center text-sm text-gray-500">
<div className="w-2 h-2 bg-purple-600 rounded-full mr-3"></div>
{users.filter(u => u.role === 'student').length} student users
</div>
</div>
<div className="space-y-2">
<button
onClick={() => setShowAddUser(!showAddUser)}
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
{showAddUser ? 'Cancel' : 'Add User'}
</button>
<button
onClick={fetchUsers}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium ml-2"
>
Refresh
</button>
</div>
{/* Add User Form */}
{showAddUser && (
<div className="mt-4 p-4 bg-gray-50 rounded-md">
<h4 className="text-sm font-medium text-gray-900 mb-3">Add New User:</h4>
<div className="space-y-3">
<input
type="text"
placeholder="Name"
value={newUser.name}
onChange={(e) => setNewUser({...newUser, name: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<input
type="text"
placeholder="Display Name (optional)"
value={newUser.displayName}
onChange={(e) => setNewUser({...newUser, displayName: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<input
type="email"
placeholder="Email"
value={newUser.email}
onChange={(e) => setNewUser({...newUser, email: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<select
value={newUser.role}
onChange={(e) => setNewUser({...newUser, role: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="student">Student</option>
<option value="admin">Admin</option>
</select>
<button
onClick={addUser}
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Add User
</button>
</div>
</div>
)}
{/* Users List */}
{users.length > 0 && (
<div className="mt-4 max-h-60 overflow-y-auto">
<h4 className="text-sm font-medium text-gray-900 mb-2">Registered Users:</h4>
<div className="space-y-2">
{users.map((user) => (
<div key={user.email} className="bg-gray-50 p-3 rounded-md">
<div className="flex justify-between items-center">
<div className="flex-1">
<p className="text-sm font-medium text-gray-900 flex items-center">
{user.online && (
<span className="inline-block w-2 h-2 bg-green-500 rounded-full mr-2" aria-label="online" />
)}
{user.name}
</p>
<p className="text-xs text-gray-600">{user.email}</p>
</div>
<div className="flex items-center space-x-2">
<span className={`text-xs px-2 py-1 rounded ${
user.role === 'admin'
? 'bg-red-100 text-red-800'
: 'bg-green-100 text-green-800'
}`}>
{user.role}
</span>
<button
onClick={() => setEditingUser(user)}
className="text-blue-600 hover:text-blue-800 text-xs"
>
Edit
</button>
{user.email !== 'hongchang.yu@monash.edu' && (
<button
onClick={() => deleteUser(user.email)}
className="text-red-600 hover:text-red-800 text-xs"
>
Delete
</button>
)}
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Edit User Modal */}
{editingUser && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded-lg max-w-md w-full mx-4">
<h3 className="text-lg font-medium text-gray-900 mb-4">Edit User</h3>
<div className="space-y-3">
<input
type="text"
placeholder="Name"
value={editingUser.name}
onChange={(e) => setEditingUser({...editingUser, name: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<input
type="text"
placeholder="Preferred name (on-screen)"
value={editingUser.displayName || ''}
onChange={(e) => setEditingUser({...editingUser, displayName: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<input
type="email"
placeholder="Email"
value={editingUser.email}
disabled
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-100"
/>
<select
value={editingUser.role}
onChange={(e) => setEditingUser({...editingUser, role: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="student">Student</option>
<option value="admin">Admin</option>
</select>
<div className="flex space-x-2">
<button
onClick={() => updateUser(editingUser.email, editingUser)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Update
</button>
<button
onClick={() => setEditingUser(null)}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Cancel
</button>
</div>
</div>
</div>
</div>
)}
</div>
{/* Content Management removed */}
{/* Tutorial Tasks Management */}
<div className="bg-white rounded-lg shadow p-6 mb-6">
<div className="flex items-center mb-4">
<img src="/icons/Tutorial Tasks Management.png" alt="Tutorial Tasks Management" className="mr-3" style={{ width: '2.4rem', height: '2.4rem' }} />
<h2 className="text-lg font-medium text-gray-900">Tutorial Tasks Management</h2>
</div>
<p className="text-gray-600 mb-4">
Manage tutorial tasks for each week.
</p>
<div className="space-y-2 mb-4">
<div className="flex items-center text-sm text-gray-500">
<div className="w-2 h-2 bg-green-600 rounded-full mr-3"></div>
{tutorialTasksLoading ? 'Loading tutorial tasks...' : `${tutorialTasks.length} tutorial tasks`}
</div>
<div className="flex items-center text-sm text-gray-500">
<div className="w-2 h-2 bg-green-600 rounded-full mr-3"></div>
Edit existing tutorial tasks
</div>
</div>
<div className="space-y-2">
<button
onClick={() => setShowAddTutorialTask(!showAddTutorialTask)}
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
{showAddTutorialTask ? 'Cancel' : 'Add Tutorial Task'}
</button>
<button
onClick={() => setShowAddTranslationBrief(!showAddTranslationBrief)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium ml-2"
>
{showAddTranslationBrief ? 'Cancel' : 'Add Translation Brief'}
</button>
<button
onClick={fetchTutorialTasks}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium ml-2"
>
Refresh
</button>
</div>
{/* Tutorial Tasks List */}
{tutorialTasks.length > 0 && (
<div className="mt-4 max-h-60 overflow-y-auto">
<h4 className="text-sm font-medium text-gray-900 mb-2">Current Tutorial Tasks:</h4>
<div className="space-y-2">
{tutorialTasks.map((task) => (
<div key={task._id} className="bg-gray-50 p-3 rounded-md">
<div className="flex justify-between items-start">
<div className="flex-1">
<p className="text-sm font-medium text-gray-900">{task.title}</p>
<p className="text-xs text-gray-600 mt-1 line-clamp-2">{task.content}</p>
<div className="flex items-center mt-1 space-x-2">
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
Week {task.weekNumber}
</span>
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
{task.sourceLanguage}
</span>
<span className="text-xs bg-purple-100 text-purple-800 px-2 py-1 rounded">
{task.difficulty}
</span>
</div>
</div>
<div className="flex items-center space-x-2 ml-2">
<button
onClick={() => setEditingTutorialTask(task)}
className="text-blue-600 hover:text-blue-800 text-xs"
>
Edit
</button>
<button
onClick={() => deleteTutorialTask(task._id)}
className="text-red-600 hover:text-red-800 text-xs"
>
Delete
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Add Tutorial Task Form */}
{showAddTutorialTask && (
<div className="mt-4 p-4 bg-gray-50 rounded-md">
<h4 className="text-sm font-medium text-gray-900 mb-3">Add New Tutorial Task:</h4>
<div className="space-y-3">
<input
type="text"
placeholder="Title"
value={newTutorialTask.title}
onChange={(e) => setNewTutorialTask({...newTutorialTask, title: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<textarea
placeholder="Content"
value={newTutorialTask.content}
onChange={(e) => setNewTutorialTask({...newTutorialTask, content: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
rows={3}
/>
<div className="grid grid-cols-3 gap-2">
<select
value={newTutorialTask.sourceLanguage}
onChange={(e) => setNewTutorialTask({...newTutorialTask, sourceLanguage: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="English">English</option>
<option value="Chinese">Chinese</option>
</select>
<select
value={newTutorialTask.weekNumber}
onChange={(e) => setNewTutorialTask({...newTutorialTask, weekNumber: parseInt(e.target.value)})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
{[1, 2, 3, 4, 5, 6].map(week => (
<option key={week} value={week}>Week {week}</option>
))}
</select>
<select
value={newTutorialTask.difficulty}
onChange={(e) => setNewTutorialTask({...newTutorialTask, difficulty: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
</div>
<button
onClick={addTutorialTask}
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Add Tutorial Task
</button>
</div>
</div>
)}
{/* Add Translation Brief Form */}
{showAddTranslationBrief && (
<div className="mt-4 p-4 bg-gray-50 rounded-md">
<h4 className="text-sm font-medium text-gray-900 mb-3">Add Translation Brief:</h4>
<div className="space-y-3">
<select
value={newTranslationBrief.type}
onChange={(e) => setNewTranslationBrief({...newTranslationBrief, type: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="tutorial">Tutorial Tasks</option>
<option value="weekly-practice">Weekly Practice</option>
</select>
<select
value={newTranslationBrief.weekNumber}
onChange={(e) => setNewTranslationBrief({...newTranslationBrief, weekNumber: parseInt(e.target.value)})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
{[1, 2, 3, 4, 5, 6].map(week => (
<option key={week} value={week}>Week {week}</option>
))}
</select>
<textarea
placeholder="Translation Brief"
value={newTranslationBrief.translationBrief}
onChange={(e) => setNewTranslationBrief({...newTranslationBrief, translationBrief: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
rows={4}
/>
<button
onClick={addTranslationBrief}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Add Translation Brief
</button>
</div>
</div>
)}
{/* Edit Tutorial Task Modal */}
{editingTutorialTask && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded-lg max-w-md w-full mx-4 max-h-96 overflow-y-auto">
<h3 className="text-lg font-medium text-gray-900 mb-4">Edit Tutorial Task</h3>
<div className="space-y-3">
<input
type="text"
placeholder="Title"
value={editingTutorialTask.title}
onChange={(e) => setEditingTutorialTask({...editingTutorialTask, title: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<textarea
placeholder="Content"
value={editingTutorialTask.content}
onChange={(e) => setEditingTutorialTask({...editingTutorialTask, content: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
rows={3}
/>
<div className="grid grid-cols-3 gap-2">
<select
value={editingTutorialTask.sourceLanguage}
onChange={(e) => setEditingTutorialTask({...editingTutorialTask, sourceLanguage: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="English">English</option>
<option value="Chinese">Chinese</option>
</select>
<select
value={editingTutorialTask.weekNumber}
onChange={(e) => setEditingTutorialTask({...editingTutorialTask, weekNumber: parseInt(e.target.value)})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
{[1, 2, 3, 4, 5, 6].map(week => (
<option key={week} value={week}>Week {week}</option>
))}
</select>
<select
value={editingTutorialTask.difficulty}
onChange={(e) => setEditingTutorialTask({...editingTutorialTask, difficulty: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
</div>
<div className="flex space-x-2">
<button
onClick={() => updateTutorialTask(editingTutorialTask._id, editingTutorialTask)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Update
</button>
<button
onClick={() => setEditingTutorialTask(null)}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Cancel
</button>
</div>
</div>
</div>
</div>
)}
</div>
{/* Weekly Practice Management */}
<div className="bg-white rounded-lg shadow p-6 mb-6">
<div className="flex items-center mb-4">
<img src="/icons/Weekly Practice Management.png" alt="Weekly Practice Management" className="mr-3" style={{ width: '2.4rem', height: '2.4rem' }} />
<h2 className="text-lg font-medium text-gray-900">Weekly Practice Management</h2>
</div>
<p className="text-gray-600 mb-4">
Manage weekly practice tasks for each week.
</p>
<div className="space-y-2 mb-4">
<div className="flex items-center text-sm text-gray-500">
<div className="w-2 h-2 bg-purple-600 rounded-full mr-3"></div>
{weeklyPracticeLoading ? 'Loading weekly practice...' : `${weeklyPractice.length} weekly practice tasks`}
</div>
<div className="flex items-center text-sm text-gray-500">
<div className="w-2 h-2 bg-purple-600 rounded-full mr-3"></div>
Edit existing weekly practice tasks
</div>
</div>
<div className="space-y-2">
<button
onClick={() => setShowAddWeeklyPractice(!showAddWeeklyPractice)}
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
{showAddWeeklyPractice ? 'Cancel' : 'Add Weekly Practice'}
</button>
<button
onClick={() => setShowAddTranslationBrief(!showAddTranslationBrief)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium ml-2"
>
{showAddTranslationBrief ? 'Cancel' : 'Add Translation Brief'}
</button>
<button
onClick={fetchWeeklyPractice}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium ml-2"
>
Refresh
</button>
</div>
{/* Weekly Practice List */}
{weeklyPractice.length > 0 && (
<div className="mt-4 max-h-60 overflow-y-auto">
<h4 className="text-sm font-medium text-gray-900 mb-2">Current Weekly Practice:</h4>
<div className="space-y-2">
{weeklyPractice.map((practice) => (
<div key={practice._id} className="bg-gray-50 p-3 rounded-md">
<div className="flex justify-between items-start">
<div className="flex-1">
<p className="text-sm font-medium text-gray-900">{practice.title}</p>
<p className="text-xs text-gray-600 mt-1 line-clamp-2">{practice.content}</p>
<div className="flex items-center mt-1 space-x-2">
<span className="text-xs bg-purple-100 text-purple-800 px-2 py-1 rounded">
Week {practice.weekNumber}
</span>
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
{practice.sourceLanguage}
</span>
<span className="text-xs bg-orange-100 text-orange-800 px-2 py-1 rounded">
{practice.difficulty}
</span>
</div>
</div>
<div className="flex items-center space-x-2 ml-2">
<button
onClick={() => setEditingWeeklyPractice(practice)}
className="text-blue-600 hover:text-blue-800 text-xs"
>
Edit
</button>
<button
onClick={() => deleteWeeklyPractice(practice._id)}
className="text-red-600 hover:text-red-800 text-xs"
>
Delete
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Add Weekly Practice Form */}
{showAddWeeklyPractice && (
<div className="mt-4 p-4 bg-gray-50 rounded-md">
<h4 className="text-sm font-medium text-gray-900 mb-3">Add New Weekly Practice:</h4>
<div className="space-y-3">
<input
type="text"
placeholder="Title"
value={newWeeklyPractice.title}
onChange={(e) => setNewWeeklyPractice({...newWeeklyPractice, title: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<textarea
placeholder="Content"
value={newWeeklyPractice.content}
onChange={(e) => setNewWeeklyPractice({...newWeeklyPractice, content: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
rows={3}
/>
<div className="grid grid-cols-3 gap-2">
<select
value={newWeeklyPractice.sourceLanguage}
onChange={(e) => setNewWeeklyPractice({...newWeeklyPractice, sourceLanguage: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="English">English</option>
<option value="Chinese">Chinese</option>
</select>
<select
value={newWeeklyPractice.weekNumber}
onChange={(e) => setNewWeeklyPractice({...newWeeklyPractice, weekNumber: parseInt(e.target.value)})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
{[1, 2, 3, 4, 5, 6].map(week => (
<option key={week} value={week}>Week {week}</option>
))}
</select>
<select
value={newWeeklyPractice.difficulty}
onChange={(e) => setNewWeeklyPractice({...newWeeklyPractice, difficulty: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
</div>
<button
onClick={addWeeklyPractice}
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Add Weekly Practice
</button>
</div>
</div>
)}
{/* Edit Weekly Practice Modal */}
{editingWeeklyPractice && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded-lg max-w-md w-full mx-4 max-h-96 overflow-y-auto">
<h3 className="text-lg font-medium text-gray-900 mb-4">Edit Weekly Practice</h3>
<div className="space-y-3">
<input
type="text"
placeholder="Title"
value={editingWeeklyPractice.title}
onChange={(e) => setEditingWeeklyPractice({...editingWeeklyPractice, title: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
<textarea
placeholder="Content"
value={editingWeeklyPractice.content}
onChange={(e) => setEditingWeeklyPractice({...editingWeeklyPractice, content: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
rows={3}
/>
<div className="grid grid-cols-3 gap-2">
<select
value={editingWeeklyPractice.sourceLanguage}
onChange={(e) => setEditingWeeklyPractice({...editingWeeklyPractice, sourceLanguage: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="English">English</option>
<option value="Chinese">Chinese</option>
</select>
<select
value={editingWeeklyPractice.weekNumber}
onChange={(e) => setEditingWeeklyPractice({...editingWeeklyPractice, weekNumber: parseInt(e.target.value)})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
{[1, 2, 3, 4, 5, 6].map(week => (
<option key={week} value={week}>Week {week}</option>
))}
</select>
<select
value={editingWeeklyPractice.difficulty}
onChange={(e) => setEditingWeeklyPractice({...editingWeeklyPractice, difficulty: e.target.value})}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
</div>
<div className="flex space-x-2">
<button
onClick={() => updateWeeklyPractice(editingWeeklyPractice._id, editingWeeklyPractice)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Update
</button>
<button
onClick={() => setEditingWeeklyPractice(null)}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Cancel
</button>
</div>
</div>
</div>
</div>
)}
</div>
</div>
{/* Quick Stats */}
<div className="bg-white rounded-lg shadow p-6">
<div className="flex items-center mb-4">
<img src="/icons/Quick Stats.png" alt="Quick Stats" className="mr-3" style={{ width: '2.4rem', height: '2.4rem' }} />
<h2 className="text-lg font-medium text-gray-900">Quick Stats</h2>
</div>
{statsLoading ? (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="bg-gray-50 p-4 rounded-lg animate-pulse">
<div className="flex items-center">
<div className="h-6 w-6 bg-gray-300 rounded mr-2"></div>
<div>
<div className="h-4 bg-gray-300 rounded w-20 mb-2"></div>
<div className="h-6 bg-gray-300 rounded w-8"></div>
</div>
</div>
</div>
))}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-purple-50 p-4 rounded-lg">
<div className="flex items-center">
<UserGroupIcon className="h-6 w-6 text-purple-600 mr-2" />
<div>
<p className="text-sm text-purple-600">Total Users</p>
<p className="text-2xl font-bold text-purple-900">{stats?.totalUsers || 0}</p>
</div>
</div>
</div>
<div className="bg-blue-50 p-4 rounded-lg">
<div className="flex items-center">
<AcademicCapIcon className="h-6 w-6 text-blue-600 mr-2" />
<div>
<p className="text-sm text-blue-600">Practice Examples</p>
<p className="text-2xl font-bold text-blue-900">{stats?.practiceExamples || 0}</p>
</div>
</div>
</div>
<div className="bg-green-50 p-4 rounded-lg">
<div className="flex items-center">
<DocumentTextIcon className="h-6 w-6 text-green-600 mr-2" />
<div>
<p className="text-sm text-green-600">Submissions</p>
<p className="text-2xl font-bold text-green-900">{stats?.totalSubmissions || 0}</p>
</div>
</div>
</div>
<div className="bg-orange-50 p-4 rounded-lg">
<div className="flex items-center">
<ShieldCheckIcon className="h-6 w-6 text-orange-600 mr-2" />
<div>
<p className="text-sm text-orange-600">Active Sessions</p>
<p className="text-2xl font-bold text-orange-900">{stats?.activeSessions || 0}</p>
</div>
</div>
</div>
</div>
)}
</div>
<div className="mt-6">
<Link
to="/dashboard"
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors duration-200"
>
Back to Dashboard
</Link>
</div>
</div>
);
};
export default Manage;