diff --git a/apps/console/src/App.tsx b/apps/console/src/App.tsx index 06a045f81..a4fd3a09e 100644 --- a/apps/console/src/App.tsx +++ b/apps/console/src/App.tsx @@ -24,7 +24,7 @@ posthog.init(process.env.POSTHOG_KEY!, { }); const OrganizationSelectionPage = lazy( - () => import("./pages/OrganizationSelectionPage"), + () => import("./pages/OrganizationSelectionPage") ); const HomePage = lazy(() => import("./pages/HomePage")); const NotFoundPage = lazy(() => import("./pages/NotFoundPage")); @@ -35,18 +35,19 @@ const VendorOverviewPage = lazy(() => import("./pages/VendorOverviewPage")); const SettingsPage = lazy(() => import("./pages/SettingsPage")); const CreatePeoplePage = lazy(() => import("./pages/CreatePeoplePage")); const FrameworkOverviewPage = lazy( - () => import("./pages/FrameworkOverviewPage"), + () => import("./pages/FrameworkOverviewPage") ); const ControlOverviewPage = lazy(() => import("./pages/ControlOverviewPage")); const PeopleOverviewPage = lazy(() => import("./pages/PeopleOverviewPage")); const LoginPage = lazy(() => import("./pages/LoginPage")); const RegisterPage = lazy(() => import("./pages/RegisterPage")); const CreateOrganizationPage = lazy( - () => import("./pages/CreateOrganizationPage"), + () => import("./pages/CreateOrganizationPage") ); const CreateFrameworkPage = lazy(() => import("./pages/CreateFrameworkPage")); const CreateControlPage = lazy(() => import("./pages/CreateControlPage")); const UpdateFrameworkPage = lazy(() => import("./pages/UpdateFrameworkPage")); +const UpdateControlPage = lazy(() => import("./pages/UpdateControlPage")); function App() { return ( @@ -261,6 +262,16 @@ function App() { } /> + + + + + + } + /> ( controlOverviewPageQuery, - queryRef, + queryRef ); const { toast } = useToast(); + const { organizationId, frameworkId, controlId } = useParams(); + const navigate = useNavigate(); const [updateTaskState] = useMutation( - updateTaskStateMutation, + updateTaskStateMutation ); const [createTask] = useMutation(createTaskMutation); @@ -244,201 +246,222 @@ function ControlOverviewPageContent({ }); }; + const handleEditControl = () => { + navigate( + `/organizations/${organizationId}/frameworks/${frameworkId}/controls/${controlId}/update` + ); + }; + return ( -
-
-
-

{control?.name}

-
-
- 30 min -
-
- Mandatory -
-
- Assigned to you -
-
-
-

{control?.description}

-
- - - -
-
-
-
- - {control?.state === "IMPLEMENTED" ? "Validated" : "Not validated"} - -
- - - -
-
-

Tasks

- - - - - - - Create New Task - - Add a new task to this control. Click save when you're - done. - - -
-
- - setNewTaskName(e.target.value)} - placeholder="Enter task name" - /> -
-
- - setNewTaskDescription(e.target.value)} - placeholder="Enter task description" - /> -
+
+ 30 min
- - - - - -
+
+ Mandatory +
+
+ Assigned to you +
+
+
+

{control?.description}

-
- {tasks.map((task) => ( -
+ + + +
- task?.id && - task?.state && - handleTaskClick(task.id, task.state) - } + className={`w-4 h-4 rounded-full bg-white flex items-center justify-center border border-gray-200`} > - {task?.state === "DONE" && ( - - )} +
-
- task?.id && - task?.state && - handleTaskClick(task.id, task.state) - } - > -
-

- {task?.name} -

- {task?.description && ( -

+ {control?.state === "IMPLEMENTED" + ? "Validated" + : "Not validated"} + +

+ + + +
+
+

Tasks

+ + + + + + + Create New Task + + Add a new task to this control. Click save when you're + done. + + +
+
+ + setNewTaskName(e.target.value)} + placeholder="Enter task name" + /> +
+
+ + setNewTaskDescription(e.target.value)} + placeholder="Enter task description" + /> +
+
+ + + + +
+
+
+
+ {tasks.map((task) => ( +
+
+ task?.id && + task?.state && + handleTaskClick(task.id, task.state) + } + > + {task?.state === "DONE" && ( + )}
-
-
06.00 - 07.30
- +
+ task?.id && + task?.state && + handleTaskClick(task.id, task.state) + } + > +
+

+ {task?.name} +

+ {task?.description && ( +

+ {task.description} +

+ )} +
+
+
06.00 - 07.30
+ +
-
- ))} + ))} - {tasks.length === 0 && ( -
-

No tasks yet. Click "Add Task" to create one.

-
- )} + {tasks.length === 0 && ( +
+

No tasks yet. Click "Add Task" to create one.

+
+ )} +
-
- {/* Delete Task Confirmation Dialog */} - - - - Delete Task - - Are you sure you want to delete the task " - {taskToDelete?.name}"? This action cannot be undone. - - - - - - - - -
+ {/* Delete Task Confirmation Dialog */} + + + + Delete Task + + Are you sure you want to delete the task " + {taskToDelete?.name}"? This action cannot be undone. + + + + + + + + +
+ ); } @@ -466,21 +489,22 @@ function ControlOverviewPageFallback() { export default function ControlOverviewPage() { const { controlId } = useParams(); const [queryRef, loadQuery] = useQueryLoader( - controlOverviewPageQuery, + controlOverviewPageQuery ); useEffect(() => { - loadQuery({ controlId: controlId! }); - }, [loadQuery, controlId]); + if (controlId) { + loadQuery({ controlId }); + } + }, [controlId, loadQuery]); + + if (!queryRef) { + return ; + } return ( - <> - - Control Overview - Probo Console - - }> - {queryRef && } - - + }> + + ); } diff --git a/apps/console/src/pages/UpdateControlPage.tsx b/apps/console/src/pages/UpdateControlPage.tsx new file mode 100644 index 000000000..8aaf5a975 --- /dev/null +++ b/apps/console/src/pages/UpdateControlPage.tsx @@ -0,0 +1,335 @@ +import { Suspense, useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router"; +import { + graphql, + useMutation, + usePreloadedQuery, + PreloadedQuery, + useQueryLoader, +} from "react-relay"; +import { Helmet } from "react-helmet-async"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { useToast } from "@/hooks/use-toast"; +import { HelpCircle } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +// Type imports will be available after Relay compiler runs +// import type { UpdateControlPageQuery as UpdateControlPageQueryType } from "./__generated__/UpdateControlPageQuery.graphql"; +// import type { UpdateControlPageUpdateControlMutation as UpdateControlPageUpdateControlMutationType } from "./__generated__/UpdateControlPageUpdateControlMutation.graphql"; + +const updateControlMutation = graphql` + mutation UpdateControlPageUpdateControlMutation($input: UpdateControlInput!) { + updateControl(input: $input) { + control { + id + name + description + category + state + version + } + } + } +`; + +const updateControlQuery = graphql` + query UpdateControlPageQuery($controlId: ID!) { + node(id: $controlId) { + ... on Control { + id + name + description + category + state + version + } + } + } +`; + +function EditableField({ + label, + value, + onChange, + type = "text", + helpText, + required, + multiline = false, +}: { + label: string; + value: string; + onChange: (value: string) => void; + type?: string; + helpText?: string; + required?: boolean; + multiline?: boolean; +}) { + return ( +
+
+ + {helpText && ( +
+ + {helpText} +
+ )} +
+ {multiline ? ( +