Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: assign bouquet to any organization #516

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 125 additions & 43 deletions src/components/forms/bouquet/BouquetOwnerForm.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<script setup lang="ts">
import { defineModel, computed, ref, watch, type Ref } from 'vue'
import type { Organization } from '@datagouv/components'
import { debounce } from 'lodash'
import { computed, ref, watch, type Ref } from 'vue'
import Multiselect from 'vue-multiselect'
import 'vue-multiselect/dist/vue-multiselect.css'

import '@/assets/multiselect.css'
import type { Topic } from '@/model/topic'
import SearchAPI from '@/services/api/SearchOrgAPI'
import { useUserStore } from '@/store/UserStore'
import { useTopicsConf } from '@/utils/config'

Expand All @@ -16,12 +22,61 @@ const { topicsName } = useTopicsConf()
const choice: Ref<'organization' | 'owner'> = ref(
topic.value.organization != null ? 'organization' : 'owner'
)

const selectedAnyOrganization: Ref<Organization | undefined> = ref(undefined)
const selectedOwnOrganization: Ref<string | undefined> = ref(undefined)

const radioOptions = [
{
label: 'En votre propre nom',
value: 'owner'
},
{
label: "En tant qu'organisation",
value: 'organization'
}
]
const selectOptions = computed(() => {
return organizations.value.map((option, index) => {
return { value: index, text: option.name }
})
})

const organizations = computed(() => userStore.data?.organizations || [])

const onSelectOrganization = (value: string) => {
const idx = parseInt(value)
topic.value.organization = organizations.value[idx]
topic.value.owner = null
const isLoading = ref(false)
const options: Ref<Organization[]> = ref([])

const search = debounce(async (query: string) => {
isLoading.value = true
if (!query) {
options.value = []
isLoading.value = false
return
}
const organizations = (await new SearchAPI().search(query, 10, 1)).data
options.value = organizations
isLoading.value = false
}, 400)

const onSelectOwnOrganization = () => {
if (selectedOwnOrganization.value) {
const idx = parseInt(selectedOwnOrganization.value)
topic.value.organization = organizations.value[idx]
topic.value.owner = null
clear()
}
}
const onSelectAnyOrganization = () => {
if (selectedAnyOrganization.value) {
topic.value.organization = selectedAnyOrganization.value
topic.value.owner = null
selectedOwnOrganization.value = undefined
}
}

const clear = () => {
selectedAnyOrganization.value = undefined
}

watch(choice, () => {
Expand All @@ -34,47 +89,74 @@ watch(choice, () => {

<template>
<div>
<label class="fr-label" for="owner"
>Choisissez si vous souhaitez gérer ce {{ topicsName }}&nbsp;:</label
<DsfrRadioButtonSet
v-model="choice"
narduin marked this conversation as resolved.
Show resolved Hide resolved
:required="true"
:options="radioOptions"
:legend="`Choisissez si vous souhaitez gérer ce ${topicsName}&nbsp;:`"
/>
<fieldset
v-if="choice === 'organization'"
class="fr-fieldset organizations"
>
<fieldset id="owner" class="fr-fieldset">
<div class="fr-fieldset__content" role="radiogroup">
<DsfrRadioButton
v-model="choice"
name="owner"
value="owner"
label="En votre propre nom"
<div class="fr-fieldset__element">
<DsfrSelect
id="ownerOrg"
narduin marked this conversation as resolved.
Show resolved Hide resolved
v-model="selectedOwnOrganization"
label="Organisations dont vous faites partie&nbsp;:"
default-unselected-text="Sélectionnez une ogranisation"
narduin marked this conversation as resolved.
Show resolved Hide resolved
:options="selectOptions"
@update:model-value="onSelectOwnOrganization()"
/>
<div>
<DsfrRadioButton
v-model="choice"
:disabled="organizations.length === 0"
name="organization"
value="organization"
label="En tant qu'organisation"
/>
<div v-if="choice === 'organization'">
<select
class="fr-select"
@change="
onSelectOrganization(($event.target as HTMLInputElement)?.value)
"
>
<option selected disabled value="">
Choisissez une organisation
</option>
<option
v-for="(org, idx) in organizations"
:key="idx"
:selected="topic.organization?.id === org.id"
:value="idx"
>
{{ org.name }}
</option>
</select>
</div>
</div>
</div>
<div v-if="userStore.isAdmin" class="fr-fieldset__element">
<label class="fr-mt-2v" for="any-org-select-bouquet"
>Cherchez une autre organisation&nbsp;:</label
>
<Multiselect
id="any-org-select-bouquet"
ref="multiselect"
v-model="selectedAnyOrganization"
role="search"
:options="options"
track-by="id"
placeholder="Ex: Ministère de la Transition Ecologique"
select-label="Entrée pour sélectionner"
:multiple="false"
:searchable="true"
:internal-search="false"
:loading="isLoading"
:clear-on-select="true"
:close-on-select="true"
:show-no-results="false"
:hide-selected="true"
@search-change="search"
@select="onSelectAnyOrganization()"
>
<template #caret>
<div
v-if="selectedAnyOrganization"
class="multiselect__clear"
@mousedown.prevent.stop="clear"
></div>
</template>
<template #singleLabel="slotProps">
{{ slotProps.option.name }}
</template>
<template #option="slotProps">
{{ slotProps.option.name }}
</template>
<template #noOptions>
Précisez ou élargissez votre recherche
</template>
</Multiselect>
</div>
</fieldset>
</div>
</template>

<style scoped>
.organizations {
gap: 1rem;
}
</style>
7 changes: 7 additions & 0 deletions src/model/organization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Organization } from '@datagouv/components'

import type { GenericResponse } from './api'

export interface OrganizationResponse extends GenericResponse {
data: Organization[]
}
29 changes: 29 additions & 0 deletions src/services/api/SearchOrgAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { size } from 'lodash'

import type { OrganizationResponse } from '@/model/organization'

import DatagouvfrAPI from './DatagouvfrAPI'

/**
* A wrapper around search engine API
*/
export default class SearchAPI extends DatagouvfrAPI {
version = 2
endpoint = 'organizations/search'

async search(
query: string,
size: number,
page: number,
args?: object
): Promise<OrganizationResponse> {
return await this.list({
params: {
q: query,
size: size ?? 20,
page: page ?? 1,
...args
}
})
}
}
Loading