Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Clients/src/application/hooks/useFrameworks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState, useEffect, useCallback } from "react";
import { Framework } from "../../domain/types/Framework";
import { getAllFrameworks } from "../repository/entity.repository";

interface UseFrameworksResult {
frameworks: Framework[];
loading: boolean;
error: string | null;
refreshFrameworks: () => Promise<void>;
}

const useFrameworks = (): UseFrameworksResult => {
const [frameworks, setFrameworks] = useState<Framework[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

const fetchFrameworks = useCallback(async () => {
try {
setLoading(true);
const response = await getAllFrameworks({ routeUrl: "/frameworks" });
if (response?.data) {
setFrameworks(response.data);
setError(null);
} else {
throw new Error("Invalid response format");
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to fetch frameworks";
setError(errorMessage);
console.error("Error fetching frameworks:", errorMessage);
} finally {
setLoading(false);
}
}, []);

useEffect(() => {
fetchFrameworks();
}, [fetchFrameworks]);

return {
frameworks,
loading,
error,
refreshFrameworks: fetchFrameworks
};
};

export default useFrameworks;
9 changes: 9 additions & 0 deletions Clients/src/application/repository/entity.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,13 @@ export async function generateReport({
} catch (error) {
throw error;
}
}

export async function getAllFrameworks({
authToken = getAuthToken(),
}: RequestParams): Promise<any> {
const response = await apiServices.get("/frameworks", {
headers: { Authorization: `Bearer ${authToken}` },
});
return response.data;
}
Comment on lines +290 to 297
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Missing error handling in getAllFrameworks function.

The function doesn't have any error handling, unlike other similar functions in this file that use try/catch blocks to log errors and rethrow them.

Add error handling for consistency with other repository functions:

export async function getAllFrameworks({
  authToken = getAuthToken(),
}: RequestParams): Promise<any> {
+  try {
    const response = await apiServices.get("/frameworks", {
      headers: { Authorization: `Bearer ${authToken}` },
    });
    return response.data;
+  } catch (error) {
+    console.error("Error fetching frameworks:", error);
+    throw error;
+  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function getAllFrameworks({
authToken = getAuthToken(),
}: RequestParams): Promise<any> {
const response = await apiServices.get("/frameworks", {
headers: { Authorization: `Bearer ${authToken}` },
});
return response.data;
}
export async function getAllFrameworks({
authToken = getAuthToken(),
}: RequestParams): Promise<any> {
try {
const response = await apiServices.get("/frameworks", {
headers: { Authorization: `Bearer ${authToken}` },
});
return response.data;
} catch (error) {
console.error("Error fetching frameworks:", error);
throw error;
}
}

9 changes: 6 additions & 3 deletions Clients/src/domain/types/Framework.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export type Framework = {
id: string;
is_demo: boolean;
project_id: string;
id: number;
name: string;
description: string;
created_at: string;
is_demo?: boolean;
project_id?: string;
};
189 changes: 113 additions & 76 deletions Clients/src/presentation/pages/ProjectView/ProjectFrameworks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useState } from "react";
import { Box, Button, Tab } from "@mui/material";
import TabList from "@mui/lab/TabList";
import TabPanel from "@mui/lab/TabPanel";
import TabContext from "@mui/lab/TabContext";
import { tabStyle, tabPanelStyle } from "../V1.0ProjectView/style";
import VWSkeleton from "../../../vw-v2-components/Skeletons";
import ComplianceTracker from "../../../pages/ComplianceTracker/1.0ComplianceTracker";
import { Project } from "../../../../domain/types/Project";
import AssessmentTracker from "../../Assessment/1.0AssessmentTracker";

import { useState, useEffect, useMemo } from 'react';
import { Box, Button, Tab, Typography, Stack, Alert } from '@mui/material';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';
import TabContext from '@mui/lab/TabContext';
import { tabStyle, tabPanelStyle } from '../V1.0ProjectView/style';
import VWSkeleton from '../../../vw-v2-components/Skeletons';
import ComplianceTracker from '../../../pages/ComplianceTracker/1.0ComplianceTracker';
import { Project } from '../../../../domain/types/Project';
import AssessmentTracker from '../../Assessment/1.0AssessmentTracker';
import useFrameworks from '../../../../application/hooks/useFrameworks';
import {
containerStyle,
headerContainerStyle,
Expand All @@ -20,51 +20,117 @@ import {
import ISO42001Annex from "../../ISO/Annex";
import ISO42001Clauses from "../../ISO/Clause";
Comment on lines 20 to 21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused imports

The ISO42001Annex import is declared but never used, as indicated by the pipeline failure. Remove this unused import and verify if ISO42001Clauses is also unused.

-import ISO42001Annex from "../../ISO/Annex";
-import ISO42001Clauses from "../../ISO/Clause";

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: Frontend Checks

[error] 20-20: TypeScript error TS6133: 'ISO42001Annex' is declared but its value is never read.


const frameworks = [
{ label: "EU AI Act", value: "eu-ai-act" },
{ label: "ISO 42001", value: "iso-42001" },
];

const trackerTabs = [
{ label: "Compliance tracker", value: "compliance" },
{ label: "Assessment tracker", value: "assessment" },
];

const iso42001Tabs = [
{ label: "Clauses", value: "clauses" },
{ label: "Annexs", value: "annexs" },
];
const ComingSoonMessage = () => (
<Stack
sx={{
height: 400,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#F5F6F6',
borderRadius: 2,
p: 4
}}
>
<Typography variant="h6" sx={{ color: '#13715B', mb: 2 }}>
Coming Soon!
</Typography>
<Typography sx={{ color: '#232B3A', textAlign: 'center' }}>
We're currently working on implementing this framework.
<br />
Please check back later for updates.
</Typography>
</Stack>
);

const ProjectFrameworks = ({ project }: { project: Project }) => {
const [framework, setFramework] = useState("eu-ai-act");
const [tracker, setTracker] = useState("compliance");
const [isoTab, setIsoTab] = useState("clauses");
const { frameworks, loading, error, refreshFrameworks } = useFrameworks();
const [selectedFrameworkId, setSelectedFrameworkId] = useState<number | null>(null);
const [tracker, setTracker] = useState('compliance');

// Set initial framework when frameworks are loaded
useEffect(() => {
if (frameworks.length > 0 && !selectedFrameworkId) {
setSelectedFrameworkId(frameworks[0].id);
}
}, [frameworks, selectedFrameworkId]);

Comment on lines +56 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle framework list changes & invalid selections

useEffect only sets the initial framework when nothing is selected.
If the frameworks list refreshes (e.g. after refreshFrameworks) and the
previously-selected framework is no longer present, selectedFrameworkId
will keep pointing to a non-existing entry, leaving the UI in an
inconsistent state.

-useEffect(() => {
-  if (frameworks.length > 0 && !selectedFrameworkId) {
-    setSelectedFrameworkId(frameworks[0].id);
-  }
-}, [frameworks, selectedFrameworkId]);
+useEffect(() => {
+  if (frameworks.length === 0) return;
+
+  // If nothing is selected **or** the selected framework disappeared,
+  // default to the first framework.
+  const frameworkExists = frameworks.some(fw => fw.id === selectedFrameworkId);
+  if (selectedFrameworkId === null || !frameworkExists) {
+    setSelectedFrameworkId(frameworks[0].id);
+  }
+}, [frameworks, selectedFrameworkId]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Set initial framework when frameworks are loaded
useEffect(() => {
if (frameworks.length > 0 && !selectedFrameworkId) {
setSelectedFrameworkId(frameworks[0].id);
}
}, [frameworks, selectedFrameworkId]);
// Set initial framework when frameworks are loaded
useEffect(() => {
if (frameworks.length === 0) return;
// If nothing is selected **or** the selected framework disappeared,
// default to the first framework.
const frameworkExists = frameworks.some(fw => fw.id === selectedFrameworkId);
if (selectedFrameworkId === null || !frameworkExists) {
setSelectedFrameworkId(frameworks[0].id);
}
}, [frameworks, selectedFrameworkId]);

// Memoize the current framework to avoid unnecessary re-renders
const currentFramework = useMemo(() =>
frameworks.find(fw => fw.id === selectedFrameworkId),
[frameworks, selectedFrameworkId]
);

const handleFrameworkChange = (frameworkId: number) => {
setSelectedFrameworkId(frameworkId);
};

const renderFrameworkContent = () => {
if (!project) {
return <VWSkeleton variant="rectangular" width="100%" height={400} />;
}

const currentTabs = framework === "iso-42001" ? iso42001Tabs : trackerTabs;
const currentValue = framework === "iso-42001" ? isoTab : tracker;
const setCurrentValue = framework === "iso-42001" ? setIsoTab : setTracker;
if (!currentFramework) {
return <ComingSoonMessage />;
}

const isEUAIAct = currentFramework.id === 1; // EU AI Act framework ID

return (
<>
{isEUAIAct ? (
tracker === 'compliance' ? (
<ComplianceTracker project={project} />
) : (
<AssessmentTracker project={project} />
)
) : (
<ComingSoonMessage />
)}
</>
);
};

if (error) {
return (
<Box sx={containerStyle}>
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
<Button onClick={refreshFrameworks} variant="contained">
Retry
</Button>
</Box>
);
}

return (
<Box sx={containerStyle}>
{/* Framework Tabs and Add Button */}
<Box sx={headerContainerStyle}>
{/* Framework Tabs as classic tabs, not buttons */}
<Box sx={frameworkTabsContainerStyle}>
{frameworks.map((fw, idx) => {
const isActive = framework === fw.value;
return (
<Box
key={fw.value}
onClick={() => setFramework(fw.value)}
sx={getFrameworkTabStyle(
isActive,
idx === frameworks.length - 1
)}
>
{fw.label}
</Box>
);
})}
{loading ? (
<VWSkeleton variant="rectangular" width={200} height={40} />
) : (
frameworks.map((fw, idx) => {
const isActive = selectedFrameworkId === fw.id;
return (
<Box
key={fw.id}
onClick={() => handleFrameworkChange(fw.id)}
sx={getFrameworkTabStyle(isActive, idx === frameworks.length - 1)}
>
{fw.name}
</Box>
);
})
)}
</Box>
<Button variant="contained" sx={addButtonStyle}>
Add new framework
Expand All @@ -90,41 +156,12 @@ const ProjectFrameworks = ({ project }: { project: Project }) => {
))}
</TabList>
</Box>
{framework === "iso-42001" ? (
<>
<TabPanel value="clauses" sx={tabPanelStyle}>
{project ? (
<ISO42001Clauses />
) : (
<VWSkeleton variant="rectangular" width="100%" height={400} />
)}
</TabPanel>
<TabPanel value="annexs" sx={tabPanelStyle}>
{project ? (
<ISO42001Annex />
) : (
<VWSkeleton variant="rectangular" width="100%" height={400} />
)}
</TabPanel>
</>
) : (
<>
<TabPanel value="compliance" sx={tabPanelStyle}>
{project ? (
<ComplianceTracker project={project} />
) : (
<VWSkeleton variant="rectangular" width="100%" height={400} />
)}
</TabPanel>
<TabPanel value="assessment" sx={tabPanelStyle}>
{project ? (
<AssessmentTracker project={project} />
) : (
<VWSkeleton variant="rectangular" width="100%" height={400} />
)}
</TabPanel>
</>
)}
<TabPanel value="compliance" sx={tabPanelStyle}>
{renderFrameworkContent()}
</TabPanel>
<TabPanel value="assessment" sx={tabPanelStyle}>
{renderFrameworkContent()}
</TabPanel>
</TabContext>
</Box>
);
Expand Down
Loading