Repository: swisstopo/swissgeol-boreholes-suite
Pull request #2069: Add readonly stepper in workflow view
Author: login: MiraGeowerkstatt
Created: 2025-05-06T06:25:39Z | Merged: 2025-05-06T12:57:12Z



Diff:
diff --git a/src/api/BdmsContext.cs b/src/api/BdmsContext.cs
index 624ab0ed97..c0b20f1a95 100644
--- a/src/api/BdmsContext.cs
+++ b/src/api/BdmsContext.cs
@@ -113,6 +113,7 @@ public IQueryable<WorkflowV2> WorkflowsV2WithIncludes
         => WorkflowsV2
         .Include(w => w.Changes).ThenInclude(wc => wc.CreatedBy)
         .Include(w => w.Changes).ThenInclude(wc => wc.Assignee)
+        .Include(w => w.Assignee)
         .Include(w => w.ReviewedTabs)
         .Include(w => w.PublishedTabs);
 
diff --git a/src/client/cypress/e2e/detailPage/workflow.cy.js b/src/client/cypress/e2e/detailPage/workflow.cy.js
index e7419676c4..f11d2efcfa 100644
--- a/src/client/cypress/e2e/detailPage/workflow.cy.js
+++ b/src/client/cypress/e2e/detailPage/workflow.cy.js
@@ -7,8 +7,13 @@ describe("Tests the publication workflow.", () => {
     cy.contains("h4", "Publication workflow").should("exist");
     goToRouteAndAcceptTerms(`/1000036/status?dev=true`);
     cy.contains("h4", "Publication workflow").should("not.exist");
-    cy.contains("h5", "Status").should("exist");
-    cy.contains("h5", "Assigned Person").should("exist");
+
+    getElementByDataCy("workflow-status-card").contains("h5", "Status").should("exist");
+    getElementByDataCy("workflow-status-card").contains("h5", "Draft").should("exist");
+
+    getElementByDataCy("workflow-assigned-user-card").contains("h5", "Assigned Person").should("exist");
+    getElementByDataCy("workflow-assigned-user-card").contains("p", "Validator User").should("exist");
+
     getElementByDataCy("workflow-history-entry-15000183").should("contain", "Editor User");
     getElementByDataCy("workflow-history-entry-15000183").should("contain", "16. Nov. 2021");
     getElementByDataCy("workflow-history-entry-15000183").should(
diff --git a/src/client/public/locale/de/common.json b/src/client/public/locale/de/common.json
index 945d6cc154..f7660a02f3 100644
--- a/src/client/public/locale/de/common.json
+++ b/src/client/public/locale/de/common.json
@@ -36,6 +36,7 @@
   "alternate_name": "Name",
   "analyze": "Analysieren",
   "appearance": "Erscheinungsbild",
+  "assignUser": "Neue Person zuweisen",
   "assignedPerson": "Zugewiesene Person",
   "attachments": "Anhänge",
   "autoSetDepthMaslEnabled": "Automatisch Von- und Bis-Tiefe m ü. M. setzen",
@@ -442,6 +443,7 @@
   "rejected": "Abgelehnt",
   "reliability": "Zuverlässigkeit der Angabe",
   "remarks": "Bemerkungen",
+  "requestReview": "Review anfordern",
   "reset": "Zurücksetzen",
   "restriction": "Beschränkung",
   "restriction_until": "Datum Ende der Beschränkung",
diff --git a/src/client/public/locale/en/common.json b/src/client/public/locale/en/common.json
index 9897a7dc5a..169c4722da 100644
--- a/src/client/public/locale/en/common.json
+++ b/src/client/public/locale/en/common.json
@@ -36,6 +36,7 @@
   "alternate_name": "Name",
   "analyze": "Analyzing",
   "appearance": "Appearance",
+  "assignUser": "Assign new person",
   "assignedPerson": "Assigned Person",
   "attachments": "Attachments",
   "autoSetDepthMaslEnabled": "Auto set From and To Depth m a.s.l.",
@@ -447,6 +448,7 @@
   "rejected": "Rejected",
   "reliability": "Reliability of data",
   "remarks": "Remarks",
+  "requestReview": "Request review",
   "reset": "Reset",
   "restriction": "Restriction",
   "restriction_until": "Restriction end date",
diff --git a/src/client/public/locale/fr/common.json b/src/client/public/locale/fr/common.json
index 4c50e0f3e4..f6a24c9b99 100644
--- a/src/client/public/locale/fr/common.json
+++ b/src/client/public/locale/fr/common.json
@@ -36,6 +36,7 @@
   "alternate_name": "Nom",
   "analyze": "Analyser",
   "appearance": "Mode de présentation",
+  "assignUser": "Attribuer nouvelle personne",
   "assignedPerson": "Personne assignée",
   "attachments": "Annexes",
   "autoSetDepthMaslEnabled": "Définir automatiquement la profondeur De et À m s.m.",
@@ -442,6 +443,7 @@
   "rejected": "Refusé",
   "reliability": "Fiabilité des informations",
   "remarks": "Remarques",
+  "requestReview": "Demander revision",
   "reset": "Réinitialiser",
   "restriction": "Restriction",
   "restriction_until": "Date fin de restriction",
diff --git a/src/client/public/locale/it/common.json b/src/client/public/locale/it/common.json
index 04e967e811..42b9228efd 100644
--- a/src/client/public/locale/it/common.json
+++ b/src/client/public/locale/it/common.json
@@ -36,6 +36,7 @@
   "alternate_name": "Nome",
   "analyze": "Analisi in corso",
   "appearance": "Aspetto",
+  "assignUser": "Assegna nuova persona",
   "assignedPerson": "Persona assegnata",
   "attachments": "Allegati",
   "autoSetDepthMaslEnabled": "Imposta automaticamente la profondità Da e A m s.l.m.",
@@ -442,6 +443,7 @@
   "rejected": "Respinto",
   "reliability": "Affidabilità delle informazioni",
   "remarks": "Osservazioni",
+  "requestReview": "Richiedi revisione",
   "reset": "Azzerare",
   "restriction": "Restrizione",
   "restriction_until": "Data fine di restrizione",
diff --git a/src/client/src/assets/icons/assignUser.svg b/src/client/src/assets/icons/assignUser.svg
new file mode 100644
index 0000000000..f8024fd929
--- /dev/null
+++ b/src/client/src/assets/icons/assignUser.svg
@@ -0,0 +1,3 @@
+<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
+    <path d="M18.8337 17.5V15.8333C18.8337 14.9493 18.4825 14.1014 17.8573 13.4763C17.2322 12.8512 16.3844 12.5 15.5003 12.5H10.5003C9.61627 12.5 8.76842 12.8512 8.1433 13.4763C7.51818 14.1014 7.16699 14.9493 7.16699 15.8333V17.5M4.66699 6.66667L7.16699 9.16667M7.16699 9.16667L4.66699 11.6667M7.16699 9.16667H2.16699M16.3337 5.83333C16.3337 7.67428 14.8413 9.16667 13.0003 9.16667C11.1594 9.16667 9.66699 7.67428 9.66699 5.83333C9.66699 3.99238 11.1594 2.5 13.0003 2.5C14.8413 2.5 16.3337 3.99238 16.3337 5.83333Z" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/src/client/src/pages/detail/form/workflow/historyEntry.tsx b/src/client/src/pages/detail/form/workflow/historyEntry.tsx
index cb75973f70..cec074d1a3 100644
--- a/src/client/src/pages/detail/form/workflow/historyEntry.tsx
+++ b/src/client/src/pages/detail/form/workflow/historyEntry.tsx
@@ -26,7 +26,14 @@ export const HistoryEntry: FC<{ workflowChange: WorkflowChange }> = ({ workflowC
             mr: "14px !important",
           }}
         />
-        <Typography variant="h5">{`${[capitalizeFirstLetter(workflowChange.createdBy?.firstName), capitalizeFirstLetter(workflowChange.createdBy?.lastName)].filter(Boolean).join(" ")}`}</Typography>
+        <Typography variant="h5">
+          {[
+            capitalizeFirstLetter(workflowChange.createdBy?.firstName),
+            capitalizeFirstLetter(workflowChange.createdBy?.lastName),
+          ]
+            .filter(Boolean)
+            .join(" ")}
+        </Typography>
         <DescriptionText>
           {`∙ ${new Date(workflowChange?.created).toLocaleDateString("de-CH", {
             day: "2-digit",
diff --git a/src/client/src/pages/detail/form/workflow/workflow.ts b/src/client/src/pages/detail/form/workflow/workflow.ts
index 2bc714672b..ed466ef5da 100644
--- a/src/client/src/pages/detail/form/workflow/workflow.ts
+++ b/src/client/src/pages/detail/form/workflow/workflow.ts
@@ -1,4 +1,4 @@
-import { useQuery } from "@tanstack/react-query";
+import { useQuery, UseQueryResult } from "@tanstack/react-query";
 import { User } from "../../../../api/apiInterfaces.ts";
 import { fetchApiV2 } from "../../../../api/fetchApiV2.ts";
 import { useShowAlertOnError } from "../../../../hooks/useShowAlertOnError.ts";
@@ -58,7 +58,7 @@ export const fetchWorkflowByBoreholeId = async (boreholeId: number) =>
 
 export const workflowQueryKey = "workflows";
 
-export const useWorkflow = (boreholeId: number) => {
+export const useWorkflow = (boreholeId: number): UseQueryResult<WorkflowV2> => {
   const query = useQuery({
     queryKey: [workflowQueryKey, boreholeId],
     queryFn: () => {
diff --git a/src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx b/src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx
index de1f198289..dc4ed55e52 100644
--- a/src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx
+++ b/src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx
@@ -1,13 +1,35 @@
 import { useTranslation } from "react-i18next";
-import { Typography } from "@mui/material";
+import { useParams } from "react-router-dom";
+import { Button, Stack, Typography } from "@mui/material";
+import AssignUser from "../../../../assets/icons/assignUser.svg?react";
+import { capitalizeFirstLetter } from "../../../../utils.ts";
 import { WorkflowCard } from "./styledWorkflowComponents.tsx";
+import { useWorkflow } from "./workflow.ts";
 
 export const WorkflowAssignedUserCard = () => {
   const { t } = useTranslation();
+  const { id: boreholeId } = useParams<{ id: string }>();
+  const { data: workflow } = useWorkflow(parseInt(boreholeId));
 
   return (
     <WorkflowCard>
-      <Typography variant="h5">{t("assignedPerson")}</Typography>
+      <Stack gap={1.5} data-cy={"workflow-assigned-user-card"}>
+        <Typography variant="h5">{t("assignedPerson")}</Typography>
+        <Typography variant="body1">
+          {[capitalizeFirstLetter(workflow?.assignee?.firstName), capitalizeFirstLetter(workflow?.assignee?.lastName)]
+            .filter(Boolean)
+            .join(" ")}
+        </Typography>
+        <Button
+          variant="outlined"
+          data-cy={"assign-user-button"}
+          endIcon={<AssignUser />}
+          onClick={() => {
+            //Todo: implement assign user functionality
+          }}>
+          {t("assignUser")}
+        </Button>
+      </Stack>
     </WorkflowCard>
   );
 };
diff --git a/src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx b/src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx
index 277f74e456..7a0bab1105 100644
--- a/src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx
+++ b/src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx
@@ -1,12 +1,100 @@
 import { useTranslation } from "react-i18next";
-import { Typography } from "@mui/material";
+import { useParams } from "react-router-dom";
+import {
+  Box,
+  Button,
+  Stack,
+  Step,
+  StepConnector,
+  stepConnectorClasses,
+  StepLabel,
+  Stepper,
+  styled,
+  Typography,
+} from "@mui/material";
+import type { StepIconProps } from "@mui/material/StepIcon";
+import { ChevronRight } from "lucide-react";
+import { theme } from "../../../../AppTheme.ts";
 import { WorkflowCard } from "./styledWorkflowComponents.tsx";
+import { useWorkflow, WorkflowStatus } from "./workflow.ts";
+
+const steps = Object.values(WorkflowStatus).slice(0, -1);
+
+const CustomConnector = styled(StepConnector)(({ theme }) => ({
+  marginLeft: theme.spacing(3.5),
+  [`& .${stepConnectorClasses.line}`]: {
+    borderColor: theme.palette.action.disabled,
+    borderLeftWidth: "1px",
+    minHeight: theme.spacing(1.5),
+  },
+}));
+
+const StepIcon = ({ active, icon }: StepIconProps) => {
+  return (
+    <Stack
+      sx={{
+        height: "36px",
+        width: "36px",
+        backgroundColor: active ? "white" : "transparent",
+        color: active ? theme.palette.primary.main : theme.palette.action.disabled,
+        mr: 1.5,
+        borderRadius: "50%",
+        border: `1px solid ${active ? theme.palette.primary.main : theme.palette.action.disabled}`,
+        alignItems: "center",
+        justifyContent: "center",
+        fontWeight: 700,
+      }}>
+      {icon}
+    </Stack>
+  );
+};
 
 export const WorkflowStatusCard = () => {
   const { t } = useTranslation();
+  const { id: boreholeId } = useParams<{ id: string }>();
+  const { data: workflow } = useWorkflow(parseInt(boreholeId));
+  const activeStep = steps.findIndex(step => step === workflow?.status);
+
   return (
     <WorkflowCard>
-      <Typography variant="h5">{t("status")}</Typography>
+      <Stack gap={1.5} data-cy={"workflow-status-card"}>
+        <Typography variant="h5">{t("status")}</Typography>
+        <Stepper activeStep={activeStep} orientation="vertical" connector={<CustomConnector />}>
+          {steps.map((label, index) => {
+            const isActive = index === activeStep;
+            return (
+              <Step key={label}>
+                <Box
+                  sx={{
+                    px: 1.5,
+                    py: isActive ? 0.5 : 0,
+                    mb: isActive ? 1 : 0,
+                    borderRadius: 0.5,
+                    backgroundColor: isActive ? theme.palette.background.grey : "transparent",
+                    border: `1px solid ${isActive ? theme.palette.border.light : "transparent"}`,
+                  }}>
+                  <StepLabel slots={{ stepIcon: StepIcon }}>
+                    <Typography
+                      variant={isActive ? "h5" : "body1"}
+                      color={isActive ? theme.palette.primary.main : theme.palette.action.disabled}>
+                      {t(`statuses.${label}`)}
+                    </Typography>
+                  </StepLabel>
+                </Box>
+              </Step>
+            );
+          })}
+        </Stepper>
+        <Button
+          variant="contained"
+          data-cy={"request-review-button"}
+          endIcon={<ChevronRight />}
+          onClick={() => {
+            //Todo: implement request review functionality
+          }}>
+          {t("requestReview")}
+        </Button>
+      </Stack>
     </WorkflowCard>
   );
 };
diff --git a/src/client/src/pages/detail/form/workflow/workflowView.tsx b/src/client/src/pages/detail/form/workflow/workflowView.tsx
index 984980c52f..6075f53e9c 100644
--- a/src/client/src/pages/detail/form/workflow/workflowView.tsx
+++ b/src/client/src/pages/detail/form/workflow/workflowView.tsx
@@ -1,4 +1,4 @@
-import { Stack } from "@mui/material";
+import { Box, Stack } from "@mui/material";
 import { WorkflowAssignedUserCard } from "./workflowAssignedUserCard.tsx";
 import { WorkflowStatusCard } from "./workflowStatusCard.tsx";
 import { WorkflowTabs } from "./workflowTabs.tsx";
@@ -6,11 +6,13 @@ import { WorkflowTabs } from "./workflowTabs.tsx";
 export const WorkflowView = () => {
   return (
     <Stack gap={1.5} direction="row">
-      <Stack gap={1.5}>
+      <Stack gap={1.5} sx={{ flexShrink: 0 }}>
         <WorkflowStatusCard />
         <WorkflowAssignedUserCard />
       </Stack>
-      <WorkflowTabs />
+      <Box sx={{ flexGrow: 0 }} width={"100%"}>
+        <WorkflowTabs />
+      </Box>
     </Stack>
   );
 };


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a readonly stepper in the workflow view and updates several UI components and tests accordingly. Key changes include adding a vertical stepper to the workflow status card, integrating assignee details with an assign user button, and updating both the workflow hook type and Cypress tests to support new data-cy attributes.

### Reviewed Changes

Copilot reviewed 7 out of 11 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| src/client/src/pages/detail/form/workflow/workflowView.tsx | Adjusted layout by wrapping WorkflowTabs in a Box component with specific flex settings. |
| src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx | Introduced a vertical Stepper with a custom connector and a review button. |
| src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx | Added UI to display assigned user details and an assign user button. |
| src/client/src/pages/detail/form/workflow/workflow.ts | Updated the useWorkflow hook’s type signature. |
| src/client/cypress/e2e/detailPage/workflow.cy.js | Updated tests to verify new data-cy attributes and UI text. |
| src/api/BdmsContext.cs | Updated entity includes to support new user detail fetching. |
</details>


<details>
<summary>Files not reviewed (4)</summary>

* **src/client/public/locale/de/common.json**: Language not supported
* **src/client/public/locale/en/common.json**: Language not supported
* **src/client/public/locale/fr/common.json**: Language not supported
* **src/client/public/locale/it/common.json**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx:92**
* If 'Request review' is expected to trigger a real action, consider implementing the actual functionality or disabling the button in this readonly context to avoid confusion.
```
onClick={() => { console.log("Request review"); }}
```
**src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx:19**
* Since the workflow view is read-only, consider disabling or removing the 'Assign user' button to prevent misleading interactive cues.
```
<Button variant="outlined" data-cy={"assign-user-button"} endIcon={<AssignUser />} onClick={() => { console.log("Assign user"); }}>
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T06:26:39Z
user: login: domi-b
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T07:11:42Z
user: login: MiraGeowerkstatt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T07:24:17Z
user: login: domi-b
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T07:28:37Z
user: login: patrickackermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T07:37:30Z
user: login: MiraGeowerkstatt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T07:39:35Z
user: login: domi-b
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T12:57:11Z

Review comments:
diff_hunk: @@ -1,16 +1,18 @@
-import { Stack } from "@mui/material";
+import { Box, Stack } from "@mui/material";
 import { WorkflowAssignedUserCard } from "./workflowAssignedUserCard.tsx";
 import { WorkflowStatusCard } from "./workflowStatusCard.tsx";
 import { WorkflowTabs } from "./workflowTabs.tsx";
 
 export const WorkflowView = () => {
   return (
     <Stack gap={1.5} direction="row">
-      <Stack gap={1.5}>
+      <Stack gap={1.5} sx={{ flexShrink: 0 }}>
path: src/client/src/pages/detail/form/workflow/workflowView.tsx
user: login: Copilot
body: [nitpick] Review whether applying 'flexShrink: 0' on this Stack is necessary for all viewport sizes, as it might lead to layout issues in certain responsive scenarios.
created_at: 2025-05-06T06:26:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,100 @@
 import { useTranslation } from "react-i18next";
-import { Typography } from "@mui/material";
+import { useParams } from "react-router-dom";
+import {
+  Box,
+  Button,
+  Stack,
+  Step,
+  StepConnector,
+  stepConnectorClasses,
+  StepLabel,
+  Stepper,
+  styled,
+  Typography,
+} from "@mui/material";
+import type { StepIconProps } from "@mui/material/StepIcon";
+import { ChevronRight } from "lucide-react";
+import { theme } from "../../../../AppTheme.ts";
 import { WorkflowCard } from "./styledWorkflowComponents.tsx";
+import { useWorkflow, WorkflowStatus } from "./workflow.ts";
+
+const steps = Object.values(WorkflowStatus).slice(0, -1);
+
+const CustomConnector = styled(StepConnector)(({ theme }) => ({
+  marginLeft: theme.spacing(3.5),
+  [`& .${stepConnectorClasses.line}`]: {
+    borderColor: theme.palette.action.disabled,
+    borderLeftWidth: "1px",
+    minHeight: theme.spacing(1.5),
+  },
+}));
+
+const StepIcon = ({ active, icon }: StepIconProps) => {
+  return (
+    <Stack
+      sx={{
+        height: "36px",
+        width: "36px",
+        backgroundColor: active ? "white" : "transparent",
+        color: active ? theme.palette.primary.main : theme.palette.action.disabled,
+        mr: 1.5,
+        borderRadius: "50%",
+        border: `1px solid ${active ? theme.palette.primary.main : theme.palette.action.disabled}`,
+        alignItems: "center",
+        justifyContent: "center",
+        fontWeight: 700,
+      }}>
+      {icon}
+    </Stack>
+  );
+};
 
 export const WorkflowStatusCard = () => {
   const { t } = useTranslation();
+  const { id: boreholeId } = useParams<{ id: string }>();
+  const { data: workflow } = useWorkflow(parseInt(boreholeId));
+  const activeStep = steps.findIndex(step => step === workflow?.status);
+
   return (
     <WorkflowCard>
-      <Typography variant="h5">{t("status")}</Typography>
+      <Stack gap={1.5} data-cy={"workflow-status-card"}>
+        <Typography variant="h5">{t("status")}</Typography>
+        <Stepper activeStep={activeStep} orientation="vertical" connector={<CustomConnector />}>
+          {steps.map((label, index) => {
+            const isActive = index === activeStep;
+            return (
+              <Step key={label}>
+                <Box
+                  sx={{
+                    px: 1.5,
+                    py: isActive ? 0.5 : 0,
+                    mb: isActive ? 1 : 0,
+                    borderRadius: 0.5,
+                    backgroundColor: isActive ? theme.palette.background.grey : "transparent",
+                    border: `1px solid ${isActive ? theme.palette.border.light : "#transparent"}`,
path: src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx
user: login: domi-b
body: Typo: `#` vor `transparent`
created_at: 2025-05-06T07:08:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,12 +1,100 @@
 import { useTranslation } from "react-i18next";
-import { Typography } from "@mui/material";
+import { useParams } from "react-router-dom";
+import {
+  Box,
+  Button,
+  Stack,
+  Step,
+  StepConnector,
+  stepConnectorClasses,
+  StepLabel,
+  Stepper,
+  styled,
+  Typography,
+} from "@mui/material";
+import type { StepIconProps } from "@mui/material/StepIcon";
+import { ChevronRight } from "lucide-react";
+import { theme } from "../../../../AppTheme.ts";
 import { WorkflowCard } from "./styledWorkflowComponents.tsx";
+import { useWorkflow, WorkflowStatus } from "./workflow.ts";
+
+const steps = Object.values(WorkflowStatus).slice(0, -1);
+
+const CustomConnector = styled(StepConnector)(({ theme }) => ({
+  marginLeft: theme.spacing(3.5),
+  [`& .${stepConnectorClasses.line}`]: {
+    borderColor: theme.palette.action.disabled,
+    borderLeftWidth: "1px",
+    minHeight: theme.spacing(1.5),
+  },
+}));
+
+const StepIcon = ({ active, icon }: StepIconProps) => {
+  return (
+    <Stack
+      sx={{
+        height: "36px",
+        width: "36px",
+        backgroundColor: active ? "white" : "transparent",
+        color: active ? theme.palette.primary.main : theme.palette.action.disabled,
+        mr: 1.5,
+        borderRadius: "50%",
+        border: `1px solid ${active ? theme.palette.primary.main : theme.palette.action.disabled}`,
+        alignItems: "center",
+        justifyContent: "center",
+        fontWeight: 700,
+      }}>
+      {icon}
+    </Stack>
+  );
+};
 
 export const WorkflowStatusCard = () => {
   const { t } = useTranslation();
+  const { id: boreholeId } = useParams<{ id: string }>();
+  const { data: workflow } = useWorkflow(parseInt(boreholeId));
+  const activeStep = steps.findIndex(step => step === workflow?.status);
+
   return (
     <WorkflowCard>
-      <Typography variant="h5">{t("status")}</Typography>
+      <Stack gap={1.5} data-cy={"workflow-status-card"}>
+        <Typography variant="h5">{t("status")}</Typography>
+        <Stepper activeStep={activeStep} orientation="vertical" connector={<CustomConnector />}>
+          {steps.map((label, index) => {
+            const isActive = index === activeStep;
+            return (
+              <Step key={label}>
+                <Box
+                  sx={{
+                    px: 1.5,
+                    py: isActive ? 0.5 : 0,
+                    mb: isActive ? 1 : 0,
+                    borderRadius: 0.5,
+                    backgroundColor: isActive ? theme.palette.background.grey : "transparent",
+                    border: `1px solid ${isActive ? theme.palette.border.light : "#transparent"}`,
path: src/client/src/pages/detail/form/workflow/workflowStatusCard.tsx
user: login: MiraGeowerkstatt
body: ```suggestion
                    border: `1px solid ${isActive ? theme.palette.border.light : "transparent"}`,
```
created_at: 2025-05-06T07:24:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,13 +1,31 @@
 import { useTranslation } from "react-i18next";
-import { Typography } from "@mui/material";
+import { useParams } from "react-router-dom";
+import { Button, Stack, Typography } from "@mui/material";
+import AssignUser from "../../../../assets/icons/assignUser.svg?react";
+import { capitalizeFirstLetter } from "../../../../utils.ts";
 import { WorkflowCard } from "./styledWorkflowComponents.tsx";
+import { useWorkflow } from "./workflow.ts";
 
 export const WorkflowAssignedUserCard = () => {
   const { t } = useTranslation();
+  const { id: boreholeId } = useParams<{ id: string }>();
+  const { data: workflow } = useWorkflow(parseInt(boreholeId));
 
   return (
     <WorkflowCard>
-      <Typography variant="h5">{t("assignedPerson")}</Typography>
+      <Stack gap={1.5} data-cy={"workflow-assigned-user-card"}>
+        <Typography variant="h5">{t("assignedPerson")}</Typography>
+        <Typography variant="body1">{`${[capitalizeFirstLetter(workflow?.assignee?.firstName), capitalizeFirstLetter(workflow?.assignee?.lastName)].filter(Boolean).join(" ")}`}</Typography>
path: src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx
user: login: patrickackermann
body: PP: Unnötige string interpolation entfernen
created_at: 2025-05-06T07:32:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,13 +1,31 @@
 import { useTranslation } from "react-i18next";
-import { Typography } from "@mui/material";
+import { useParams } from "react-router-dom";
+import { Button, Stack, Typography } from "@mui/material";
+import AssignUser from "../../../../assets/icons/assignUser.svg?react";
+import { capitalizeFirstLetter } from "../../../../utils.ts";
 import { WorkflowCard } from "./styledWorkflowComponents.tsx";
+import { useWorkflow } from "./workflow.ts";
 
 export const WorkflowAssignedUserCard = () => {
   const { t } = useTranslation();
+  const { id: boreholeId } = useParams<{ id: string }>();
+  const { data: workflow } = useWorkflow(parseInt(boreholeId));
 
   return (
     <WorkflowCard>
-      <Typography variant="h5">{t("assignedPerson")}</Typography>
+      <Stack gap={1.5} data-cy={"workflow-assigned-user-card"}>
+        <Typography variant="h5">{t("assignedPerson")}</Typography>
+        <Typography variant="body1">{`${[capitalizeFirstLetter(workflow?.assignee?.firstName), capitalizeFirstLetter(workflow?.assignee?.lastName)].filter(Boolean).join(" ")}`}</Typography>
+        <Button
+          variant="outlined"
+          data-cy={"assign-user-button"}
+          endIcon={<AssignUser />}
+          onClick={() => {
+            //Todo: implement assign user functionality
path: src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx
user: login: patrickackermann
body: Q: Werden die Todos später implementiert?
created_at: 2025-05-06T07:33:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,13 +1,31 @@
 import { useTranslation } from "react-i18next";
-import { Typography } from "@mui/material";
+import { useParams } from "react-router-dom";
+import { Button, Stack, Typography } from "@mui/material";
+import AssignUser from "../../../../assets/icons/assignUser.svg?react";
+import { capitalizeFirstLetter } from "../../../../utils.ts";
 import { WorkflowCard } from "./styledWorkflowComponents.tsx";
+import { useWorkflow } from "./workflow.ts";
 
 export const WorkflowAssignedUserCard = () => {
   const { t } = useTranslation();
+  const { id: boreholeId } = useParams<{ id: string }>();
+  const { data: workflow } = useWorkflow(parseInt(boreholeId));
 
   return (
     <WorkflowCard>
-      <Typography variant="h5">{t("assignedPerson")}</Typography>
+      <Stack gap={1.5} data-cy={"workflow-assigned-user-card"}>
+        <Typography variant="h5">{t("assignedPerson")}</Typography>
+        <Typography variant="body1">{`${[capitalizeFirstLetter(workflow?.assignee?.firstName), capitalizeFirstLetter(workflow?.assignee?.lastName)].filter(Boolean).join(" ")}`}</Typography>
+        <Button
+          variant="outlined"
+          data-cy={"assign-user-button"}
+          endIcon={<AssignUser />}
+          onClick={() => {
+            //Todo: implement assign user functionality
path: src/client/src/pages/detail/form/workflow/workflowAssignedUserCard.tsx
user: login: MiraGeowerkstatt
body: Ja im aktuellen Stand ist noch alles readonly, die Buttons machen später was
created_at: 2025-05-06T07:39:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-06T06:43:56Z
body: ## Test Results
  2 files  ±0    2 suites  ±0   6m 43s ⏱️ -12s
422 tests ±0  422 ✅ ±0  0 💤 ±0  0 ❌ ±0 
434 runs  ±0  434 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit fff20cf0. ± Comparison against base commit 272b7ae0.

[test-results]:data:application/gzip;base64,H4sIAEr8GWgC/12MSw7CIBQAr9KwdoGPpwEvY+AVEmJbDJ+V8e4iqYguZyaZB3N+sYldJjhMLBWfO8wl6uzDVhG5qKKm/I4I8KFrKkT/6ubvVfEunPbLj7AxhribWLb2FLhDXw7me2w8DBuPPwrr6nMF5pwDTo7T+SgIlTFSS4RZKWEUwslKsCTQKvZ8AfrWmdgFAQAA

:recycle: This comment has been updated with latest results.
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-06T12:01:51Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=swisstopo_swissgeol-boreholes-suite&pullRequest=2069) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=swisstopo_swissgeol-boreholes-suite&pullRequest=2069&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=swisstopo_swissgeol-boreholes-suite&pullRequest=2069&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=swisstopo_swissgeol-boreholes-suite&pullRequest=2069&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=swisstopo_swissgeol-boreholes-suite&pullRequest=2069&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=swisstopo_swissgeol-boreholes-suite&pullRequest=2069&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=swisstopo_swissgeol-boreholes-suite&pullRequest=2069)


author_association: NONE