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'} {:else if URLModel === 'ro-to'} - + {:else if URLModel === 'stakeholders'} {: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; export let model: ModelInfo; export let cacheLocks: Record = {}; export let formDataCache: Record = {}; export let initialData: Record = {}; + 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'; + } + }); - - - - - - - - - + + + {m.activityOne()} + + + + + + + {m.activityTwo()} + + + + + + + + + {m.activityThree()} + + + + + 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()} {#if ebiosRmStudy.description} - {ebiosRmStudy.description} + + {ebiosRmStudy.description} + {:else} {m.noDescription()} {/if} @@ -117,7 +119,7 @@ {#if ebiosRmStudy.authors?.length} {#each ebiosRmStudy.authors as author} - {author.str} + {author.str} {/each} {:else} {m.noAuthor()} @@ -132,7 +134,7 @@ {#if ebiosRmStudy.reviewers?.length} {#each ebiosRmStudy.reviewers as reviewer} - {reviewer.str} + {reviewer.str} {/each} {:else} {m.noReviewer()} 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'; + } + }); - + { + 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 @@ + + + + + + {m.activityOne()} + + {m.identifyRoTo()} + + + + {m.riskOrigin()} + {roto.risk_origin} / + + + {m.targetObjective()} + {roto.target_objective} + + + + + {m.edit()} + + + + {m.activityTwo()} + + {m.evaluateRoTo()} + + + + {m.motivation()} + {safeTranslate(roto.motivation)} + + x + + {m.resources()} + {safeTranslate(roto.resources)} + + = + + {m.pertinence()} + {safeTranslate(roto.pertinence)} + + + + {m.activity()} + = + {roto.activity} + + + + {m.activityThree()} + + {m.selectRoTo()} + + + {#if roto.is_selected} + {m.selected()} + {:else} + {m.notSelected()} + {/if} + + + + + {m.fearedEvents()} + + + + + + + {m.justification()} + + {#if roto.justification} + {roto.justification} + {:else} + {m.noJustification()} + {/if} + + + + 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 = {}; + + 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 = {}; + + 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 @@ + + + + +
+ {m.activityOne()} +
+ {m.activityTwo()} +
+ {m.activityThree()} +
{ebiosRmStudy.description}
+ {ebiosRmStudy.description} +
{m.noDescription()}
+ {m.riskOrigin()} + {roto.risk_origin} / +
+ {m.targetObjective()} + {roto.target_objective} +
+ {m.motivation()} + {safeTranslate(roto.motivation)} +
+ {m.resources()} + {safeTranslate(roto.resources)} +
+ {m.pertinence()} + {safeTranslate(roto.pertinence)} +
+ {m.activity()} + = + {roto.activity} +
+ {#if roto.is_selected} + {m.selected()} + {:else} + {m.notSelected()} + {/if} +
{roto.justification}
{m.noJustification()}