diff --git a/apps/console/src/pages/ControlOverviewPage.tsx b/apps/console/src/pages/ControlOverviewPage.tsx
index 9aa120938..26a199893 100644
--- a/apps/console/src/pages/ControlOverviewPage.tsx
+++ b/apps/console/src/pages/ControlOverviewPage.tsx
@@ -56,6 +56,43 @@ import type { ControlOverviewPageUploadEvidenceMutation as ControlOverviewPageUp
import type { ControlOverviewPageDeleteEvidenceMutation as ControlOverviewPageDeleteEvidenceMutationType } from "./__generated__/ControlOverviewPageDeleteEvidenceMutation.graphql";
import { Textarea } from "@/components/ui/textarea";
+// Function to format ISO8601 duration to human-readable format
+const formatDuration = (isoDuration: string): string => {
+ if (!isoDuration || !isoDuration.startsWith("P")) {
+ return isoDuration;
+ }
+
+ try {
+ const durationRegex =
+ /P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?/;
+ const matches = isoDuration.match(durationRegex);
+
+ if (!matches) return isoDuration;
+
+ const years = matches[1] ? parseInt(matches[1]) : 0;
+ const months = matches[2] ? parseInt(matches[2]) : 0;
+ const days = matches[3] ? parseInt(matches[3]) : 0;
+ const hours = matches[4] ? parseInt(matches[4]) : 0;
+ const minutes = matches[5] ? parseInt(matches[5]) : 0;
+ const seconds = matches[6] ? parseInt(matches[6]) : 0;
+
+ const parts = [];
+ if (years) parts.push(`${years} ${years === 1 ? "year" : "years"}`);
+ if (months) parts.push(`${months} ${months === 1 ? "month" : "months"}`);
+ if (days) parts.push(`${days} ${days === 1 ? "day" : "days"}`);
+ if (hours) parts.push(`${hours} ${hours === 1 ? "hour" : "hours"}`);
+ if (minutes)
+ parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`);
+ if (seconds)
+ parts.push(`${seconds} ${seconds === 1 ? "second" : "seconds"}`);
+
+ return parts.length > 0 ? parts.join(", ") : "No duration";
+ } catch (error) {
+ console.error("Error parsing duration:", error);
+ return isoDuration;
+ }
+};
+
const controlOverviewPageQuery = graphql`
query ControlOverviewPageQuery($controlId: ID!) {
control: node(id: $controlId) {
@@ -74,6 +111,7 @@ const controlOverviewPageQuery = graphql`
name
description
state
+ timeEstimate
version
evidences(first: 50)
@connection(key: "ControlOverviewPage_evidences") {
@@ -122,6 +160,7 @@ const createTaskMutation = graphql`
id
name
description
+ timeEstimate
state
}
}
@@ -269,6 +308,9 @@ function ControlOverviewPageContent({
const [isCreateTaskOpen, setIsCreateTaskOpen] = useState(false);
const [newTaskName, setNewTaskName] = useState("");
const [newTaskDescription, setNewTaskDescription] = useState("");
+ const [timeEstimateDays, setTimeEstimateDays] = useState("");
+ const [timeEstimateHours, setTimeEstimateHours] = useState("");
+ const [timeEstimateMinutes, setTimeEstimateMinutes] = useState("");
const [isDeleteTaskOpen, setIsDeleteTaskOpen] = useState(false);
const [taskToDelete, setTaskToDelete] = useState<{
@@ -325,6 +367,33 @@ function ControlOverviewPageContent({
[tasks]
);
+ // Function to convert days, hours, and minutes to ISO 8601 duration format
+ const convertToISODuration = useCallback(() => {
+ let duration = "P";
+
+ if (timeEstimateDays && parseInt(timeEstimateDays) > 0) {
+ duration += `${parseInt(timeEstimateDays)}D`;
+ }
+
+ if (
+ (timeEstimateHours && parseInt(timeEstimateHours) > 0) ||
+ (timeEstimateMinutes && parseInt(timeEstimateMinutes) > 0)
+ ) {
+ duration += "T";
+
+ if (timeEstimateHours && parseInt(timeEstimateHours) > 0) {
+ duration += `${parseInt(timeEstimateHours)}H`;
+ }
+
+ if (timeEstimateMinutes && parseInt(timeEstimateMinutes) > 0) {
+ duration += `${parseInt(timeEstimateMinutes)}M`;
+ }
+ }
+
+ // Return empty string if no time components were provided
+ return duration === "P" ? "" : duration;
+ }, [timeEstimateDays, timeEstimateHours, timeEstimateMinutes]);
+
useEffect(() => {
const handleDragEnter = (e: globalThis.DragEvent) => {
e.preventDefault();
@@ -415,6 +484,9 @@ function ControlOverviewPageContent({
return;
}
+ // Convert the time estimate components to ISO 8601 format
+ const isoTimeEstimate = convertToISODuration();
+
createTask({
variables: {
connections: [`${data.control.tasks?.__id}`],
@@ -422,6 +494,7 @@ function ControlOverviewPageContent({
controlId: data.control.id,
name: newTaskName,
description: newTaskDescription,
+ timeEstimate: isoTimeEstimate,
},
},
onCompleted: () => {
@@ -431,6 +504,9 @@ function ControlOverviewPageContent({
});
setNewTaskName("");
setNewTaskDescription("");
+ setTimeEstimateDays("");
+ setTimeEstimateHours("");
+ setTimeEstimateMinutes("");
setIsCreateTaskOpen(false);
},
onError: (error) => {
@@ -804,6 +880,72 @@ function ControlOverviewPageContent({
placeholder="Enter task description"
/>
+
+
+
+