Skip to content
Snippets Groups Projects
Select Git revision
  • 9b17d3ba73f3255bdafd9e1d9d7f39c729a35dd8
  • master default protected
  • Rpc
3 results

grpc_csharp_plugin.exe

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    FormMetadata.vue 26.52 KiB
    <template>
      <div>
        <!-- Copy Metadata -->
        <CoscineFormGroup
          v-if="parentProject"
          :mandatory="false"
          label-for="CopyData"
          :label="
            $t('form.project.copyMetadataLabel', {
              project: parentProject.name,
            })
          "
          :is-loading="isLoading"
          type="button"
        >
          <b-button
            id="project_copy_button"
            variant="secondary"
            size="sm"
            :disabled="disabled"
            @click.prevent="copyMetadataFromParent"
          >
            {{ $t("buttons.copyMetadata") }}
          </b-button>
        </CoscineFormGroup>
    
        <!-- Principal Investigators -->
        <CoscineFormGroup
          :mandatory="true"
          label-for="PrincipleInvestigators"
          :label="$t('form.project.projectPrincipleInvestigatorsLabel')"
          :is-loading="isLoading"
          type="input"
        >
          <!-- number of characters -->
          <template #hint>
            {{ projectForManipulation.principleInvestigators?.length ?? 0 }} /
            {{
              v$.projectForManipulation.principleInvestigators.maxLength.$params.max
            }}
          </template>
          <b-form-input
            id="PrincipleInvestigators"
            v-model="v$.projectForManipulation.principleInvestigators.$model"
            :state="
              v$.projectForManipulation.principleInvestigators.$dirty
                ? !v$.projectForManipulation.principleInvestigators.$error
                : null
            "
            :placeholder="$t('form.project.projectPrincipleInvestigators')"
            :maxlength="
              v$.projectForManipulation.principleInvestigators.maxLength.$params.max
            "
            required
            :disabled="disabled"
          />
          <div class="invalid-tooltip">
            {{
              $t("form.project.projectPrincipleInvestigatorsHelp", {
                maxLength:
                  v$.projectForManipulation.principleInvestigators.maxLength.$params
                    .max,
              })
            }}
          </div>
        </CoscineFormGroup>
    
        <!-- Project Start -->
        <CoscineFormGroup
          :mandatory="true"
          label-for="StartDate"
          :label="$t('form.project.projectStartLabel')"
          :is-loading="isLoading"
          type="input"
        >
          <b-form-input
            id="StartDate"
            v-model="v$.projectForManipulation.startDate.$model"
            type="date"
            :locale="$i18n.locale"
            :disabled="disabled"
            :state="
              v$.projectForManipulation.startDate.$dirty
                ? !v$.projectForManipulation.startDate.$error
                : null
            "
            required
            :placeholder="$t('form.project.projectStart')"
          />
        </CoscineFormGroup>
    
        <!-- Project End -->
        <CoscineFormGroup
          :mandatory="true"
          label-for="EndDate"
          :label="$t('form.project.projectEndLabel')"
          :is-loading="isLoading"
          type="input"
        >
          <b-form-input
            id="EndDate"
            v-model="v$.projectForManipulation.endDate.$model"
            type="date"
            :locale="$i18n.locale"
            :min="projectForManipulation.startDate"
            :disabled="disabled"
            :state="
              v$.projectForManipulation.endDate.$dirty
                ? !v$.projectForManipulation.endDate.$error
                : null
            "
            required
            :placeholder="$t('form.project.projectEnd')"
          />
        </CoscineFormGroup>
    
        <!-- Discipline -->
        <CoscineFormGroup
          :mandatory="true"
          label-for="Discipline"
          :label="$t('form.project.projectDisciplineLabel')"
          :is-loading="isLoading"
          type="input"
        >
          <multiselect
            id="Discipline"
            v-model="v$.projectForManipulation.disciplines.$model"
            :disabled="disabled"
            :options="disciplines"
            :multiple="true"
            :hide-selected="true"
            :label="disciplineLabel"
            :track-by="disciplineLabel"
            :placeholder="$t('form.project.projectDiscipline')"
            :show-labels="false"
            :class="{
              invalid: v$.projectForManipulation.disciplines.$dirty
                ? v$.projectForManipulation.disciplines.$invalid
                : false,
            }"
          >
            <template #singleLabel="entities">
              <div :disabled="disabled">
                {{ entities.option[disciplineLabel] }}
              </div>
            </template>
            <template #option="entities">
              <div :disabled="disabled">
                {{ entities.option[disciplineLabel] }}
              </div>
            </template>
          </multiselect>
        </CoscineFormGroup>
    
        <!-- Responsible Organization -->
        <CoscineFormGroup
          :mandatory="true"
          label-for="ResponsibleOrganization"
          :label="$t('form.project.projectResponsibleOrganizationLabel')"
          :is-loading="isLoading"
          type="input"
          info
        >
          <!-- Hint -->
          <template #hint>
            {{ $t("form.project.projectOrganizationHint") }}
          </template>
    
          <template #popover>
            {{ $t("form.project.projectOrganizationLabelPopover") }}
            <b-link
              :href="
                $t('form.project.projectOrganizationLabelPopoverUrl').toString()
              "
              target="_blank"
            >
              {{ $t("default.help") }}
            </b-link>
          </template>
    
          <!-- Rely on the API for filtering, disable internal component search -->
          <multiselect
            id="ResponsibleOrganization"
            v-model="responsibleOrganization"
            :disabled="disabled"
            :options="organizations"
            :multiple="false"
            :loading="isLoadingOrganizations"
            :hide-selected="true"
            label="displayName"
            track-by="uri"
            :show-labels="false"
            :class="{
              invalid: v$.projectForManipulation.organizations.$dirty
                ? v$.projectForManipulation.organizations.$invalid
                : false,
            }"
            :internal-search="false"
            :searchable="true"
            :placeholder="$t('form.project.projectResponsibleOrganization')"
            @search-change="retrieveMoreOrganizations"
          >
            <template #afterList>
              <div
                v-if="organizations.length && !organizationsComplete"
                v-observe-visibility="retrieveMoreOrganizations"
              >
                <div class="d-flex justify-content-left my-3 ps-3">
                  <b-spinner v-if="isLoadingOrganizations" small />
                </div>
              </div>
            </template>
            <template #noOptions>
              {{ $t("form.project.projectOrganizationNoOptions") }}
            </template>
            <template #noResult>
              {{ $t("form.project.projectOrganizationNoResult") }}
            </template>
          </multiselect>
        </CoscineFormGroup>
    
        <!-- Additional Organizations -->
        <CoscineFormGroup
          :mandatory="false"
          label-for="AdditionalOrganization"
          :label="$t('form.project.projectAdditionalOrganizationLabel')"
          :is-loading="isLoading"
          type="input"
        >
          <multiselect
            id="AdditionalOrganization"
            v-model="additionalOrganizations"
            :disabled="disabled"
            :options="organizations"
            :multiple="true"
            :loading="isLoadingOrganizations"
            :hide-selected="true"
            label="displayName"
            track-by="uri"
            :show-labels="false"
            :placeholder="$t('form.project.projectAdditionalOrganization')"
            :class="{
              invalid: v$.projectForManipulation.organizations.$dirty
                ? v$.projectForManipulation.organizations.$invalid
                : false,
            }"
            :internal-search="false"
            :searchable="true"
            @search-change="retrieveMoreOrganizations"
          >
            <template #afterList>
              <div
                v-if="organizations.length && !organizationsComplete"
                v-observe-visibility="retrieveMoreOrganizations"
              >
                <div class="d-flex justify-content-left my-3 ps-3">
                  <b-spinner v-if="isLoadingOrganizations" small />
                </div>
              </div>
            </template>
            <template #noOptions>
              {{ $t("form.project.projectOrganizationNoOptions") }}
            </template>
            <template #noResult>
              {{ $t("form.project.projectOrganizationNoResult") }}
            </template>
          </multiselect>
        </CoscineFormGroup>
    
        <!-- Project Keywords -->
        <CoscineFormGroup
          label-for="Keywords"
          :label="$t('form.project.projectKeywordsLabel')"
          :is-loading="isLoading"
        >
          <multiselect
            id="Keywords"
            v-model="v$.projectForManipulation.keywords.$model"
            :options="projectForManipulation.keywords"
            :placeholder="$t('form.project.projectKeywordsPlaceholder')"
            :multiple="true"
            :taggable="true"
            :max="limitKeywords(projectForManipulation.keywords)"
            :tag-placeholder="$t('form.project.tagPlaceholder')"
            :disabled="disabled"
            :show-labels="false"
            @tag="addTag"
            @remove="v$.projectForManipulation.keywords.$touch()"
          >
            <template #maxElements>
              {{
                $t("form.project.projectKeywordsHelp", {
                  maxLength:
                    v$.projectForManipulation.keywords.maxLength.$params.max,
                })
              }}
            </template>
            <template #noOptions>
              {{
                $t("form.project.projectKeywordsEmpty", {
                  maxLength:
                    v$.projectForManipulation.keywords.maxLength.$params.max,
                })
              }}
            </template>
          </multiselect>
        </CoscineFormGroup>
    
        <!-- Visibility -->
        <CoscineFormGroup
          :mandatory="true"
          label-for="Visibility"
          :label="$t('form.project.projectMetadataVisibilityLabel')"
          :is-loading="isLoading"
          type="input"
          info
        >
          <template #popover>
            {{ $t("form.project.projectMetadataVisibilityLabelPopover") }}
            <b-link
              :href="
                $t(
                  'form.project.projectMetadataVisibilityLabelPopoverUrl',
                ).toString()
              "
              target="_blank"
            >
              {{ $t("default.help") }}
            </b-link>
          </template>
    
          <b-form-radio-group
            v-model="selectedVisibility"
            name="radios-stacked"
            :options="visibilities"
            text-field="displayName"
            value-field="id"
            track-by="id"
            stacked
            :disabled="disabled"
            @update:modelValue="setVisibility(selectedVisibility)"
          />
        </CoscineFormGroup>
    
        <!-- Grant ID -->
        <CoscineFormGroup
          label-for="GrantId"
          :label="$t('form.project.projectGrantIdLabel')"
          :is-loading="isLoading"
          type="input"
        >
          <!-- number of characters -->
          <template #hint>
            {{ projectForManipulation.grantId?.length ?? 0 }} /
            {{ v$.projectForManipulation.grantId.maxLength.$params.max }}
          </template>
          <b-form-input
            id="GrantId"
            v-model="v$.projectForManipulation.grantId.$model"
            :state="
              v$.projectForManipulation.grantId.$dirty
                ? !v$.projectForManipulation.grantId.$error
                : null
            "
            :placeholder="$t('form.project.projectGrantId')"
            :maxlength="v$.projectForManipulation.grantId.maxLength.$params.max"
            required
            :disabled="disabled"
          />
          <div class="invalid-tooltip">
            {{
              $t("form.project.projectGrantIdHelp", {
                maxLength: v$.projectForManipulation.grantId.maxLength.$params.max,
              })
            }}
          </div>
        </CoscineFormGroup>
      </div>
    </template>
    
    <script lang="ts" setup>
    import { required, maxLength, helpers } from "@vuelidate/validators";
    import { useVuelidate, type BaseValidation } from "@vuelidate/core";
    import {
      DisciplineDto2DisciplineForProjectManipulationDto,
      projectMapper,
      OrganizationDto2OrganizationForProjectManipulationDto,
      VisibilityDto2VisibilityForProjectManipulationDto,
    } from "@/mapping/project";
    import moment from "moment";
    import { OrganizationForProjectManipulation } from "@/models/OrganizationForProjectManipulation";
    import { ProjectForUpdate } from "@/models/ProjectForUpdate";
    import { ProjectForCreation } from "@/models/ProjectForCreation";
    
    // import the store for current module
    import useProjectStore from "../../store";
    import type {
      AcceptedLanguage,
      ProjectDto,
      OrganizationForProjectManipulationDto,
    } from "@coscine/api-client/dist/types/Coscine.Api";
    
    import { useI18n } from "vue-i18n";
    const i18n = useI18n();
    
    const projectForManipulation = defineModel<
      ProjectForUpdate | ProjectForCreation
    >({
      required: true,
    });
    
    const props = defineProps({
      currentProject: {
        default: null,
        type: [Object, null] as PropType<ProjectDto | null>,
        required: false,
      },
      parentProject: {
        default: null,
        type: [Object, null] as PropType<ProjectDto | null>,
        required: false,
      },
      disabled: {
        default: false,
        type: Boolean,
      },
      isLoading: {
        default: false,
        type: Boolean,
      },
    });
    
    const emit = defineEmits<{
      validation: [_: BaseValidation<typeof projectForManipulation.value>];
    }>();
    
    const projectStore = useProjectStore();
    
    const responsibleOrganization = ref<OrganizationForProjectManipulation | null>(
      null,
    );
    const additionalOrganizations = ref<OrganizationForProjectManipulation[]>([]);
    
    /*
      Definition of the validation rules and initial state
      will enable proper typings in the code
    */
    // Custom validator to check for exactly one responsible organization
    const organizationsHasOneResponsible = helpers.withMessage(
      "There must be exactly one responsible organization",
      (value: OrganizationForProjectManipulationDto[]) => {
        if (!value || !Array.isArray(value)) return false;
        const countResponsible = value.filter((org) => org.responsible).length;
        return countResponsible === 1;
      },
    );
    
    // Custom validator to ensure all organizations have unique URIs
    const uniqueOrganizationUris = helpers.withMessage(
      "Organizations must have unique URIs",
      (value: OrganizationForProjectManipulationDto[]) => {
        if (!value || !Array.isArray(value)) return true;
        const uris = value.map((org) => org.uri);
        const uniqueUris = new Set(uris);
        return uniqueUris.size === uris.length;
      },
    );
    
    const state = reactive({
      projectForManipulation: projectForManipulation,
    });
    
    const rules = {
      projectForManipulation: {
        principleInvestigators: { required, maxLength: maxLength(500) },
        startDate: { required },
        endDate: { required },
        disciplines: { required },
        organizations: {
          required,
          organizationsHasOneResponsible,
          uniqueOrganizationUris,
        },
        keywords: { maxLength: maxLength(1000) },
        visibility: { required },
        grantId: { maxLength: maxLength(500) },
      },
    };
    const v$ = useVuelidate(rules, state);
    
    const isLoadingOrganizations = ref(false);
    const selectedVisibility = ref<string | undefined>("");
    const queryTimer = ref(0);
    
    const language = computed(() => {
      return i18n.locale.value as AcceptedLanguage;
    });
    const organizations = computed(() => {
      return (projectStore.organizations ?? projectStore.defaultOrganizations).map(
        (organization) =>
          projectMapper.map(
            OrganizationDto2OrganizationForProjectManipulationDto,
            organization,
          ),
      );
    });
    const organizationsComplete = computed(
      () => !projectStore.organizationsMeta.pagination.hasNext,
    );
    const visibilities = computed(() => projectStore.visibilities ?? []);
    const disciplines = computed(() => projectStore.disciplines ?? []);
    const disciplineLabel = computed(() => {
      let locale = i18n.locale.value;
      // Ensure that the value of locale is a valid locale, otherwie the property for disciplineLabel will be inaccurate
      if (i18n.availableLocales.includes(locale) === false) {
        locale = i18n.fallbackLocale.value.toString();
      }
      locale = locale.charAt(0).toUpperCase() + locale.slice(1);
      return `displayName${locale}`;
    });
    
    watch(
      () => props.currentProject,
      () => onProjectLoaded(),
    );
    watch(
      () => props.parentProject,
      () => onParentProjectLoaded(),
    );
    watch(visibilities, () => {
      // Used in Project Create
      if (!props.currentProject) {
        setDefaultVisibility();
      }
    });
    watch(
      () => projectForManipulation.value.startDate,
      () => {
        // Adjust the endDate to match startDate, if startDate is past endDate
        const start = moment(projectForManipulation.value.startDate);
        const end = moment(projectForManipulation.value.endDate);
        const difference = moment.duration(start.diff(end)).asDays();
        if (difference > 0) {
          projectForManipulation.value.endDate =
            projectForManipulation.value.startDate;
        }
      },
    );
    watch(
      () => projectForManipulation.value,
      () => {
        emit("validation", v$.value.projectForManipulation);
      },
      { deep: true },
    );
    
    watch(
      () => props.isLoading,
      (newVal, oldVal) => {
        // If the form is no longer loading, set the organizations, but only if it was loading before
        // This avoids potential recursion issues with the other watchers.
        if (newVal === false && oldVal === true) {
          // Responsible organization could be set outside of the component (see CreateProject.vue)
          // As well as other values with the new feature of saving/restoring form data.
          onProjectLoaded();
        }
      },
    );
    watch(
      () => responsibleOrganization.value,
      (newVal, _) => {
        // Don't do anything if the form is loading to avoid recursion
        if (props.isLoading) return;
    
        // Remove any organization marked as responsible
        projectForManipulation.value.organizations =
          projectForManipulation.value.organizations.filter(
            (org) => !org.responsible,
          );
    
        if (newVal) {
          const responsibleOrganizationUri = newVal.uri;
    
          // Remove any existing entries for this organization (responsible or not)
          projectForManipulation.value.organizations =
            projectForManipulation.value.organizations.filter(
              (org) => org.uri !== responsibleOrganizationUri,
            );
    
          // Remove the responsible organization from additional organizations
          additionalOrganizations.value = additionalOrganizations.value.filter(
            (org) => org.uri !== responsibleOrganizationUri,
          );
    
          // Add the new responsible organization
          projectForManipulation.value.organizations.push({
            responsible: true,
            uri: responsibleOrganizationUri,
            displayName: newVal.displayName,
          });
        }
    
        // Ensure the organizations are touched to trigger validation
        v$.value.projectForManipulation.organizations.$touch();
      },
    );
    
    watch(
      () => additionalOrganizations.value,
      (newVal, oldVal) => {
        // Don't do anything if the form is loading to avoid recursion
        if (props.isLoading) return;
    
        // Find the removed additional organizations
        const removedOrganizations = oldVal.filter(
          (org) => !newVal.some((newOrg) => newOrg.uri === org.uri),
        );
    
        removedOrganizations.forEach((removedOrganization) => {
          const removedOrganizationUri = removedOrganization.uri;
    
          // Only remove from projectForManipulation if it's not the responsible organization
          if (responsibleOrganization.value?.uri !== removedOrganizationUri) {
            projectForManipulation.value.organizations =
              projectForManipulation.value.organizations.filter(
                (org) => org.uri !== removedOrganizationUri,
              );
          }
        });
    
        // Find the newly added additional organizations
        const addedOrganizations = newVal.filter(
          (org) => !oldVal.some((oldOrg) => oldOrg.uri === org.uri),
        );
    
        addedOrganizations.forEach((addedOrganization) => {
          const addedOrganizationUri = addedOrganization.uri;
    
          // Remove from responsible if added as additional
          if (responsibleOrganization.value?.uri === addedOrganizationUri) {
            responsibleOrganization.value = null;
          }
    
          // Remove any existing entries for this organization
          projectForManipulation.value.organizations =
            projectForManipulation.value.organizations.filter(
              (org) => org.uri !== addedOrganizationUri,
            );
    
          // Add to the project organizations as non-responsible
          projectForManipulation.value.organizations.push({
            responsible: false,
            uri: addedOrganization.uri,
            displayName: addedOrganization.displayName,
          });
        });
    
        // Ensure the organizations are touched to trigger validation
        v$.value.projectForManipulation.organizations.$touch();
      },
    );
    
    const onProjectLoaded = () => {
      if (props.currentProject?.visibility?.id) {
        selectedVisibility.value = props.currentProject.visibility.id;
        setVisibility(selectedVisibility.value);
      } else if (projectForManipulation.value?.visibility?.id) {
        selectedVisibility.value = projectForManipulation.value.visibility.id;
        setVisibility(selectedVisibility.value);
      } else {
        setDefaultVisibility();
      }
      setOrganizations();
      // Changes have been made to the form, reset its state
      v$.value.projectForManipulation.$reset();
    };
    
    const onParentProjectLoaded = () => {
      const projectForCreation = projectForManipulation.value as ProjectForCreation;
      if (props.parentProject && projectForCreation) {
        projectForCreation.parentId = props.parentProject.id;
        projectForCreation.copyOwnersFromParent = true;
      }
    };
    
    /**
     * Limits the number of keywords based on validation
     * @param {string[]} values - array of keyword strings
     * @return {number | null} - returns the current length of values if the keyword validation fails, else null
     */
    const limitKeywords = (values: string[] | null | undefined): number | null => {
      // Should the max number of allowed characters is exceeded, prevent the addition of further tags
      if (v$.value.projectForManipulation.keywords?.$invalid) {
        return values?.length ?? null;
      } else {
        return null;
      }
    };
    
    /**
     * Add a new tag to the selected keywords
     * @param {string} newTag - new tag to be added to the keywords
     */
    const addTag = (newTag: string) => {
      projectForManipulation.value.keywords?.push(newTag);
      v$.value.projectForManipulation.keywords?.$touch();
    };
    
    const retrieveMoreOrganizations = async (search: string | boolean) => {
      clearTimeout(queryTimer.value);
      // Add delay to API organization retrieval
      queryTimer.value = window.setTimeout(async () => {
        isLoadingOrganizations.value = true;
        await projectStore.retrieveMoreOrganizations(search, language.value);
        isLoadingOrganizations.value = false;
      }, 300);
    };
    
    const copyMetadataFromParent = () => {
      if (props.parentProject) {
        // --- set b-placeholder while filling parent-project form ---
        fillWithProjectMetadata(props.parentProject);
        v$.value.projectForManipulation.$touch();
      }
    };
    
    const fillWithProjectMetadata = (project: ProjectDto) => {
      projectForManipulation.value.principleInvestigators =
        project.principleInvestigators;
      projectForManipulation.value.startDate = moment(project.startDate).format(
        "YYYY-MM-DD",
      );
      projectForManipulation.value.endDate = moment(project.endDate).format(
        "YYYY-MM-DD",
      );
      projectForManipulation.value.disciplines =
        project.disciplines?.map((discipline) =>
          projectMapper.map(
            DisciplineDto2DisciplineForProjectManipulationDto,
            discipline,
          ),
        ) ?? [];
      projectForManipulation.value.organizations =
        project.organizations?.map((organization) =>
          projectMapper.map(
            OrganizationDto2OrganizationForProjectManipulationDto,
            organization,
          ),
        ) ?? [];
      // we need to call setOrganizations() to set responsibleOrganization and additionalOrganizations based on organizations
      setOrganizations();
      projectForManipulation.value.keywords = project.keywords;
      projectForManipulation.value.visibility = projectMapper.map(
        VisibilityDto2VisibilityForProjectManipulationDto,
        project.visibility,
      );
      projectForManipulation.value.grantId = project.grantId;
    
      // Update v-model for Visibility
      if (project.visibility) {
        selectedVisibility.value = project.visibility.id;
      }
    };
    
    /**
     * Set visibility for the resource form
     * @param {string | undefined} visibilityId - ID of the visibility option to be set
     */
    const setVisibility = (visibilityId: string | undefined) => {
      if (visibilityId) {
        projectForManipulation.value.visibility = {
          id: visibilityId,
        };
        v$.value.projectForManipulation.visibility.$touch();
      }
    };
    
    /**
     * Set default visibility option for the resource form
     */
    const setDefaultVisibility = () => {
      if (visibilities.value) {
        const visibility = visibilities.value.find(
          (entry) => entry.id === "8AB9C883-EB0D-4402-AAAD-2E4007BADCE6", // Id of "Project Members"
        );
    
        if (visibility?.id) {
          selectedVisibility.value = visibility.id;
        } else {
          // If visibility not found or has no id, select the first visibility from the array visibilities
          if (visibilities.value.length > 0) {
            selectedVisibility.value = visibilities.value[0].id;
          }
        }
        // If selectedVisibility is undefined, assign an empty string
        setVisibility(selectedVisibility.value);
      }
    };
    
    /**
     * Sets the responsible and additional organizations for the resource form.
     *
     * This function checks if there are any organizations in the `projectForManipulation`
     * and identifies the organization marked as responsible, setting it in `responsibleOrganization`.
     * If the `responsibleOrganization` is not found in `projectForManipulation`, it attempts
     * to set it from `props.currentProject`. Additionally, it populates `additionalOrganizations`
     * with organizations from `props.currentProject` that are not marked as responsible.
     *
     * @function setOrganizations
     * @returns {void}
     */
    const setOrganizations = () => {
      // Check if projectForManipulation.value.organizations has any entries
      if (
        projectForManipulation.value.organizations &&
        projectForManipulation.value.organizations.length !== 0
      ) {
        const responsibleOrgUri = projectForManipulation.value.organizations.find(
          (org) => org.responsible,
        )?.uri;
    
        if (responsibleOrgUri) {
          const respOrgToSet = organizations.value.find(
            (org) => org.uri === responsibleOrgUri,
          );
          // Use Object.assign to avoid reactivity issues
          responsibleOrganization.value = Object.assign({}, respOrgToSet);
        }
        // Populate additional organizations
        additionalOrganizations.value = projectForManipulation.value.organizations
          .filter((org) => !org.responsible)
          .map((org) => Object.assign({}, org)); // Use Object.assign to avoid reactivity issues
      }
    
      // Check if there's a current project to use as a source for responsible organization
      if (props.currentProject) {
        if (!responsibleOrganization.value) {
          responsibleOrganization.value =
            props.currentProject.organizations.find((org) => org.responsible) ??
            null;
        }
        // Populate additional organizations from the current project, excluding the responsible one
        additionalOrganizations.value = props.currentProject.organizations
          .filter((org) => !org.responsible)
          .map((org) => Object.assign({}, org)); // Use Object.assign to avoid reactivity issues
      }
    };
    
    onProjectLoaded();
    onParentProjectLoaded();
    </script>
    
    <style></style>