import React, { useState, useEffect } from "react"; import { cn } from "@/lib/utils"; import { ArrowRight, CheckCircle, Circle, Clock, Filter, MoreHorizontal, Plus, RefreshCw, Search, Pencil, Trash2, Triangle, Eye, XCircle, AlertCircle, FileCheck, User, Calendar } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Card, CardContent, CardHeader, CardTitle, } from "@/components/ui/card"; import { Dropdown, DropdownOption } from "@/components/ui/dropdown"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import Layout from "@/components/layout/layout"; import { issuesApi, projectService, deliverablesApi, employeeService, userSessionService } from "@/lib/api"; import { IssueApi, ProjectApi, Deliverable, Employee } from "@/types"; import { useNavigate } from "react-router-dom"; import { toast } from "@/lib/custom-toast"; import { format } from "date-fns"; import ImportIssuesDialog from "@/components/issue/import-issues-dialog"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; const Issues = () => { const navigate = useNavigate(); const [searchQuery, setSearchQuery] = useState(""); const [projectFilter, setProjectFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all"); const [typeFilter, setTypeFilter] = useState("all"); const [priorityFilter, setPriorityFilter] = useState("all"); const [displayMode, setDisplayMode] = useState<"list" | "board">("list"); const [isLoading, setIsLoading] = useState(true); const [issues, setIssues] = useState([]); const [allIssues, setAllIssues] = useState([]); const [projects, setProjects] = useState([]); const [userProjects, setUserProjects] = useState([]); const [deliverables, setDeliverables] = useState([]); const [employees, setEmployees] = useState([]); const [isDeleting, setIsDeleting] = useState(false); const [deleteId, setDeleteId] = useState(null); // Get current user info const currentUser = userSessionService.getSession(); // Check if user has permission to view all projects // Only CEO, COO, admin, and Administrator roles can see all projects const canViewAllProjects = currentUser?.roleName && ['CEO', 'COO', 'Admin', 'Administrator'].includes(currentUser.roleName); // Check if user has permission to add/edit/delete issues // Only Project Manager and Business Analyst can add, edit or delete const canManageIssues = currentUser?.roleName && ['Project Manager', 'CEO', 'COO', 'Admin', 'Administrator', 'Sr. Project Manager','Business Analyst'].includes(currentUser.roleName); // By default, we only show user's projects // Admin users can toggle to see all projects const [showAllProjects, setShowAllProjects] = useState(false); const statusOptions = ["Open", "In Progress", "In Review", "Completed", "Closed"]; const priorityOptions = ["Low", "Medium", "High", "Critical"]; const typeOptions = ["User Story", "Bug", "Task", "Documentation", "Enhancement"]; // Load saved filters from localStorage when component mounts useEffect(() => { const savedFilters = localStorage.getItem('issuesPageFilters'); if (savedFilters) { try { const filters = JSON.parse(savedFilters); setSearchQuery(filters.searchQuery || ""); setProjectFilter(filters.projectFilter || "all"); setStatusFilter(filters.statusFilter || "all"); setTypeFilter(filters.typeFilter || "all"); setPriorityFilter(filters.priorityFilter || "all"); setDisplayMode(filters.displayMode || "list"); setShowAllProjects(filters.showAllProjects || false); } catch (error) { console.error("Error parsing saved filters:", error); // Clear corrupted filter data localStorage.removeItem('issuesPageFilters'); } } }, []); // Save filters to localStorage whenever they change useEffect(() => { const filtersToSave = { searchQuery, projectFilter, statusFilter, typeFilter, priorityFilter, displayMode, showAllProjects }; localStorage.setItem('issuesPageFilters', JSON.stringify(filtersToSave)); }, [ searchQuery, projectFilter, statusFilter, typeFilter, priorityFilter, displayMode, showAllProjects ]); useEffect(() => { fetchData(); }, [showAllProjects, canViewAllProjects]); // Set default view mode based on screen size useEffect(() => { const handleResize = () => { if (window.innerWidth < 768) { setDisplayMode("board"); } else { setDisplayMode("list"); } }; // Set initial value handleResize(); // Add event listener for window resize window.addEventListener("resize", handleResize); // Clean up event listener return () => window.removeEventListener("resize", handleResize); }, []); const fetchData = async () => { setIsLoading(true); try { // Get all issues first const issuesData = await issuesApi.getAll(); setAllIssues(issuesData); // Get all projects for reference const allProjectsData = await projectService.getAll(); setProjects(allProjectsData); // If user can view all projects and has toggled that option on if (canViewAllProjects && showAllProjects) { setIssues(issuesData); setUserProjects(allProjectsData); } // Otherwise show only user's projects (default for all users) else if (currentUser?.employeeId) { const userProjectsData = await projectService.getByEmployeeId(currentUser.employeeId); setUserProjects(userProjectsData); // Filter issues to only show those from user's projects const filteredIssues = issuesData.filter(issue => userProjectsData.some(project => project.id === issue.projectId) ); setIssues(filteredIssues); } else { // Fallback if somehow no employee ID setIssues(issuesData); setUserProjects(allProjectsData); } const [deliverablesData, employeesData] = await Promise.all([ deliverablesApi.getAll(), employeeService.getAll() ]); setDeliverables(deliverablesData); setEmployees(employeesData); } catch (error) { console.error("Error fetching data:", error); toast.error("Failed to load data. Please try again."); } finally { setIsLoading(false); } }; const filteredIssues = issues.filter((issue) => { const matchesSearch = issue.title?.toLowerCase().includes(searchQuery.toLowerCase()) || issue.description?.toLowerCase().includes(searchQuery.toLowerCase()) || issue.biCode?.toLowerCase().includes(searchQuery.toLowerCase()); const matchesProject = projectFilter === "all" || issue.projectId === projectFilter; const matchesType = typeFilter === "all" || issue.type === typeFilter; const matchesPriority = priorityFilter === "all" || issue.priority === priorityFilter; const matchesStatus = statusFilter === "all" || issue.status === statusFilter; return matchesSearch && matchesProject && matchesType && matchesPriority && matchesStatus; }); const handleDelete = async (id: number) => { if (isDeleting) return; setIsDeleting(true); setDeleteId(id); try { await issuesApi.delete(id); setIssues(issues.filter(issue => issue.id !== id)); toast.success("Features deleted successfully"); } catch (error) { console.error("Error deleting issue:", error); toast.error("Failed to delete issue. Please try again."); } finally { setIsDeleting(false); setDeleteId(null); } }; const getEmployeeName = (id: number) => { const employee = employees.find(emp => emp.id === id); if (!employee) return "Unknown"; return `${employee.firstName} ${employee.lastName}`; }; const getEmployeeInitials = (id: number) => { const employee = employees.find(emp => emp.id === id); if (!employee) return "?"; return `${employee.firstName.charAt(0)}${employee.lastName.charAt(0)}`; }; const getProjectName = (id: number) => { const project = projects.find(proj => proj.id === id); return project ? project.projectName : "Unknown Project"; }; const getDeliverableName = (id: number) => { const deliverable = deliverables.find(del => del.id === id); return deliverable ? deliverable.name : "Unknown Deliverable"; }; const getPriorityBadge = (priority: string) => { let color = ""; let bgColor = ""; let icon = null; switch (priority.toLowerCase()) { case "low": color = "text-blue-700"; bgColor = "bg-blue-50"; icon = ; break; case "medium": color = "text-yellow-700"; bgColor = "bg-yellow-50"; icon = ; break; case "high": color = "text-orange-700"; bgColor = "bg-orange-50"; icon = ; break; case "critical": color = "text-red-700"; bgColor = "bg-red-50"; icon = ; break; default: color = "text-gray-700"; bgColor = "bg-gray-50"; icon = ; } return ( {icon} {priority} ); }; const getTypeBadge = (type: string) => { let color = ""; let bgColor = ""; let icon = null; switch (type.toLowerCase()) { case "bug": color = "text-red-700"; bgColor = "bg-red-50"; icon = ; break; case "feature": case "user story": color = "text-emerald-700"; bgColor = "bg-emerald-50"; icon = ; break; case "task": color = "text-blue-700"; bgColor = "bg-blue-50"; icon = ; break; case "enhancement": color = "text-purple-700"; bgColor = "bg-purple-50"; icon = ; break; case "documentation": color = "text-gray-700"; bgColor = "bg-gray-50"; icon = ; break; default: color = "text-gray-700"; bgColor = "bg-gray-50"; icon = ; } return ( {icon} {type} ); }; const getStatusBadge = (status: string) => { let color = ""; let bgColor = ""; let icon = null; switch (status?.toLowerCase()) { case "open": color = "text-blue-700"; bgColor = "bg-blue-50"; icon = ; break; case "in progress": color = "text-yellow-700"; bgColor = "bg-yellow-50"; icon = ; break; case "in review": color = "text-purple-700"; bgColor = "bg-purple-50"; icon = ; break; case "completed": color = "text-emerald-700"; bgColor = "bg-emerald-50"; icon = ; break; case "closed": color = "text-gray-700"; bgColor = "bg-gray-50"; icon = ; break; default: color = "text-gray-700"; bgColor = "bg-gray-50"; icon = ; } return ( {icon} {status || "Unknown"} ); }; return (

Feature Management

{canManageIssues && ( <> )}
setSearchQuery(e.target.value)} />
{canViewAllProjects && (
)}
({ label: project.projectName, value: project.id.toString() })) ]} value={projectFilter.toString()} onValueChange={(value) => setProjectFilter(value === "all" ? "all" : parseInt(value))} placeholder="Project" className="w-full" /> ({ label: type, value: type })) ]} value={typeFilter.toString()} onValueChange={(value) => setTypeFilter(value)} placeholder="Type" className="w-full" /> ({ label: priority, value: priority })) ]} value={priorityFilter.toString()} onValueChange={(value) => setPriorityFilter(value)} placeholder="Priority" className="w-full" /> ({ label: status, value: status })) ]} value={statusFilter.toString()} onValueChange={(value) => setStatusFilter(value)} placeholder="Status" className="w-full" />
{isLoading ? (

Loading Features...

) : filteredIssues.length === 0 ? (

No Features found

We couldn't find any Features matching your current filters or search criteria.

) : displayMode === "list" ? ( Features ({filteredIssues.length}) {canViewAllProjects && showAllProjects ? "Showing all Features" : "Showing only your project Features"} ({filteredIssues.length} of {allIssues.length})
{filteredIssues.map((issue, index) => ( navigate(`/issues/${issue.id}`)} > ))}
# Title Project Type Priority Status Assigned To Start Date End Date Actions
{index + 1}
{issue.title}
{issue.description}
{getProjectName(issue.projectId)} {getDeliverableName(issue.deliverablesId)}
{getTypeBadge(issue.type)} {getPriorityBadge(issue.priority)} {getStatusBadge(issue.status)}
{getEmployeeInitials(issue.assignedTo)} {getEmployeeName(issue.assignedTo)}
{issue.startDate ? format(new Date(issue.startDate), "MMM d, yyyy") : "-"} {issue.endDate ? format(new Date(issue.endDate), "MMM d, yyyy") : "-"}
e.stopPropagation()}> {canManageIssues ? ( <> ) : ( No actions )}
) : (
{filteredIssues.map((issue) => ( navigate(`/issues/${issue.id}`)} >
{getTypeBadge(issue.type)} {getPriorityBadge(issue.priority)}
{issue.title}
#{issue.id} {getProjectName(issue.projectId)}
e.stopPropagation()}> { e.stopPropagation(); navigate(`/issues/${issue.id}`); }} > View Details {canManageIssues && ( <> { e.stopPropagation(); navigate(`/issues/edit/${issue.id}`); }} > Edit { e.stopPropagation(); handleDelete(issue.id); }} disabled={isDeleting && deleteId === issue.id} > {isDeleting && deleteId === issue.id ? "Deleting..." : "Delete"} )}
{getStatusBadge(issue.status)}

{issue.description || "No description provided"}

{(issue.startDate || issue.endDate) && (
{issue.startDate && (
Start: {format(new Date(issue.startDate), "MMM d, yyyy")}
)} {issue.endDate && (
End: {format(new Date(issue.endDate), "MMM d, yyyy")}
)}
)}
{getEmployeeInitials(issue.assignedTo)}
Assigned to
{getEmployeeName(issue.assignedTo)}
Reported by
{getEmployeeName(issue.reportedBy)}
))}
)}
); }; export default Issues;