import React, { useState, useEffect, useMemo } from 'react'; import { Home, FileText, PlusCircle, Users, BarChart2, Settings, LogOut, Menu, X, AlertCircle, CheckCircle, Clock, ChevronRight, Search, Filter, Paperclip, Send, Download, MapPin, Tag, MessageSquare, Briefcase, Shield, ShieldAlert } from 'lucide-react'; // --- MOCK DATA --- const MOCK_USERS = [ { id: 'U1', name: 'Ahmad Faiz', email: 'ahmad@student.edu.my', role: 'user', avatar: 'https://i.pravatar.cc/150?u=U1' }, { id: 'U2', name: 'Siti Nurhaliza', email: 'siti@student.edu.my', role: 'user', avatar: 'https://i.pravatar.cc/150?u=U2' }, { id: 'T1', name: 'Technician Kamarul', email: 'kamarul@tech.edu.my', role: 'technician', avatar: 'https://i.pravatar.cc/150?u=T1' }, { id: 'T2', name: 'Technician Ravi', email: 'ravi@tech.edu.my', role: 'technician', avatar: 'https://i.pravatar.cc/150?u=T2' }, { id: 'A1', name: 'Admin Sarah', email: 'sarah@admin.edu.my', role: 'admin', avatar: 'https://i.pravatar.cc/150?u=A1' }, { id: 'SA1', name: 'Super Admin Johan', email: 'johan@admin.edu.my', role: 'super_admin', avatar: 'https://i.pravatar.cc/150?u=SA1' }, ]; const MOCK_CATEGORIES = ['Hardware', 'Software', 'Network', 'Facilities', 'Other']; const MOCK_LOCATIONS = ['Makmal Komputer 1', 'Makmal Komputer 2', 'Bilik Kuliah A', 'Dewan Kuliah Utama', 'Pejabat Pentadbiran']; const initialComplaints = [ { id: 'CMP-001', title: 'Projector not turning on', description: 'The projector in Bilik Kuliah A is completely dead. Power cable seems fine.', category: 'Hardware', location: 'Bilik Kuliah A', priority: 'High', status: 'Pending', reporterId: 'U1', date: '2026-04-25T08:30:00Z', assigneeId: null, comments: [] }, { id: 'CMP-002', title: 'WiFi disconnects randomly', description: 'Network keeps dropping every 10 minutes in Makmal 2.', category: 'Network', location: 'Makmal Komputer 2', priority: 'Medium', status: 'In Progress', reporterId: 'U2', date: '2026-04-24T10:15:00Z', assigneeId: 'T1', comments: [{ id: 1, author: 'Technician Kamarul', text: 'Checking the router configuration now.', time: '2026-04-25T09:00:00Z' }] }, { id: 'CMP-003', title: 'Aircon leaking water', description: 'Water is dripping near the front desk.', category: 'Facilities', location: 'Pejabat Pentadbiran', priority: 'High', status: 'Resolved', reporterId: 'U1', date: '2026-04-20T14:20:00Z', assigneeId: 'T2', comments: [{ id: 2, author: 'Technician Ravi', text: 'Pipe cleared and filter replaced.', time: '2026-04-21T11:00:00Z' }] }, { id: 'CMP-004', title: 'AutoCAD license expired', description: 'Cannot open AutoCAD on PC 12.', category: 'Software', location: 'Makmal Komputer 1', priority: 'Low', status: 'Pending', reporterId: 'U2', date: '2026-04-26T09:05:00Z', assigneeId: null, comments: [] }, { id: 'CMP-005', title: 'Broken chair', description: 'Chair leg is broken at row 3.', category: 'Facilities', location: 'Dewan Kuliah Utama', priority: 'Low', status: 'Resolved', reporterId: 'U1', date: '2026-04-15T10:00:00Z', assigneeId: 'T2', comments: [] }, ]; // --- UTILITY COMPONENTS --- const Badge = ({ children, variant = 'default' }) => { const styles = { default: 'bg-slate-100 text-slate-700 border-slate-200', success: 'bg-green-100 text-green-700 border-green-200', warning: 'bg-amber-100 text-amber-700 border-amber-200', danger: 'bg-red-100 text-red-700 border-red-200', primary: 'bg-blue-100 text-blue-700 border-blue-200', }; return ( {children} ); }; const Card = ({ children, className = '', noPadding = false }) => (
{children}
); const Button = ({ children, variant = 'primary', icon: Icon, className = '', ...props }) => { const baseStyle = "inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium transition-colors rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2"; const variants = { primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 shadow-sm", secondary: "bg-white text-slate-700 border border-slate-200 hover:bg-slate-50 focus:ring-slate-500", danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500", ghost: "bg-transparent text-slate-600 hover:bg-slate-100 focus:ring-slate-500" }; return ( ); }; const StatusBadge = ({ status }) => { if (status === 'Resolved') return Resolved; if (status === 'In Progress') return In Progress; return Pending; }; const PriorityBadge = ({ priority }) => { if (priority === 'High') return High; if (priority === 'Medium') return Medium; return Low; }; // --- MAIN APPLICATION --- export default function App() { const [currentUser, setCurrentUser] = useState(null); const [currentRoute, setCurrentRoute] = useState({ path: '/login', params: {} }); const [complaints, setComplaints] = useState(initialComplaints); const [users, setUsers] = useState(MOCK_USERS); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); // Navigation Helper const navigate = (path, params = {}) => { setCurrentRoute({ path, params }); setIsMobileMenuOpen(false); }; // Auth Helpers const login = (email, password) => { // Mock login logic const user = users.find(u => u.email === email); if (user) { setCurrentUser(user); navigate(user.role === 'user' ? '/dashboard' : (user.role === 'technician' ? '/tech-dashboard' : '/admin-dashboard')); } else { alert("Invalid credentials. Try 'ahmad@student.edu.my', 'kamarul@tech.edu.my', or 'sarah@admin.edu.my'"); } }; const logout = () => { setCurrentUser(null); navigate('/login'); }; // Render Router let content; if (!currentUser && currentRoute.path === '/register') content = ; else if (!currentUser) content = ; else { // Authenticated Layout content = (
{/* Sidebar */} {/* Main Content */}
{/* Topbar for mobile */}
Aduan System
{/* Routing Logic */} {currentRoute.path === '/dashboard' && } {currentRoute.path === '/submit' && } {currentRoute.path === '/my-complaints' && } {currentRoute.path === '/complaint' && } {currentRoute.path === '/tech-dashboard' && } {currentRoute.path === '/admin-dashboard' && } {currentRoute.path === '/manage-complaints' && } {currentRoute.path === '/reports' && } {currentRoute.path === '/users' && }
); } return content; } const SidebarLink = ({ icon: Icon, label, active, onClick }) => ( ); // --- PAGES --- // 1. LOGIN PAGE const LoginPage = ({ login, navigate }) => { const [email, setEmail] = useState('ahmad@student.edu.my'); const [password, setPassword] = useState('password'); return (

Welcome Back

Faculty of Engineering Complaint System

{ e.preventDefault(); login(email, password); }} className="space-y-4">
setEmail(e.target.value)} className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none" required />
setPassword(e.target.value)} className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none" required />
Forgot password?

Don't have an account?

{/* Helper for demo */}

Demo Accounts:

User: ahmad@student.edu.my

Tech: kamarul@tech.edu.my

Admin: sarah@admin.edu.my

Super: johan@admin.edu.my

(Password can be anything)

); }; // 2. REGISTER PAGE const RegisterPage = ({ navigate }) => { return (

Create Account

Register as a new student or staff

{ e.preventDefault(); alert('Registration successful! Please login.'); navigate('/login'); }} className="space-y-4">

All new accounts are registered as normal users. Contact an admin if you need staff privileges.

Already have an account?

); }; // 3. USER DASHBOARD const UserDashboard = ({ user, complaints, navigate }) => { const myComplaints = complaints.filter(c => c.reporterId === user.id); const pending = myComplaints.filter(c => c.status === 'Pending').length; const inProgress = myComplaints.filter(c => c.status === 'In Progress').length; const resolved = myComplaints.filter(c => c.status === 'Resolved').length; return (

Welcome, {user.name}

Here is an overview of your submitted complaints.

Recent Complaints

{myComplaints.slice(0, 3).map(complaint => ( navigate('/complaint', { id: complaint.id })} /> ))} {myComplaints.length === 0 && (

You haven't submitted any complaints yet.

)}
); }; const StatCard = ({ title, value, icon: Icon, color }) => (

{title}

{value}

); const ComplaintRow = ({ complaint, onClick }) => (

{complaint.title}

{complaint.id}
{complaint.category} {complaint.location} {new Date(complaint.date).toLocaleDateString()}
); // 4. SUBMIT COMPLAINT PAGE const SubmitComplaint = ({ user, complaints, setComplaints, navigate }) => { const handleSubmit = (e) => { e.preventDefault(); const formData = new FormData(e.target); const newComplaint = { id: `CMP-00${complaints.length + 1}`, title: formData.get('title'), description: formData.get('description'), category: formData.get('category'), location: formData.get('location'), priority: formData.get('priority'), status: 'Pending', reporterId: user.id, date: new Date().toISOString(), assigneeId: null, comments: [] }; setComplaints([newComplaint, ...complaints]); navigate('/dashboard'); }; return (

Submit New Complaint

Please provide details about the issue you are facing.

Upload a file

or drag and drop

PNG, JPG, PDF up to 10MB

); }; // 5. USER COMPLAINT LIST const ComplaintList = ({ user, complaints, navigate }) => { const myComplaints = complaints.filter(c => c.reporterId === user.id); const [searchTerm, setSearchTerm] = useState(''); const [filterStatus, setFilterStatus] = useState('All'); const filtered = myComplaints.filter(c => { const matchSearch = c.title.toLowerCase().includes(searchTerm.toLowerCase()) || c.id.toLowerCase().includes(searchTerm.toLowerCase()); const matchStatus = filterStatus === 'All' || c.status === filterStatus; return matchSearch && matchStatus; }); return (

My Complaints

Track the status of all your submitted issues.

setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg outline-none focus:ring-2 focus:ring-blue-500" />
{filtered.length > 0 ? filtered.map(complaint => ( navigate('/complaint', { id: complaint.id })} /> )) : (
No complaints found matching your criteria.
)}
); }; // 6. COMPLAINT DETAIL PAGE const ComplaintDetail = ({ id, user, complaints, setComplaints, users, navigate }) => { const complaintIndex = complaints.findIndex(c => c.id === id); const complaint = complaints[complaintIndex]; const [commentText, setCommentText] = useState(''); if (!complaint) return
Complaint not found
; const reporter = users.find(u => u.id === complaint.reporterId); const assignee = users.find(u => u.id === complaint.assigneeId); const isAdminOrTech = user.role === 'admin' || user.role === 'super_admin' || user.role === 'technician'; const handleUpdateStatus = (newStatus) => { const updated = [...complaints]; updated[complaintIndex] = { ...complaint, status: newStatus }; setComplaints(updated); }; const handleAssign = (techId) => { const updated = [...complaints]; updated[complaintIndex] = { ...complaint, assigneeId: techId, status: techId ? 'In Progress' : 'Pending' }; setComplaints(updated); }; const handleAddComment = () => { if(!commentText.trim()) return; const newComment = { id: Date.now(), author: user.name, text: commentText, time: new Date().toISOString() }; const updated = [...complaints]; updated[complaintIndex].comments.push(newComment); setComplaints(updated); setCommentText(''); }; return (
{/* Header */}
/ {complaint.id}
{/* Main Content */}

{complaint.title}

{complaint.category} {complaint.location} {new Date(complaint.date).toLocaleString()}

Description

{complaint.description}

{/* Admin/Tech Controls */} {isAdminOrTech && (

Management Actions

{(user.role === 'admin' || user.role === 'super_admin') && (
)}
{['Pending', 'In Progress', 'Resolved'].map(s => ( ))}
)}
{/* Comments Section */}

Discussion

{complaint.comments.length === 0 ? (

No comments yet.

) : ( complaint.comments.map(c => (
{c.author.charAt(0)}
{c.author} {new Date(c.time).toLocaleString()}

{c.text}

)) )}