Skip to content

Commit

Permalink
#561 - feat: filter select options on co reviewer assignment dialog…
Browse files Browse the repository at this point in the history
… based on active selection
  • Loading branch information
svenvandescheur committed Dec 19, 2024
1 parent 1e0fa9b commit ac40d1c
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 29 deletions.
19 changes: 13 additions & 6 deletions backend/src/openarchiefbeheer/destruction/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,19 @@ def validate(self, attrs):
.assignees.filter(role=ListRole.co_reviewer)
.count()
)
if (
current_number_co_reviewers
+ len(attrs.get("add", []))
- len(attrs.get("remove", []))
> MAX_NUMBER_CO_REVIEWERS
):

# (New) number of co reviewers depends on whether a partial update has been mode or a full update is provided.
number_of_co_reviewers = (
(
current_number_co_reviewers
+ len(attrs.get("add", []))
- len(attrs.get("remove", []))
)
if self.partial
else len(attrs.get("add", []))
)

if number_of_co_reviewers > MAX_NUMBER_CO_REVIEWERS:
raise ValidationError(
_("The maximum number of allowed co-reviewers is %(max_co_reviewers)s.")
% {"max_co_reviewers": MAX_NUMBER_CO_REVIEWERS}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import {
Button,
FormField,
P,
SerializedFormData,
Solid,
useAlert,
useFormDialog,
validateForm,
} from "@maykin-ui/admin-ui";
import { useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import { useNavigation, useRevalidator } from "react-router-dom";

import {
Expand Down Expand Up @@ -57,11 +59,41 @@ export function DestructionListReviewer({
const assignedCoReviewers = useDestructionListCoReviewers(destructionList);
const user = useWhoAmI();

const [
assignCoReviewersFormValuesState,
setAssignCoReviewersFormValuesState,
] = useState<SerializedFormData>({});

useEffect(() => {
setAssignCoReviewersFormValuesState({
...assignCoReviewersFormValuesState,
coReviewer: assignedCoReviewers.map((r) => r.user.pk.toString()),
});
}, [assignedCoReviewers]);

const [assignCoReviewerModalOpenState, setAssignCoReviewerModalOpenState] =
useState(false);

/**
* Updates `assignCoReviewersFormValuesState` with `values`.
* This allows `field` to use filtered options based on it's value.
* @param values
*/
const handleValidate = (values: SerializedFormData) => {
// Ignore first run.
if (!Object.keys(values).length) {
return;
}
setAssignCoReviewersFormValuesState(values);
return validateForm(values, fields);
};

/**
* Gets called when the change is confirmed.
*/
const handleSubmit = (data: DestructionListReviewerFormType) => {
const { coReviewer, reviewer, comment } = data;
setAssignCoReviewerModalOpenState(false);

const promises: Promise<unknown>[] = [];

Expand Down Expand Up @@ -122,6 +154,10 @@ export function DestructionListReviewer({
(assignee) => assignee.role === "main_reviewer",
);

/**
* The fields to show in the form dialog, can be either for (re)assigning a
* reviewer or for (re)assigning co-reviewers.
*/
const fields = useMemo<FormField[]>(() => {
if (!user) return [];

Expand All @@ -145,29 +181,58 @@ export function DestructionListReviewer({
required: true,
};

const activeCoReviewers =
(assignCoReviewersFormValuesState.coReviewer as string[]) || [];

if (canReviewDestructionList(user, destructionList)) {
const coReviewerFields = new Array(5)
.fill({
label: "Medebeoordelaar",
name: "coReviewer",
type: "string",
options: coReviewers.map((user) => ({
label: formatUser(user),
value: user.pk,
})),
required: false,
})
.map((f, i) => ({
...f,
label: `Medebeoordelaar ${1 + i}`,
value: assignedCoReviewers[i]?.user.pk,
}));
.map((f, i) => {
return {
...f,
label: `Medebeoordelaar ${1 + i}`,
value: activeCoReviewers?.[i],
options: coReviewers
// Don't show the co-reviewer as option if:
// - The co-reviewer is already selected AND
// - The co-reviewer is not selected as value for the current
// field.
.filter((c) => {
const selectedIndex = activeCoReviewers.indexOf(
c.pk.toString(),
);
if (selectedIndex < 0 || selectedIndex === i) {
return true;
}
return false;
})
.map((user) => ({
label: formatUser(user),
value: user.pk,
})),
};
});

return [...coReviewerFields, comment];
}
return [reviewer, comment];
}, [user, destructionList, reviewers, assignedCoReviewers]);
}, [
user,
destructionList,
reviewers,
assignedCoReviewers,
assignCoReviewersFormValuesState,
]);

/**
* Contains the co-reviewers that are assigned to the destruction list as
* items for the AttributeTable.
*/
const coReviewerItems = useMemo(
() =>
assignedCoReviewers.reduce((acc, coReviewer, i) => {
Expand All @@ -194,6 +259,29 @@ export function DestructionListReviewer({
[assignedCoReviewers, coReviews],
);

/**
* Opens a dialog to assign a co-reviewer and updates it when `fields` change.
*/
useEffect(() => {
formDialog(
"Beoordelaar toewijzen",
null,
fields,
"Toewijzen",
"Annuleren",
handleSubmit,
undefined,
{
allowClose: true,
open: assignCoReviewerModalOpenState,
onClose: () => setAssignCoReviewerModalOpenState(false),
},
{
validate: handleValidate,
},
);
}, [assignCoReviewerModalOpenState, fields]);

return (
<>
{reviewer && (
Expand All @@ -217,18 +305,9 @@ export function DestructionListReviewer({
}
size="xs"
variant="secondary"
onClick={(e) => {
formDialog(
"Beoordelaar toewijzen",
null,
fields,
"Toewijzen",
"Annuleren",
handleSubmit,
undefined,
{ allowClose: true },
);
}}
onClick={() =>
setAssignCoReviewerModalOpenState(true)
}
>
<Solid.PencilIcon />
</Button>
Expand Down

0 comments on commit ac40d1c

Please sign in to comment.