diff --git a/ui/src/pages/ViewMatrix.tsx b/ui/src/pages/ViewMatrix.tsx index 704a719..2e3aaf8 100644 --- a/ui/src/pages/ViewMatrix.tsx +++ b/ui/src/pages/ViewMatrix.tsx @@ -18,11 +18,16 @@ import { import { useNavigate } from "react-router-dom"; import axios from "axios"; import { ROUTES } from "../helpers/helpers"; -import SearchIcon from '@mui/icons-material/Search'; -import AddIcon from '@mui/icons-material/Add'; -import FilterListIcon from '@mui/icons-material/FilterList'; -import OpenInNewIcon from '@mui/icons-material/OpenInNew'; -import { ExportPdfButton } from '../components/custom/pdfExport/ExportPdfButton'; +import SearchIcon from "@mui/icons-material/Search"; +import AddIcon from "@mui/icons-material/Add"; +import FilterListIcon from "@mui/icons-material/FilterList"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; +import DeleteIcon from "@mui/icons-material/Delete"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; +import { ExportPdfButton } from "../components/custom/pdfExport/ExportPdfButton"; interface CarverMatrix { matrixId: number; @@ -61,19 +66,27 @@ const ViewMatrix: React.FC = () => { participant: false, both: false, }); - const [roleBasedFilter, setRoleBasedFilter] = useState<'all' | 'enabled' | 'disabled'>('all'); + const [roleBasedFilter, setRoleBasedFilter] = useState< + "all" | "enabled" | "disabled" + >("all"); const [userEmail, setUserEmail] = useState(null); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [matrixToDelete, setMatrixToDelete] = useState( + null + ); useEffect(() => { const fetchUserEmail = async () => { try { - const response = await axios.get('/api/user2/whoami-upsert', { withCredentials: true }); - if (response.data.includes('{')) { - const userData = JSON.parse(response.data.split('}{')[0] + '}'); + const response = await axios.get("/api/user2/whoami-upsert", { + withCredentials: true, + }); + if (response.data.includes("{")) { + const userData = JSON.parse(response.data.split("}{")[0] + "}"); setUserEmail(userData.email); } } catch (error) { - console.error('Error fetching user email:', error); + console.error("Error fetching user email:", error); } }; @@ -97,9 +110,9 @@ const ViewMatrix: React.FC = () => { }, []); const handleRoleFilterChange = (role: keyof typeof roleFilters) => { - setRoleFilters(prev => ({ + setRoleFilters((prev) => ({ ...prev, - [role]: !prev[role] + [role]: !prev[role], })); }; @@ -107,15 +120,16 @@ const ViewMatrix: React.FC = () => { const filteredMatrices = matrices.filter((matrix) => { // Text search filter const term = searchTerm.toLowerCase(); - const matchesSearch = matrix.name.toLowerCase().includes(term) || + const matchesSearch = + matrix.name.toLowerCase().includes(term) || matrix.description.toLowerCase().includes(term); // Role-based matrix filter - if (roleBasedFilter !== 'all') { - if (roleBasedFilter === 'enabled' && !matrix.roleBased) { + if (roleBasedFilter !== "all") { + if (roleBasedFilter === "enabled" && !matrix.roleBased) { return false; } - if (roleBasedFilter === 'disabled' && matrix.roleBased) { + if (roleBasedFilter === "disabled" && matrix.roleBased) { return false; } } @@ -132,7 +146,7 @@ const ViewMatrix: React.FC = () => { // Role-based filtering if (!userEmail) { - console.log('No user email available'); + console.log("No user email available"); return false; } @@ -156,26 +170,30 @@ const ViewMatrix: React.FC = () => { } } - console.log('Matrix:', matrix.name); - console.log('Current user email:', userEmail); - console.log('Hosts:', matrix.hosts); - console.log('Is host:', isHost); - console.log('Is participant:', isParticipant); - console.log('Is both:', isBoth); - console.log('Role filters:', roleFilters); - console.log('Matches role:', matchesRole); - console.log('Matches search:', matchesSearch); - console.log('Final result:', matchesSearch && matchesRole); + console.log("Matrix:", matrix.name); + console.log("Current user email:", userEmail); + console.log("Hosts:", matrix.hosts); + console.log("Is host:", isHost); + console.log("Is participant:", isParticipant); + console.log("Is both:", isBoth); + console.log("Role filters:", roleFilters); + console.log("Matches role:", matchesRole); + console.log("Matches search:", matchesSearch); + console.log("Final result:", matchesSearch && matchesRole); return matchesSearch && matchesRole; }); - const transformItemsForPdf = (items: CarverMatrix['items']) => { - return items.map(item => { + const transformItemsForPdf = (items: CarverMatrix["items"]) => { + return items.map((item) => { const getAverageScore = (scores: any) => { - if (typeof scores === 'number') return scores; - const values = Object.values(scores || {}).filter(score => score !== undefined && score !== null) as number[]; - return values.length > 0 ? values.reduce((sum, score) => sum + score, 0) / values.length : 0; + if (typeof scores === "number") return scores; + const values = Object.values(scores || {}).filter( + (score) => score !== undefined && score !== null + ) as number[]; + return values.length > 0 + ? values.reduce((sum, score) => sum + score, 0) / values.length + : 0; }; return { @@ -185,13 +203,39 @@ const ViewMatrix: React.FC = () => { recoverability: { default: getAverageScore(item.recoverability) }, vulnerability: { default: getAverageScore(item.vulnerability) }, effect: { default: getAverageScore(item.effect) }, - recognizability: { default: getAverageScore(item.recognizability) } + recognizability: { default: getAverageScore(item.recognizability) }, }; }); }; + const handleOpenDeleteDialog = (matrix: CarverMatrix) => { + setMatrixToDelete(matrix); + setDeleteDialogOpen(true); + }; + + const handleCloseDeleteDialog = () => { + setDeleteDialogOpen(false); + setMatrixToDelete(null); + }; + + const handleConfirmDelete = async () => { + if (!matrixToDelete) return; + + try { + await axios.delete(`/api/carvermatrices/${matrixToDelete.matrixId}`, { + withCredentials: true, + }); + setMatrices((prev) => + prev.filter((m) => m.matrixId !== matrixToDelete.matrixId) + ); + handleCloseDeleteDialog(); + } catch (error) { + console.error("Failed to delete matrix:", error); + } + }; + return ( - { left: 0, right: 0, bottom: 0, - backgroundImage: "linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('/military-pattern.svg')", + backgroundImage: + "linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('/military-pattern.svg')", backgroundSize: "100px 100px", backgroundPosition: "center", opacity: 0.1, @@ -256,33 +301,35 @@ const ViewMatrix: React.FC = () => { InputProps={{ startAdornment: ( - + ), }} sx={{ - '& .MuiOutlinedInput-root': { - color: '#ffffff', - '& fieldset': { - borderColor: 'rgba(255, 255, 255, 0.23)', + "& .MuiOutlinedInput-root": { + color: "#ffffff", + "& fieldset": { + borderColor: "rgba(255, 255, 255, 0.23)", }, - '&:hover fieldset': { - borderColor: '#014093', + "&:hover fieldset": { + borderColor: "#014093", }, - '&.Mui-focused fieldset': { - borderColor: '#014093', + "&.Mui-focused fieldset": { + borderColor: "#014093", }, }, }} /> - - + + { Filter by Role - + handleRoleFilterChange('host')} + onChange={() => handleRoleFilterChange("host")} sx={{ - color: 'rgba(255, 255, 255, 0.7)', - '&.Mui-checked': { - color: '#014093', + color: "rgba(255, 255, 255, 0.7)", + "&.Mui-checked": { + color: "#014093", }, }} /> } label={ - Host + Host } /> handleRoleFilterChange('participant')} + onChange={() => handleRoleFilterChange("participant")} sx={{ - color: 'rgba(255, 255, 255, 0.7)', - '&.Mui-checked': { - color: '#014093', + color: "rgba(255, 255, 255, 0.7)", + "&.Mui-checked": { + color: "#014093", }, }} /> } label={ - Participant + + Participant + } /> handleRoleFilterChange('both')} + onChange={() => handleRoleFilterChange("both")} sx={{ - color: 'rgba(255, 255, 255, 0.7)', - '&.Mui-checked': { - color: '#014093', + color: "rgba(255, 255, 255, 0.7)", + "&.Mui-checked": { + color: "#014093", }, }} /> } label={ - Host & Participant + + Host & Participant + } /> @@ -349,12 +400,14 @@ const ViewMatrix: React.FC = () => { - - + + { Role-Based Matrix Filter - + setRoleBasedFilter(e.target.value as 'all' | 'enabled' | 'disabled')} + onChange={(e) => + setRoleBasedFilter( + e.target.value as "all" | "enabled" | "disabled" + ) + } sx={{ - '& .MuiRadio-root': { - color: 'rgba(255, 255, 255, 0.7)', - '&.Mui-checked': { - color: '#014093', + "& .MuiRadio-root": { + color: "rgba(255, 255, 255, 0.7)", + "&.Mui-checked": { + color: "#014093", }, }, }} @@ -380,21 +437,27 @@ const ViewMatrix: React.FC = () => { value="all" control={} label={ - All Matrices + + All Matrices + } /> } label={ - Role-Based Only + + Role-Based Only + } /> } label={ - Non-Role-Based Only + + Non-Role-Based Only + } /> @@ -402,21 +465,23 @@ const ViewMatrix: React.FC = () => { - + + + + ); };