From 2da47d8536d232adaeda2b7431f4d55762d6df1a Mon Sep 17 00:00:00 2001
From: Mohamed-Hacene <90701924+Mohamed-Hacene@users.noreply.github.com>
Date: Fri, 13 Dec 2024 17:48:05 +0100
Subject: [PATCH] Workshop 2 UX (#1180)

Co-authored-by: eric-intuitem <71850047+eric-intuitem@users.noreply.github.com>
---
 ...oto_pertinence_alter_roto_feared_events.py |  26 +++
 backend/ebios_rm/models.py                    |  24 ++-
 backend/ebios_rm/serializers.py               |   2 +-
 backend/ebios_rm/views.py                     |   8 +-
 documentation/architecture/data-model.md      |   7 +-
 frontend/messages/en.json                     |   6 +-
 .../src/lib/components/Forms/ModelForm.svelte |   2 +-
 .../Forms/ModelForm/RoToForm.svelte           | 184 +++++++++++-------
 .../components/ModelTable/ModelTable.svelte   |   3 +-
 frontend/src/lib/utils/crud.ts                |  14 +-
 frontend/src/lib/utils/load.ts                |   4 +-
 frontend/src/lib/utils/schemas.ts             |   3 +-
 .../ebios-rm/[id=uuid]/+page.svelte           |   6 +-
 .../workshop-one/ebios-rm-study/+page.svelte  |   8 +-
 .../feared-events/+page.server.ts             |   8 +-
 .../workshop-two/ro-to/+page.server.ts        |   4 +-
 .../[id=uuid]/workshop-two/ro-to/+page.svelte |  19 +-
 .../ro-to/[id=uuid]/+page.server.ts           |  37 ++++
 .../(internal)/ro-to/[id=uuid]/+page.svelte   | 165 ++++++++++++++++
 .../ro-to/[id=uuid]/edit/+page.server.ts      |  67 +++++++
 .../ro-to/[id=uuid]/edit/+page.svelte         |  17 ++
 21 files changed, 507 insertions(+), 107 deletions(-)
 create mode 100644 backend/ebios_rm/migrations/0004_remove_roto_pertinence_alter_roto_feared_events.py
 create mode 100644 frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.server.ts
 create mode 100644 frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.svelte
 create mode 100644 frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.server.ts
 create mode 100644 frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.svelte

diff --git a/backend/ebios_rm/migrations/0004_remove_roto_pertinence_alter_roto_feared_events.py b/backend/ebios_rm/migrations/0004_remove_roto_pertinence_alter_roto_feared_events.py
new file mode 100644
index 000000000..954fe19f8
--- /dev/null
+++ b/backend/ebios_rm/migrations/0004_remove_roto_pertinence_alter_roto_feared_events.py
@@ -0,0 +1,26 @@
+# Generated by Django 5.1.4 on 2024-12-12 18:40
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("ebios_rm", "0003_remove_ebiosrmstudy_risk_assessments"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="roto",
+            name="pertinence",
+        ),
+        migrations.AlterField(
+            model_name="roto",
+            name="feared_events",
+            field=models.ManyToManyField(
+                blank=True,
+                related_name="ro_to_couples",
+                to="ebios_rm.fearedevent",
+                verbose_name="Feared events",
+            ),
+        ),
+    ]
diff --git a/backend/ebios_rm/models.py b/backend/ebios_rm/models.py
index 83ad19ea1..dbe56bfb8 100644
--- a/backend/ebios_rm/models.py
+++ b/backend/ebios_rm/models.py
@@ -194,7 +194,10 @@ class Pertinence(models.IntegerChoices):
         on_delete=models.CASCADE,
     )
     feared_events = models.ManyToManyField(
-        FearedEvent, verbose_name=_("Feared events"), related_name="ro_to_couples"
+        FearedEvent,
+        verbose_name=_("Feared events"),
+        related_name="ro_to_couples",
+        blank=True,
     )
 
     risk_origin = models.CharField(
@@ -211,11 +214,6 @@ class Pertinence(models.IntegerChoices):
         choices=Resources.choices,
         default=Resources.UNDEFINED,
     )
-    pertinence = models.PositiveSmallIntegerField(
-        verbose_name=_("Pertinence"),
-        choices=Pertinence.choices,
-        default=Pertinence.UNDEFINED,
-    )
     activity = models.PositiveSmallIntegerField(
         verbose_name=_("Activity"), default=0, validators=[MaxValueValidator(4)]
     )
@@ -234,6 +232,20 @@ def save(self, *args, **kwargs):
         self.folder = self.ebios_rm_study.folder
         super().save(*args, **kwargs)
 
+    @property
+    def get_pertinence(self):
+        PERTINENCE_MATRIX = [
+            [1, 1, 2, 2],
+            [1, 2, 3, 3],
+            [2, 3, 3, 4],
+            [2, 3, 4, 4],
+        ]
+        if self.motivation == 0 or self.resources == 0:
+            return self.Pertinence(self.Pertinence.UNDEFINED).label
+        return self.Pertinence(
+            PERTINENCE_MATRIX[self.motivation - 1][self.resources - 1]
+        ).label
+
 
 class Stakeholder(AbstractBaseModel, FolderMixin):
     class Category(models.TextChoices):
diff --git a/backend/ebios_rm/serializers.py b/backend/ebios_rm/serializers.py
index 2d81ac5c9..3eed45bf0 100644
--- a/backend/ebios_rm/serializers.py
+++ b/backend/ebios_rm/serializers.py
@@ -94,9 +94,9 @@ class RoToReadSerializer(BaseModelSerializer):
     folder = FieldsRelatedField()
     feared_events = FieldsRelatedField(["folder", "id"], many=True)
 
-    pertinence = serializers.CharField(source="get_pertinence_display")
     motivation = serializers.CharField(source="get_motivation_display")
     resources = serializers.CharField(source="get_resources_display")
+    pertinence = serializers.CharField(source="get_pertinence")
 
     class Meta:
         model = RoTo
diff --git a/backend/ebios_rm/views.py b/backend/ebios_rm/views.py
index 71ddb5d7d..6d35d48a1 100644
--- a/backend/ebios_rm/views.py
+++ b/backend/ebios_rm/views.py
@@ -69,9 +69,7 @@ def likelihood(self, request, pk):
 class FearedEventViewSet(BaseModelViewSet):
     model = FearedEvent
 
-    filterset_fields = [
-        "ebios_rm_study",
-    ]
+    filterset_fields = ["ebios_rm_study", "ro_to_couples"]
 
     @action(detail=True, name="Get risk matrix", url_path="risk-matrix")
     def risk_matrix(self, request, pk=None):
@@ -112,10 +110,6 @@ def motivation(self, request):
     def resources(self, request):
         return Response(dict(RoTo.Resources.choices))
 
-    @action(detail=False, name="Get pertinence choices")
-    def pertinence(self, request):
-        return Response(dict(RoTo.Pertinence.choices))
-
 
 class StakeholderViewSet(BaseModelViewSet):
     model = Stakeholder
diff --git a/documentation/architecture/data-model.md b/documentation/architecture/data-model.md
index 20766a703..59d68c962 100644
--- a/documentation/architecture/data-model.md
+++ b/documentation/architecture/data-model.md
@@ -1210,7 +1210,7 @@ The object risk_origin_target_objective (workshop 2) contains the following fiel
 - target objective (text)
 - motivation (--/1 very low/2 low/3 significant/4 strong) (--/très peu/peu/assez/fortement motivé)
 - resources (--/1 limited/2 significant/3 important/4 unlimited) (--/limitées/significatives/importantes/illimitées)
-- pertinence (--/1 Irrelevant/2 partially relevant/3 fairly relevant/4 highly relevant) (--/peu pertinent/moyennement pertient/plutôt pertinent/très pertinent)
+- pertinence (--/1 Irrelevant/2 partially relevant/3 fairly relevant/4 highly relevant) (--/peu pertinent/moyennement pertient/plutôt pertinent/très pertinent) -> calculated
 - activity (--/1/2/3/4)
 - selected
 - justification
@@ -1267,7 +1267,7 @@ The frontend for risk study shall propose the following steps:
 ```mermaid
 erDiagram
     DOMAIN                ||--o{ EBIOS_RM_STUDY       : contains
-    DOMAIN                ||--o{ STAKEHOLDER     : contains
+    DOMAIN                ||--o{ STAKEHOLDER          : contains
     DOMAIN                ||--o{ OPERATIONAL_SCENARIO : contains
     DOMAIN                ||--o{ FEARED_EVENT         : contains
     DOMAIN                ||--o{ RO_TO                : contains
@@ -1289,7 +1289,7 @@ erDiagram
     EBIOS_RM_STUDY        }o--o| ENTITY               : studies
     EBIOS_RM_STUDY        }o--o{ COMPLIANCE_ASSESSMENT: leverages
     EBIOS_RM_STUDY        }o--|| RISK_MATRIX          : leverages
-    EBIOS_RM_STUDY        }o--o{ RISK_ASSESSMENT      : generates
+    EBIOS_RM_STUDY        |o--o{ RISK_ASSESSMENT      : generates
     ATTACK_PATH           }o--|| RO_TO                : derives
     RO_TO                 }o--o{ FEARED_EVENT         : corresponds_to
     OPERATIONAL_SCENARIO  }o--|{ ATTACK_PATH          : derives
@@ -1326,7 +1326,6 @@ erDiagram
         string target_objective
         int    motivation
         int    resources
-        int    pertinence
         int    activity
         bool   selected
         string justification
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index dc6f3a23e..3e3526fbd 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -926,6 +926,7 @@
 	"ebiosRmMatrixHelpText": "Risk matrix used as a reference for the study. Defaults to `urn:intuitem:risk:library:risk-matrix-4x4-ebios-rm`",
 	"activityOne": "Activity 1",
 	"activityTwo": "Activity 2",
+	"activityThree": "Activity 3",
 	"ebiosRmStudy": "Ebios RM study",
 	"qualifications": "Qualifications",
 	"impacts": "Impacts",
@@ -980,7 +981,10 @@
 	"attackPaths": "Attack paths",
 	"currentCriticality": "Current criticality",
 	"residualCriticality": "Residual criticality",
-	"errorAssetGraphMustNotContainCycles": "The asset graph must not contain cycles.",
+	"notSelected": "Not selected",
+	"identifyRoTo": "Identify RO/TO",
+	"evaluateRoTo": "Evaluate RO/TO",
+	"selectRoTo": "Select RO/TO",
 	"resetPasswordHere": "You can reset your password here.",
 	"resetPassword": "Reset password",
 	"ebiosRm": "Ebios RM",
diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte
index e30978a25..8ca71675b 100644
--- a/frontend/src/lib/components/Forms/ModelForm.svelte
+++ b/frontend/src/lib/components/Forms/ModelForm.svelte
@@ -267,7 +267,7 @@
 	{:else if URLModel === 'feared-events'}
 		<FearedEventForm {form} {model} {cacheLocks} {formDataCache} {initialData} />
 	{:else if URLModel === 'ro-to'}
-		<RoToForm {form} {model} {cacheLocks} {formDataCache} {initialData} />
+		<RoToForm {form} {model} {cacheLocks} {formDataCache} {initialData} {context} />
 	{:else if URLModel === 'stakeholders'}
 		<StakeholderForm {form} {model} {cacheLocks} {formDataCache} {context} />
 	{:else if URLModel === 'attack-paths'}
diff --git a/frontend/src/lib/components/Forms/ModelForm/RoToForm.svelte b/frontend/src/lib/components/Forms/ModelForm/RoToForm.svelte
index eee24fba6..a78db0a56 100644
--- a/frontend/src/lib/components/Forms/ModelForm/RoToForm.svelte
+++ b/frontend/src/lib/components/Forms/ModelForm/RoToForm.svelte
@@ -8,12 +8,27 @@
 	import { getOptions } from '$lib/utils/crud';
 	import TextArea from '../TextArea.svelte';
 	import NumberField from '../NumberField.svelte';
+	import { page } from '$app/stores';
 
 	export let form: SuperValidated<any>;
 	export let model: ModelInfo;
 	export let cacheLocks: Record<string, CacheLock> = {};
 	export let formDataCache: Record<string, any> = {};
 	export let initialData: Record<string, any> = {};
+	export let context: string;
+
+	const activityBackground = context === 'edit' ? 'bg-white' : 'bg-surface-100-800-token';
+
+	let activeActivity: string | null = null;
+	$page.url.searchParams.forEach((value, key) => {
+		if (key === 'activity' && value === 'one') {
+			activeActivity = 'one';
+		} else if (key === 'activity' && value === 'two') {
+			activeActivity = 'two';
+		} else if (key === 'activity' && value === 'three') {
+			activeActivity = 'three';
+		}
+	});
 </script>
 
 <AutocompleteSelect
@@ -25,68 +40,107 @@
 	label={m.ebiosRmStudy()}
 	hidden={initialData.ebios_rm_study}
 />
-<AutocompleteSelect
-	multiple
-	{form}
-	options={getOptions({
-		objects: model.foreignKeys['feared_events'],
-		extra_fields: [['folder', 'str']],
-		label: 'auto'
-	})}
-	field="feared_events"
-	label={m.fearedEvents()}
-/>
-<Select
-	{form}
-	options={model.selectOptions['risk-origin']}
-	field="risk_origin"
-	label={m.riskOrigin()}
-	cacheLock={cacheLocks['risk_origin']}
-	bind:cachedValue={formDataCache['risk_origin']}
-/>
-<TextArea
-	{form}
-	field="target_objective"
-	label={m.targetObjective()}
-	cacheLock={cacheLocks['target_objective']}
-	bind:cachedValue={formDataCache['target_objective']}
-/>
-<Select
-	{form}
-	options={model.selectOptions['motivation']}
-	field="motivation"
-	label={m.motivation()}
-	cacheLock={cacheLocks['motivation']}
-	bind:cachedValue={formDataCache['motivation']}
-/>
-<Select
-	{form}
-	options={model.selectOptions['resources']}
-	field="resources"
-	label={m.resources()}
-	cacheLock={cacheLocks['resources']}
-	bind:cachedValue={formDataCache['resources']}
-/>
-<Select
-	{form}
-	options={model.selectOptions['pertinence']}
-	field="pertinence"
-	label={m.pertinence()}
-	cacheLock={cacheLocks['pertinence']}
-	bind:cachedValue={formDataCache['pertinence']}
-/>
-<NumberField
-	{form}
-	field="activity"
-	label={m.activity()}
-	cacheLock={cacheLocks['activity']}
-	bind:cachedValue={formDataCache['activity']}
-/>
-<Checkbox {form} field="is_selected" label={m.isSelected()} />
-<TextArea
-	{form}
-	field="justification"
-	label={m.justification()}
-	cacheLock={cacheLocks['justification']}
-	bind:cachedValue={formDataCache['justification']}
-/>
+<div
+	class="relative p-2 space-y-2 rounded-md {activeActivity === 'one'
+		? 'border-2 border-primary-500'
+		: 'border-2 border-gray-300 border-dashed'}"
+>
+	<p
+		class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'one'
+			? 'text-primary-500'
+			: 'text-gray-500'}"
+	>
+		{m.activityOne()}
+	</p>
+	<Select
+		{form}
+		options={model.selectOptions['risk-origin']}
+		field="risk_origin"
+		label={m.riskOrigin()}
+		cacheLock={cacheLocks['risk_origin']}
+		bind:cachedValue={formDataCache['risk_origin']}
+	/>
+	<TextArea
+		{form}
+		field="target_objective"
+		label={m.targetObjective()}
+		cacheLock={cacheLocks['target_objective']}
+		bind:cachedValue={formDataCache['target_objective']}
+	/>
+</div>
+<div
+	class="relative p-2 space-y-2 rounded-md {activeActivity === 'two'
+		? 'border-2 border-primary-500'
+		: 'border-2 border-gray-300 border-dashed'}"
+>
+	<p
+		class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'two'
+			? 'text-primary-500'
+			: 'text-gray-500'}"
+	>
+		{m.activityTwo()}
+	</p>
+	<Select
+		{form}
+		options={model.selectOptions['motivation']}
+		field="motivation"
+		label={m.motivation()}
+		cacheLock={cacheLocks['motivation']}
+		bind:cachedValue={formDataCache['motivation']}
+	/>
+	<Select
+		{form}
+		options={model.selectOptions['resources']}
+		field="resources"
+		label={m.resources()}
+		cacheLock={cacheLocks['resources']}
+		bind:cachedValue={formDataCache['resources']}
+	/>
+	<!-- <Select
+		{form}
+		options={model.selectOptions['pertinence']}
+		field="pertinence"
+		label={m.pertinence()}
+		cacheLock={cacheLocks['pertinence']}
+		bind:cachedValue={formDataCache['pertinence']}
+	/> -->
+	<NumberField
+		{form}
+		field="activity"
+		label={m.activity()}
+		cacheLock={cacheLocks['activity']}
+		bind:cachedValue={formDataCache['activity']}
+	/>
+</div>
+<div
+	class="relative p-2 space-y-2 rounded-md {activeActivity === 'three'
+		? 'border-2 border-primary-500'
+		: 'border-2 border-gray-300 border-dashed'}"
+>
+	<p
+		class="absolute -top-3 {activityBackground} font-bold {activeActivity === 'three'
+			? 'text-primary-500'
+			: 'text-gray-500'}"
+	>
+		{m.activityThree()}
+	</p>
+	<Checkbox {form} field="is_selected" label={m.isSelected()} />
+	<AutocompleteSelect
+		multiple
+		{form}
+		options={getOptions({
+			objects: model.foreignKeys['feared_events'],
+			extra_fields: [['folder', 'str']],
+			label: 'auto'
+		})}
+		field="feared_events"
+		label={m.fearedEvents()}
+	/>
+	<TextArea
+		{form}
+		field="justification"
+		label={m.justification()}
+		cacheLock={cacheLocks['justification']}
+		bind:cachedValue={formDataCache['justification']}
+	/>
+</div>
diff --git a/frontend/src/lib/components/ModelTable/ModelTable.svelte b/frontend/src/lib/components/ModelTable/ModelTable.svelte
index 6cd242326..5b8a25b8c 100644
--- a/frontend/src/lib/components/ModelTable/ModelTable.svelte
+++ b/frontend/src/lib/components/ModelTable/ModelTable.svelte
@@ -42,6 +42,7 @@
 	// Props (styles)
 	export let element: CssClasses = 'table';
 	export let text: CssClasses = 'text-xs';
+	export let backgroundColor: CssClasses = 'bg-white';
 	export let color: CssClasses = '';
 	export let regionHead: CssClasses = '';
 	export let regionHeadCell: CssClasses = 'uppercase bg-white text-gray-700';
@@ -94,7 +95,7 @@
 	// Replace $$props.class with classProp for compatibility
 	let classProp = ''; // Replacing $$props.class
 
-	$: classesBase = `${classProp || 'bg-white'}`;
+	$: classesBase = `${classProp || backgroundColor}`;
 	$: classesTable = `${element} ${text} ${color}`;
 
 	import { goto } from '$app/navigation';
diff --git a/frontend/src/lib/utils/crud.ts b/frontend/src/lib/utils/crud.ts
index 6bbdd4862..b9a6d9357 100644
--- a/frontend/src/lib/utils/crud.ts
+++ b/frontend/src/lib/utils/crud.ts
@@ -105,6 +105,7 @@ interface ForeignKeyField {
 	urlModel: urlModel;
 	endpointUrl?: string;
 	urlParams?: string;
+	detail?: boolean;
 }
 
 interface Field {
@@ -624,7 +625,7 @@ export const URL_MODEL_MAP: ModelMap = {
 		verboseNamePlural: 'Feared events',
 		foreignKeyFields: [
 			{ field: 'ebios_rm_study', urlModel: 'ebios-rm', endpointUrl: 'ebios-rm/studies' },
-			{ field: 'assets', urlModel: 'assets', urlParams: 'ebios_rm_studies=' },
+			{ field: 'assets', urlModel: 'assets', urlParams: 'ebios_rm_studies=', detail: true },
 			{ field: 'qualifications', urlModel: 'qualifications' }
 		],
 		selectFields: [{ field: 'gravity', valueType: 'number', detail: true }]
@@ -638,13 +639,18 @@ export const URL_MODEL_MAP: ModelMap = {
 		verboseNamePlural: 'Ro to',
 		foreignKeyFields: [
 			{ field: 'ebios_rm_study', urlModel: 'ebios-rm', endpointUrl: 'ebios-rm/studies' },
-			{ field: 'feared_events', urlModel: 'feared-events', endpointUrl: 'ebios-rm/feared-events' }
+			{
+				field: 'feared_events',
+				urlModel: 'feared-events',
+				endpointUrl: 'ebios-rm/feared-events',
+				urlParams: 'ebios_rm_study=',
+				detail: true
+			}
 		],
 		selectFields: [
 			{ field: 'risk-origin' },
 			{ field: 'motivation', valueType: 'number' },
-			{ field: 'resources', valueType: 'number' },
-			{ field: 'pertinence', valueType: 'number' }
+			{ field: 'resources', valueType: 'number' }
 		]
 	},
 	stakeholders: {
diff --git a/frontend/src/lib/utils/load.ts b/frontend/src/lib/utils/load.ts
index bc4b2838d..893b34068 100644
--- a/frontend/src/lib/utils/load.ts
+++ b/frontend/src/lib/utils/load.ts
@@ -43,7 +43,7 @@ export const loadDetail = async ({ event, model, id }) => {
 		const initialData = {};
 		await Promise.all(
 			model.reverseForeignKeyFields.map(async (e) => {
-				const relEndpoint = `${BASE_API_URL}/${e.urlModel}/?${e.field}=${event.params.id}`;
+				const relEndpoint = `${BASE_API_URL}/${e.endpointUrl || e.urlModel}/?${e.field}=${event.params.id}`;
 				const res = await event.fetch(relEndpoint);
 				const revData = await res.json().then((res) => res.results);
 
@@ -103,7 +103,7 @@ export const loadDetail = async ({ event, model, id }) => {
 				if (info.selectFields) {
 					await Promise.all(
 						info.selectFields.map(async (selectField) => {
-							const url = `${BASE_API_URL}/${urlModel}/${selectField.field}/`;
+							const url = `${BASE_API_URL}/${e.endpointUrl || e.urlModel}/${selectField.field}/`;
 							const response = await event.fetch(url);
 							if (response.ok) {
 								selectOptions[selectField.field] = await response.json().then((data) =>
diff --git a/frontend/src/lib/utils/schemas.ts b/frontend/src/lib/utils/schemas.ts
index c26536b1e..7872fa503 100644
--- a/frontend/src/lib/utils/schemas.ts
+++ b/frontend/src/lib/utils/schemas.ts
@@ -418,12 +418,11 @@ export const fearedEventsSchema = z.object({
 
 export const roToSchema = z.object({
 	ebios_rm_study: z.string(),
-	feared_events: z.string().uuid().array(),
+	feared_events: z.string().uuid().optional().array().optional(),
 	risk_origin: z.string(),
 	target_objective: z.string(),
 	motivation: z.number().default(0).optional(),
 	resources: z.number().default(0).optional(),
-	pertinence: z.number().default(0).optional(),
 	activity: z.number().min(0).max(4).optional().default(0),
 	is_selected: z.boolean().optional().default(false),
 	justification: z.string().optional()
diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte
index 105e5989c..a39ec4d84 100644
--- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte
+++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/+page.svelte
@@ -45,17 +45,17 @@
 			{
 				title: safeTranslate(m.ebiosWs2_1()),
 				status: 'to_do',
-				href: `${$page.url.pathname}/workshop-two/ro-to?next=${$page.url.pathname}`
+				href: `${$page.url.pathname}/workshop-two/ro-to?activity=one&next=${$page.url.pathname}`
 			},
 			{
 				title: safeTranslate(m.ebiosWs2_2()),
 				status: 'to_do',
-				href: `${$page.url.pathname}/workshop-two/ro-to?next=${$page.url.pathname}`
+				href: `${$page.url.pathname}/workshop-two/ro-to?activity=two&next=${$page.url.pathname}`
 			},
 			{
 				title: safeTranslate(m.ebiosWs2_3()),
 				status: 'to_do',
-				href: `${$page.url.pathname}/workshop-two/ro-to?next=${$page.url.pathname}`
+				href: `${$page.url.pathname}/workshop-two/ro-to?activity=three&next=${$page.url.pathname}`
 			}
 		],
 		ws3: [
diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.svelte
index aef2b1594..55c599eed 100644
--- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.svelte
+++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/ebios-rm-study/+page.svelte
@@ -105,7 +105,9 @@
 					: 'text-gray-500'}">{m.activityOne()}</span
 			>
 			{#if ebiosRmStudy.description}
-				<p class="text-gray-600">{ebiosRmStudy.description}</p>
+				<p class="text-gray-600 whitespace-pre-wrap text-justify w-full">
+					{ebiosRmStudy.description}
+				</p>
 			{:else}
 				<p class="text-gray-600">{m.noDescription()}</p>
 			{/if}
@@ -117,7 +119,7 @@
 				<ul class="list-disc list-inside text-gray-600">
 					{#if ebiosRmStudy.authors?.length}
 						{#each ebiosRmStudy.authors as author}
-							<li>{author.str}</li>
+							<li><a class="anchor" href="/users/{author.id}">{author.str}</a></li>
 						{/each}
 					{:else}
 						<li>{m.noAuthor()}</li>
@@ -132,7 +134,7 @@
 				<ul class="list-disc list-inside text-gray-600">
 					{#if ebiosRmStudy.reviewers?.length}
 						{#each ebiosRmStudy.reviewers as reviewer}
-							<li>{reviewer.str}</li>
+							<li><a class="anchor" href="/users/{reviewer.id}">{reviewer.str}</a></li>
 						{/each}
 					{:else}
 						<li>{m.noReviewer()}</li>
diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.server.ts
index 257a7a783..09d7e7047 100644
--- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.server.ts
+++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-one/feared-events/+page.server.ts
@@ -49,14 +49,12 @@ export const load: PageServerLoad = async ({ params, fetch }) => {
 
 	for (const keyField of foreignKeyFields) {
 		const model = getModelInfo(keyField.urlModel);
-		const queryParams = keyField.urlParams ? `?${keyField.urlParams}` : '';
+		const queryParams = keyField.urlParams
+			? `?${keyField.urlParams}${keyField.detail ? params.id : ''}`
+			: '';
 		let url = model.endpointUrl
 			? `${BASE_API_URL}/${model.endpointUrl}/${queryParams}`
 			: `${BASE_API_URL}/${model.urlModel}/${queryParams}`;
-		if (model.urlModel === 'assets') {
-			url = `${BASE_API_URL}/${model.urlModel}/${queryParams}${params.id}`;
-			console.log(url);
-		}
 		const response = await fetch(url);
 		if (response.ok) {
 			foreignKeys[keyField.field] = await response.json().then((data) => data.results);
diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.server.ts b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.server.ts
index fdad14f79..8ff2e8aeb 100644
--- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.server.ts
+++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.server.ts
@@ -32,7 +32,9 @@ export const load: PageServerLoad = async ({ params, fetch }) => {
 
 	for (const keyField of foreignKeyFields) {
 		const keyModel = getModelInfo(keyField.urlModel);
-		const queryParams = keyField.urlParams ? `?${keyField.urlParams}` : '';
+		const queryParams = keyField.urlParams
+			? `?${keyField.urlParams}${keyField.detail ? params.id : ''}`
+			: '';
 		const url = `${BASE_API_URL}/${keyModel.endpointUrl ?? keyModel.urlModel}/${queryParams}`;
 		const response = await fetch(url);
 		if (response.ok) {
diff --git a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.svelte b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.svelte
index 08ebedae8..772da6e6e 100644
--- a/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.svelte
+++ b/frontend/src/routes/(app)/(internal)/ebios-rm/[id=uuid]/workshop-two/ro-to/+page.svelte
@@ -8,6 +8,7 @@
 	import MissingConstraintsModal from '$lib/components/Modals/MissingConstraintsModal.svelte';
 	import { checkConstraints } from '$lib/utils/crud';
 	import * as m from '$paraglide/messages.js';
+	import { page } from '$app/stores';
 
 	const modalStore: ModalStore = getModalStore();
 
@@ -50,9 +51,25 @@
 		}
 		modalStore.trigger(modal);
 	}
+
+	let activeActivity: string | null = null;
+	$page.url.searchParams.forEach((value, key) => {
+		if (key === 'activity' && value === 'one') {
+			activeActivity = 'one';
+		} else if (key === 'activity' && value === 'two') {
+			activeActivity = 'two';
+		} else if (key === 'activity' && value === 'three') {
+			activeActivity = 'three';
+		}
+	});
 </script>
 
-<ModelTable source={data.table} deleteForm={data.deleteForm} {URLModel}>
+<ModelTable
+	source={data.table}
+	deleteForm={data.deleteForm}
+	{URLModel}
+	detailQueryParameter={`activity=${activeActivity}`}
+>
 	<div slot="addButton">
 		<span class="inline-flex overflow-hidden rounded-md border bg-white shadow-sm">
 			<button
diff --git a/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.server.ts
new file mode 100644
index 000000000..e3c9c06ba
--- /dev/null
+++ b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.server.ts
@@ -0,0 +1,37 @@
+import type { PageServerLoad } from './$types';
+import { BASE_API_URL } from '$lib/utils/constants';
+import { listViewFields } from '$lib/utils/table';
+import { tableSourceMapper, type TableSource } from '@skeletonlabs/skeleton';
+import { getModelInfo } from '$lib/utils/crud';
+
+export const load: PageServerLoad = async (event) => {
+	const URLModel = 'ro-to';
+	const model = getModelInfo(URLModel);
+	const endpoint = `${BASE_API_URL}/${model.endpointUrl}/${event.params.id}/`;
+	const response = await event.fetch(endpoint);
+	const data = await response.json();
+
+	const relEndpoint = `${BASE_API_URL}/ebios-rm/feared-events?ro_to_couples=${event.params.id}`;
+	const res = await event.fetch(relEndpoint);
+	const revData = await res.json().then((res) => res.results);
+
+	const tableFieldsRef = listViewFields['feared-events'];
+	const tableFields = {
+		head: [...tableFieldsRef.head],
+		body: [...tableFieldsRef.body]
+	};
+	const index = tableFields.body.indexOf('ro_to_couples');
+	if (index > -1) {
+		tableFields.head.splice(index, 1);
+		tableFields.body.splice(index, 1);
+	}
+	const bodyData = tableSourceMapper(revData, tableFields.body);
+
+	const table: TableSource = {
+		head: tableFields.head,
+		body: bodyData,
+		meta: revData
+	};
+
+	return { data, table };
+};
diff --git a/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.svelte
new file mode 100644
index 000000000..6305659e3
--- /dev/null
+++ b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/+page.svelte
@@ -0,0 +1,165 @@
+<script lang="ts">
+	import type { PageData } from './$types';
+	import * as m from '$paraglide/messages';
+	import { page } from '$app/stores';
+	import { pageTitle } from '$lib/utils/stores';
+	import { safeTranslate } from '$lib/utils/i18n';
+	import ModelTable from '$lib/components/ModelTable/ModelTable.svelte';
+
+	export let data: PageData;
+
+	const roto = data.data;
+
+	pageTitle.set(roto.risk_origin + ' - ' + roto.target_objective);
+
+	let activeActivity: string | null = null;
+	$page.url.searchParams.forEach((value, key) => {
+		if (key === 'activity' && value === 'one') {
+			activeActivity = 'one';
+		} else if (key === 'activity' && value === 'two') {
+			activeActivity = 'two';
+		} else if (key === 'activity' && value === 'three') {
+			activeActivity = 'three';
+		}
+	});
+
+	const pertinenceColor = {
+		undefined: 'bg-gray-200 text-gray-700',
+		irrelevant: 'bg-green-200 text-green-700',
+		'partially relevant': 'bg-yellow-200 text-yellow-700',
+		fairly_relevant: 'bg-orange-200 text-orange-700',
+		higly_relevant: 'bg-red-200 text-red-700'
+	};
+</script>
+
+<div class="card p-4 bg-white shadow-lg">
+	<div class="flex flex-col space-y-4">
+		<div
+			id="activityOne"
+			class="relative p-4 space-y-4 rounded-md w-full flex flex-col items-center justify-center
+                {activeActivity === 'one'
+				? 'border-2 border-primary-500'
+				: 'border-2 border-gray-300 border-dashed'}"
+		>
+			<span
+				class="absolute -top-3 bg-white font-bold {activeActivity === 'one'
+					? 'text-primary-500'
+					: 'text-gray-500'}">{m.activityOne()}</span
+			>
+			<h1
+				class="font-bold text-xl {activeActivity === 'one' ? 'text-primary-500' : 'text-gray-500'}"
+			>
+				{m.identifyRoTo()}
+			</h1>
+			<div class="flex flex-row space-x-1">
+				<p class="flex flex-col items-center">
+					<span class="text-xs text-gray-500">{m.riskOrigin()}</span>
+					<span class="font-bold">{roto.risk_origin} /</span>
+				</p>
+				<p class="flex flex-col items-center">
+					<span class="text-xs text-gray-500">{m.targetObjective()}</span>
+					<span class="font-bold">{roto.target_objective}</span>
+				</p>
+			</div>
+			<a
+				href={`${$page.url.pathname}/edit?activity=${activeActivity}&next=${$page.url.pathname}?activity=${activeActivity}`}
+				class="btn variant-filled-primary h-fit absolute top-2 right-4"
+			>
+				<i class="fa-solid fa-pen-to-square mr-2" data-testid="edit-button" />
+				{m.edit()}
+			</a>
+		</div>
+		<div
+			id="activityTwo"
+			class="relative p-4 space-y-4 rounded-md w-full flex flex-col items-center
+                {activeActivity === 'two'
+				? 'border-2 border-primary-500'
+				: 'border-2 border-gray-300 border-dashed'}"
+		>
+			<span
+				class="absolute -top-3 bg-white font-bold {activeActivity === 'two'
+					? 'text-primary-500'
+					: 'text-gray-500'}">{m.activityTwo()}</span
+			>
+			<h1
+				class="font-bold text-xl {activeActivity === 'two' ? 'text-primary-500' : 'text-gray-500'}"
+			>
+				{m.evaluateRoTo()}
+			</h1>
+			<div class="flex space-x-6">
+				<p class="flex flex-col items-center">
+					<span class="text-xs text-gray-500">{m.motivation()}</span>
+					<span class="badge text-sm font-bold">{safeTranslate(roto.motivation)}</span>
+				</p>
+				<span>x</span>
+				<p class="flex flex-col items-center">
+					<span class="text-xs text-gray-500">{m.resources()}</span>
+					<span class="badge text-sm font-bold">{safeTranslate(roto.resources)}</span>
+				</p>
+				<span>=</span>
+				<p class="flex flex-col items-center">
+					<span class="text-xs text-gray-500">{m.pertinence()}</span>
+					<span class="badge text-sm font-bold {pertinenceColor[roto.pertinence]}"
+						>{safeTranslate(roto.pertinence)}</span
+					>
+				</p>
+			</div>
+			<p>
+				<span class="badge bg-violet-200 text-violet-700">{m.activity()}</span>
+				<span>=</span>
+				<span class="font-bold">{roto.activity}</span>
+			</p>
+		</div>
+		<div
+			id="activityThree"
+			class="relative p-4 space-y-4 rounded-md w-full flex flex-col items-center
+                {activeActivity === 'three'
+				? 'border-2 border-primary-500'
+				: 'border-2 border-gray-300 border-dashed'}"
+		>
+			<span
+				class="absolute -top-3 bg-white font-bold {activeActivity === 'three'
+					? 'text-primary-500'
+					: 'text-gray-500'}">{m.activityThree()}</span
+			>
+			<h1
+				class="font-bold text-xl {activeActivity === 'three'
+					? 'text-primary-500'
+					: 'text-gray-500'}"
+			>
+				{m.selectRoTo()}
+			</h1>
+			<p>
+				{#if roto.is_selected}
+					<span class="badge bg-green-200 text-green-700">{m.selected()}</span>
+				{:else}
+					<span class="badge bg-red-200 text-red-700">{m.notSelected()}</span>
+				{/if}
+			</p>
+			<div class="w-full p-4 bg-gray-50 border rounded-md shadow-sm">
+				<h3 class="font-semibold text-lg text-gray-700 flex items-center space-x-2">
+					<i class="fa-solid fa-table text-gray-500 opacity-75"></i>
+					<span>{m.fearedEvents()}</span>
+				</h3>
+				<ModelTable
+					backgroundColor="bg-gray-50"
+					regionBody="bg-gray-50"
+					regionHeadCell="uppercase bg-gray-50 text-gray-700"
+					source={data.table}
+					URLModel="feared-events"
+				></ModelTable>
+			</div>
+			<div class="w-full p-4 bg-gray-50 border rounded-md shadow-sm">
+				<h3 class="font-semibold text-lg text-gray-700 flex items-center space-x-2">
+					<i class="fa-solid fa-eye text-gray-500 opacity-75"></i>
+					<span>{m.justification()}</span>
+				</h3>
+				{#if roto.justification}
+					<p class="text-gray-600">{roto.justification}</p>
+				{:else}
+					<p class="text-gray-600">{m.noJustification()}</p>
+				{/if}
+			</div>
+		</div>
+	</div>
+</div>
diff --git a/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.server.ts
new file mode 100644
index 000000000..0c9e15c11
--- /dev/null
+++ b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.server.ts
@@ -0,0 +1,67 @@
+import { BASE_API_URL } from '$lib/utils/constants';
+import { getModelInfo } from '$lib/utils/crud';
+import { modelSchema } from '$lib/utils/schemas';
+import { superValidate } from 'sveltekit-superforms';
+import { zod } from 'sveltekit-superforms/adapters';
+import type { PageServerLoad, Actions } from '../$types';
+import { defaultWriteFormAction } from '$lib/utils/actions';
+
+export const load: PageServerLoad = async (event) => {
+	const URLModel = 'ro-to';
+	const model = getModelInfo(URLModel);
+	const schema = modelSchema(URLModel);
+	const objectEndpoint = `${BASE_API_URL}/${model.endpointUrl}/${event.params.id}/object/`;
+	const objectResponse = await event.fetch(objectEndpoint);
+	const object = await objectResponse.json();
+
+	const form = await superValidate(object, zod(schema), { errors: false });
+	const foreignKeyFields = model.foreignKeyFields;
+	const selectFields = model.selectFields;
+
+	const foreignKeys: Record<string, any> = {};
+
+	if (foreignKeyFields) {
+		for (const keyField of foreignKeyFields) {
+			const queryParams = keyField.urlParams
+				? `?${keyField.urlParams}${keyField.detail ? object.ebios_rm_study : ''}`
+				: '';
+			const url = `${BASE_API_URL}/${keyField.endpointUrl || keyField.urlModel}/${queryParams}`;
+			const response = await event.fetch(url);
+			if (response.ok) {
+				foreignKeys[keyField.field] = await response.json().then((data) => data.results);
+			} else {
+				console.error(`Failed to fetch data for ${keyField.field}: ${response.statusText}`);
+			}
+		}
+	}
+
+	const selectOptions: Record<string, any> = {};
+
+	if (selectFields) {
+		for (const selectField of selectFields) {
+			const url = `${BASE_API_URL}/${model.endpointUrl}/${
+				selectField.detail ? event.params.id + '/' : ''
+			}${selectField.field}/`;
+			const response = await event.fetch(url);
+			if (response.ok) {
+				selectOptions[selectField.field] = await response.json().then((data) =>
+					Object.entries(data).map(([key, value]) => ({
+						label: value,
+						value: selectField.valueType === 'number' ? parseInt(key) : key
+					}))
+				);
+			} else {
+				console.error(`Failed to fetch data for ${selectField.field}: ${response.statusText}`);
+			}
+		}
+	}
+	model.foreignKeys = foreignKeys;
+	model.selectOptions = selectOptions;
+	return { form, model, object, foreignKeys, selectOptions, URLModel };
+};
+
+export const actions: Actions = {
+	default: async (event) => {
+		return defaultWriteFormAction({ event, urlModel: 'ro-to', action: 'edit' });
+	}
+};
diff --git a/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.svelte b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.svelte
new file mode 100644
index 000000000..ec44e5c21
--- /dev/null
+++ b/frontend/src/routes/(app)/(internal)/ro-to/[id=uuid]/edit/+page.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+	import type { PageData } from './$types';
+	import ModelForm from '$lib/components/Forms/ModelForm.svelte';
+	export let data: PageData;
+</script>
+
+<div class="card p-4 bg-white shadow-lg">
+	<ModelForm
+		customNameDescription
+		form={data.form}
+		object={data.object}
+		selectOptions={data.selectOptions}
+		foreignKeys={data.foreignKeys}
+		model={data.model}
+		context="edit"
+	/>
+</div>