Skip to content

Commit 20aee81

Browse files
authored
Merge pull request #22 from redlink-gmbh/MORE-Platform#390-error-handling-and-action-handling-for-goals
MORE-Platform#322: add 409 error handling for goal meassurements and categories
2 parents 130bdc7 + 195654f commit 20aee81

9 files changed

Lines changed: 181 additions & 43 deletions

File tree

src/components/GoalList.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@
419419
factory: factoryForType(component?.type),
420420
componentType: 'goalTemplate',
421421
hasComponentCategories: categoryTopicOptions.value,
422+
hasComponentKind: component?.categoryKind,
422423
errorMessage: component?.errorMessage,
423424
hasSimpleScheduler: goalConfig.value?.schedule?.map((item) => {
424425
return {
@@ -513,11 +514,15 @@
513514
</div>
514515
</div>
515516
<div class="global-goal-settings">
516-
<GoalTemplateMessurementSection :study-id="studyId" class="mb-8" />
517+
<GoalTemplateMessurementSection
518+
:study-id="studyId"
519+
class="mb-8"
520+
:actions-disabled="!actionsVisible" />
517521
<GoalTemplateCategorySection
518522
class="mb-8"
519523
:study-id="studyId"
520524
:study-status="studyStatus"
525+
:actions-disabled="!actionsVisible"
521526
/>
522527
</div>
523528
</div>

src/components/dialog/ComponentDialog.vue

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Licensed under the Elastic License 2.0. */
3636
import { extractCurrentLimeDomain } from '@/utils/limeSurveyUtils';
3737
import { scrollToFirstError } from '@/utils/componentUtils';
3838
import InfoWarningErrorSection from '@/components/shared/InfoWarningErrorSection.vue';
39+
import PillItem from '@/components/shared/PillItem.vue';
3940
4041
const { handleToastErrors, showErrorToast } = useToastService();
4142
const dialog = useDialog();
@@ -65,6 +66,7 @@ Licensed under the Elastic License 2.0. */
6566
const simpleSchedulerValues = dialogRef.value.data.simpleSchedulerValues;
6667
const hasComponentCategories = dialogRef.value.data
6768
.hasComponentCategories as MoreTableChoice[];
69+
const hasComponentKind = dialogRef.value.data.hasComponentKind as string;
6870
const editable =
6971
studyStore.study.status === StudyStatus.Draft ||
7072
studyStore.study.status === StudyStatus.Paused ||
@@ -265,7 +267,7 @@ Licensed under the Elastic License 2.0. */
265267
.map((s) => s.key) ?? [],
266268
studyGroupId: studyGroupId.value,
267269
categories: {
268-
kind: component.categories?.kind ?? 'behavioral',
270+
kind: hasComponentKind ?? 'behavioral',
269271
topics: categories.value,
270272
},
271273
};
@@ -339,7 +341,7 @@ Licensed under the Elastic License 2.0. */
339341

340342
<info-warning-error-section
341343
class="mb-4"
342-
:error-message="errorMessage"
344+
:error-message="errorMessage || undefined"
343345
:error-label="t('global.labels.error')"
344346
/>
345347

@@ -379,16 +381,30 @@ Licensed under the Elastic License 2.0. */
379381
<div v-if="getError('categories')" class="error error-label mb-4">
380382
{{ getError('categories') }}
381383
</div>
382-
<MultiSelect
383-
v-model="categories"
384-
:options="hasComponentCategories"
385-
option-label="label"
386-
option-value="value"
387-
class="w-full"
388-
:show-toggle-all="false"
389-
:disabled="!editable"
390-
:placeholder="$t('global.placeholder.chooseDropdownOptionDefault')"
391-
></MultiSelect>
384+
<div class="flex flex-row flex-nowrap items-center gap-6 text-nowrap">
385+
<div class="flex w-full min-w-0 items-center gap-2">
386+
<span class="font-bold whitespace-nowrap">{{t('goaltemplate.props.categoryTitle')}}:</span>
387+
<MultiSelect
388+
v-model="categories"
389+
:options="hasComponentCategories"
390+
option-label="label"
391+
option-value="value"
392+
class="w-full"
393+
:show-toggle-all="false"
394+
:disabled="!editable"
395+
:placeholder="
396+
$t('global.placeholder.chooseDropdownOptionDefault')
397+
"
398+
></MultiSelect>
399+
</div>
400+
<div
401+
v-if="hasComponentCategories"
402+
class="flex items-center gap-2 whitespace-nowrap"
403+
>
404+
<span class="font-bold">{{t('goaltemplate..props.goalType')}}: </span>
405+
<pill-item :label="hasComponentKind" />
406+
</div>
407+
</div>
392408
</div>
393409
<div v-if="hasSimpleScheduler" class="col-span-8 col-start-0">
394410
<h5 class="mb-1">{{ $t('scheduler.singular') }}*</h5>

src/components/dialog/DeleteMoreTableRowDialog.vue

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ Prevention -- A research institute of the Ludwig Boltzmann Gesellschaft,
44
Oesterreichische Vereinigung zur Foerderung der wissenschaftlichen Forschung).
55
Licensed under the Elastic License 2.0. */
66
<script setup lang="ts">
7-
import { inject } from 'vue';
7+
import { inject, ref } from 'vue';
88
import Button from 'primevue/button';
9+
import InfoWarningErrorSection from '@/components/shared/InfoWarningErrorSection.vue';
910
import WarningSection from './shared/WarningSection.vue';
1011
1112
const dialogRef: any = inject('dialogRef');
@@ -16,9 +17,31 @@ Licensed under the Elastic License 2.0. */
1617
const elTitle: string = dialogRef?.value?.data?.elTitle;
1718
const elInfoTitle: string = dialogRef?.value?.data?.elInfoTitle;
1819
const elInfoDesc: string = dialogRef?.value?.data?.elInfoDesc;
20+
const onDelete: ((row: any) => Promise<void>) | undefined =
21+
dialogRef?.value?.data?.onDelete;
1922
20-
function deleteRowEl(): void {
21-
dialogRef.value.close(row);
23+
const errorMessage = ref<string | null>(null);
24+
const isLoading = ref(false);
25+
26+
async function deleteRowEl(): Promise<void> {
27+
if (onDelete) {
28+
errorMessage.value = null;
29+
isLoading.value = true;
30+
try {
31+
await onDelete(row);
32+
dialogRef.value.close(row);
33+
} catch (error: any) {
34+
if (error.response?.status === 409) {
35+
errorMessage.value = error.errorMessage || 'Conflict';
36+
} else {
37+
errorMessage.value = 'An error occurred';
38+
}
39+
} finally {
40+
isLoading.value = false;
41+
}
42+
} else {
43+
dialogRef.value.close(row);
44+
}
2245
}
2346
2447
function closeDialog(): void {
@@ -37,19 +60,27 @@ Licensed under the Elastic License 2.0. */
3760
<div>{{ elInfoDesc }}</div>
3861
</div>
3962

63+
<info-warning-error-section
64+
v-if="errorMessage"
65+
class="mt-4"
66+
:error-message="errorMessage || undefined"
67+
/>
68+
4069
<WarningSection :confirm-msg="confirmMsg" :warning-msg="warningMsg" />
4170

4271
<div class="flex flex-row items-center justify-end">
4372
<Button
4473
type="button"
4574
class="p-button btn-gray !mr-3"
4675
:label="$t('global.labels.close')"
76+
:disabled="isLoading"
4777
@click="closeDialog"
4878
/>
4979
<Button
5080
type="button"
5181
class="p-button btn-important ml-2"
5282
:label="$t('global.labels.delete')"
83+
:loading="isLoading"
5384
@click="deleteRowEl"
5485
/>
5586
</div>

src/components/shared/InfoWarningErrorSection.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<script setup lang="ts">
22
interface Props {
33
isWarning?: boolean;
4-
errorMessage: string;
5-
errorLabel: string;
4+
errorMessage?: string;
5+
errorLabel?: string;
66
}
77
88
withDefaults(defineProps<Props>(), {
99
isWarning: false,
10+
errorMessage: '',
11+
errorLabel: '',
1012
});
1113
</script>
1214

@@ -16,11 +18,12 @@
1618
:class="[
1719
'rounded border px-4 py-2',
1820
isWarning
19-
? 'border-yellow-400 bg-yellow-50 p-4'
21+
? 'border-yellow-400 bg-yellow-50 pt-4'
2022
: 'border-red-700 bg-red-50',
2123
]"
2224
>
2325
<div
26+
v-if="errorLabel"
2427
:class="[
2528
'flex items-center gap-1',
2629
isWarning ? 'text-yellow-700' : 'text-red-700',
@@ -30,7 +33,6 @@
3033
</div>
3134
<div
3235
:class="[
33-
'mt-1',
3436
isWarning ? 'text-yellow-700' : 'text-red-700',
3537
]"
3638
>

src/components/shared/PillItem.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
interface Props {
3+
label: string;
4+
textColor?: string;
5+
bgColor?: string;
6+
}
7+
8+
defineProps<Props>();
9+
</script>
10+
11+
<template>
12+
<div
13+
:class="[
14+
'pill rounded border px-2 py-1',
15+
textColor
16+
? `border-${textColor} text-${textColor}`
17+
: 'border-gray-500 text-gray-500',
18+
bgColor ? `bg-${bgColor}` : 'bg-gray-100',
19+
]"
20+
>
21+
{{ label }}
22+
</div>
23+
</template>

src/components/subComponents/GoalTemplateCategorySection.vue

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import InputText from 'primevue/inputtext';
1414
import { GoalTopic, StudyRole, StudyStatus } from '@gs';
1515
import MoreTable from '@/components/shared/MoreTable.vue';
16+
import { useToastService } from '@/composable/toastService';
1617
import {
1718
MoreTableAction,
1819
MoreTableColumn,
@@ -26,11 +27,17 @@
2627
interface Props {
2728
studyId: number;
2829
studyStatus: StudyStatus;
30+
actionsDisabled?: boolean;
2931
}
3032
31-
const props = defineProps<Props>();
33+
const props = withDefaults(defineProps<Props>(), {
34+
actionsDisabled: false,
35+
});
36+
const { showErrorToast } = useToastService();
3237
33-
const { data: goalConfig } = useGoalConfig(props.studyId) as { data: { value: StudyGoalConfigData | undefined } };
38+
const { data: goalConfig } = useGoalConfig(props.studyId) as {
39+
data: { value: StudyGoalConfigData | undefined };
40+
};
3441
const { mutateAsync: createGoalTopicMutation } = useCreateGoalTopic();
3542
const { mutateAsync: updateGoalTopicMutation } = useUpdateGoalTopic();
3643
const { mutateAsync: deleteGoalTopicMutation } = useDeleteGoalTopic();
@@ -56,18 +63,31 @@
5663
description: newTopicDescription.value,
5764
} as any;
5865
59-
await createGoalTopicMutation({ studyId: props.studyId, topic: topic as GoalTopic })
60-
.then(() => {
61-
isOpen.value = false;
62-
overlayPanel.value?.hide();
63-
});
66+
await createGoalTopicMutation({
67+
studyId: props.studyId,
68+
topic: topic as GoalTopic,
69+
}).then(() => {
70+
isOpen.value = false;
71+
overlayPanel.value?.hide();
72+
});
6473
newTopicName.value = '';
6574
newTopicDescription.value = '';
6675
};
6776
6877
async function deleteTopic(topic: GoalTopic): Promise<void> {
6978
if (!topic.key) return;
70-
await deleteGoalTopicMutation({ studyId: props.studyId, key: topic.key });
79+
try {
80+
await deleteGoalTopicMutation({ studyId: props.studyId, key: topic.key });
81+
} catch (error: any) {
82+
if (error.response?.status === 409) {
83+
error.errorMessage = t(
84+
'goaltemplate.goalTemplateList.error.conflictUsedCategory',
85+
);
86+
} else {
87+
error.errorMessage = t('global.error.generic');
88+
}
89+
throw error;
90+
}
7191
}
7292
7393
const goalCategoryColumns: MoreTableColumn[] = [
@@ -108,7 +128,17 @@
108128
109129
function changeValueInPlace(topic: GoalTopic): void {
110130
if (topic && !!topic?.key) {
111-
updateGoalTopicMutation({ studyId: props.studyId, key: topic.key, topic: topic });
131+
updateGoalTopicMutation({
132+
studyId: props.studyId,
133+
key: topic.key,
134+
topic: topic,
135+
}).catch((error) => {
136+
if (error.response?.status === 409) {
137+
showErrorToast(
138+
t('goaltemplate.goalTemplateList.error.conflictUsedElement'),
139+
);
140+
}
141+
});
112142
}
113143
}
114144
@@ -138,6 +168,7 @@
138168
elTitle: row.title,
139169
elInfoTitle: t('global.labels.description'),
140170
elInfoDesc: row.description,
171+
onDelete: deleteTopic,
141172
},
142173
props: {
143174
header: t('goaltemplate.dialog.header.delete'),
@@ -152,11 +183,8 @@
152183
draggable: false,
153184
},
154185
onClose: (options) => {
155-
if (options?.data) {
156-
executeAction({
157-
id: 'delete',
158-
row: options.data,
159-
} as MoreTableRowActionResult);
186+
if (options?.data && !options?.data?.key) {
187+
console.info('category section was closed successfully')
160188
}
161189
},
162190
}),
@@ -174,6 +202,7 @@
174202
<Button
175203
type="button"
176204
class="flex shrink-0 items-center justify-between text-nowrap"
205+
:disabled="actionsDisabled"
177206
@click="toggleOverlay($event)"
178207
>
179208
<span>{{ t('goaltemplate.goalTemplateList.categories.add') }}</span>

0 commit comments

Comments
 (0)