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 3 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
169 changes: 126 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, defineModel, 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,62 @@ 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(() => {
const options = organizations.value.map((option, index) => {
return { value: index, text: option.name }
})
return options
narduin marked this conversation as resolved.
Show resolved Hide resolved
})

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 +90,74 @@ watch(choice, () => {

<template>
<div>
<label class="fr-label" for="owner"
>Choisissez si vous souhaitez gérer ce {{ topicsName }}&nbsp;:</label
<DsfrRadioButtonSet
:required="true"
v-model="choice"
narduin marked this conversation as resolved.
Show resolved Hide resolved
: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
v-model="selectedOwnOrganization"
label="Organisations dont vous faites partie."
narduin marked this conversation as resolved.
Show resolved Hide resolved
defaultUnselectedText="Sélectionnez une ogranisation"
narduin marked this conversation as resolved.
Show resolved Hide resolved
id="ownerOrg"
narduin marked this conversation as resolved.
Show resolved Hide resolved
:options="selectOptions"
@update:modelValue="onSelectOwnOrganization()"
narduin marked this conversation as resolved.
Show resolved Hide resolved
/>
<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.</label
narduin marked this conversation as resolved.
Show resolved Hide resolved
>
<Multiselect
id="any-org-select-bouquet"
ref="multiselect"
v-model="selectedAnyOrganization"
:options="options"
track-by="id"
placeholder="Ex: Lyon Métropole"
narduin marked this conversation as resolved.
Show resolved Hide resolved
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()"
role="search"
narduin marked this conversation as resolved.
Show resolved Hide resolved
>
<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