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
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
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 }) => (
);
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.
);
};
// 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.
{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}
))
)}
{/* Sidebar Info */}
Status
Reporter
{reporter?.name || 'Unknown'}
{reporter?.email}
Assignee
{assignee ? (
{assignee.name}
Technician
) : (
Unassigned
)}
);
};
// 7. TECHNICIAN DASHBOARD
const TechnicianDashboard = ({ user, complaints, navigate }) => {
const assignedTasks = complaints.filter(c => c.assigneeId === user.id);
const pending = assignedTasks.filter(c => c.status !== 'Resolved');
const completed = assignedTasks.filter(c => c.status === 'Resolved');
return (
Technician Workspace
Manage your assigned maintenance tasks.
Your Active Tasks
{pending.length > 0 ? pending.map(task => (
{task.location}
{task.category}
{new Date(task.date).toLocaleDateString()}
)) : (
You have no active assigned tasks.
)}
);
};
// 8 & 9. ADMIN DASHBOARD & COMPLAINT MANAGEMENT
const AdminDashboard = ({ complaints, navigate }) => {
const total = complaints.length;
const resolved = complaints.filter(c => c.status === 'Resolved').length;
const pending = complaints.filter(c => c.status === 'Pending').length;
const resolutionRate = total > 0 ? Math.round((resolved / total) * 100) : 0;
return (
Admin Dashboard
System overview and key performance indicators.
Recent Submissions
{complaints.slice(0, 4).map(c => (
navigate('/complaint', {id: c.id})}>
{c.title}
{c.id} • {c.category}
))}
{/* Simple inline chart for Dashboard */}
Complaints by Category
);
};
const AdminComplaintManagement = ({ complaints, users, navigate }) => {
const [filter, setFilter] = useState('All');
const filtered = filter === 'All' ? complaints : complaints.filter(c => c.status === filter);
return (
Complaint Management
View, assign, and manage all system complaints.
{['All', 'Pending', 'In Progress', 'Resolved'].map(f => (
))}
| ID / Title |
Reporter |
Status |
Priority |
Assignee |
Action |
{filtered.map(c => {
const reporter = users.find(u => u.id === c.reporterId);
const assignee = users.find(u => u.id === c.assigneeId);
return (
|
{c.title}
{c.id} • {c.category}
|
{reporter?.name || 'Unknown'} |
|
|
{assignee ? (
{assignee.name.split(' ')[1] || assignee.name}
) : (
Unassigned
)}
|
|
);
})}
);
};
// 11. SUPER ADMIN USER MANAGEMENT
const UserManagement = ({ users, setUsers }) => {
const updateRole = (id, newRole) => {
setUsers(users.map(u => u.id === id ? { ...u, role: newRole } : u));
};
return (
User Management
Super Admin access: Manage system roles and permissions.
| User |
Contact |
Role |
Status |
{users.map(user => (
|
|
{user.email} |
{user.role === 'super_admin' ? (
Super Admin
) : (
)}
|
Active |
))}
);
};
// 12. REPORT & ANALYTICS PAGE
const ReportPage = ({ complaints }) => {
// Simple aggregations
const total = complaints.length;
const resolved = complaints.filter(c => c.status === 'Resolved').length;
const resolutionRate = total > 0 ? Math.round((resolved / total) * 100) : 0;
// Category counts
const categoryCounts = MOCK_CATEGORIES.reduce((acc, cat) => {
acc[cat] = complaints.filter(c => c.category === cat).length;
return acc;
}, {});
// For the CSS Bar Chart (Mock Monthly Trend Data)
const barData = [35, 45, 30, 60, 50, 80, 65, 40, 55, 70, 90, 85]; // Mock percentages for height
return (
Analytics & Reports
Detailed KPIs and system performance metrics.
Total Complaints YTD
{total * 24}
+12% from last year
Overall Resolution Rate
{resolutionRate}%
↑ 5% improvement
Avg. Resolution Time
1.8d
↓ 0.4d faster
Current Backlog
{complaints.filter(c=>c.status!=='Resolved').length}
Active pending items
{/* CSS Bar Chart */}
Monthly Complaint Trend
2026
{/* Y-axis labels */}
100
50
0
{['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'].map((month, i) => (
))}
Distribution by Category
{MOCK_CATEGORIES.map((cat, i) => {
const colors = ['bg-blue-500', 'bg-emerald-500', 'bg-amber-500', 'bg-violet-500', 'bg-slate-300'];
// Calculate mock percentage based on index for visual rep
const pct = [35, 25, 20, 15, 5][i];
return (
)
})}
);
};