Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
87 changes: 87 additions & 0 deletions datagouv-components/src/components/OrganizationHorizontalCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<template>
<ObjectCard>
<template #media>
<OrganizationLogo
:organization="organization"
size-class="size-12"
/>
</template>
<ObjectCardHeader
:icon="RiBuilding2Line"
:url="organization.page"
>
<OrganizationNameWithCertificate
:show-type="false"
:organization
size="sm"
color-class="text-gray-title"
/>
</ObjectCardHeader>
<div class="text-sm m-0 flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate">
<template v-if="type !== 'other'">
<OwnerType
class="mb-0"
:type
/>
<RiSubtractLine
v-if="'metrics' in organization"
aria-hidden="true"
class="hidden md:block size-4 flex-none fill-gray-medium"
/>
</template>
<div
v-if="'metrics' in organization"
class="text-gray-medium flex items-center text-sm gap-0.5"
:aria-label="t('{datasets} jeux de donn\u00e9es, {dataservices} API et {reuses} r\u00e9utilisations', {
datasets: organization.metrics.datasets,
dataservices: organization.metrics.dataservices,
reuses: organization.metrics.reuses,
})"
>
<RiDatabase2Line
aria-hidden="true"
class="size-3.5"
/> {{ summarize(organization.metrics.datasets) }}
<RiTerminalLine
aria-hidden="true"
class="size-3.5 ml-1"
/> {{ summarize(organization.metrics.dataservices) }}
<RiLineChartLine
aria-hidden="true"
class="size-3.5 ml-1"
/> {{ summarize(organization.metrics.reuses) }}
<RiStarLine
aria-hidden="true"
class="size-3.5 ml-1"
/> {{ summarize(organization.metrics.followers) }}
</div>
</div>
<ObjectCardShortDescription
v-if="'description' in organization"
:text="organization.description"
/>
</ObjectCard>
</template>

<script setup lang="ts">
import { RiBuilding2Line, RiDatabase2Line, RiLineChartLine, RiStarLine, RiSubtractLine, RiTerminalLine } from '@remixicon/vue'
import { computed } from 'vue'
import { getOrganizationType } from '../functions/organizations'
import { summarize } from '../functions/helpers'
import { useTranslation } from '../composables/useTranslation'
import type { Organization, OrganizationReference } from '../types/organizations'
import OrganizationLogo from './OrganizationLogo.vue'
import OrganizationNameWithCertificate from './OrganizationNameWithCertificate.vue'
import OwnerType from './OwnerType.vue'
import ObjectCard from './ObjectCard.vue'
import ObjectCardHeader from './ObjectCardHeader.vue'
import ObjectCardShortDescription from './ObjectCardShortDescription.vue'

const props = defineProps<{
organization: Organization | OrganizationReference
}>()

const { t } = useTranslation()

const type = computed(() => getOrganizationType(props.organization))
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/>
<component
:is="as"
class="mb-0 truncate flex-initial font-normal"
class="mb-0 truncate flex-initial"
:class="[colorClass, { 'text-xs': size === 'xs', 'text-sm': size === 'sm', 'text-base': size === 'base' }]"
>
{{ organization.name }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,37 @@
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { FacetItem } from '../../../types/search'
import { useTranslation } from '../../../composables/useTranslation'
import FilterButtonGroup from './FilterButtonGroup.vue'

defineProps<{
const props = withDefaults(defineProps<{
modelValue: string | undefined
facets?: FacetItem[]
loading?: boolean
}>()
exclude?: string[]
}>(), {
exclude: () => [],
})

const emit = defineEmits<{
'update:modelValue': [value: string | undefined]
}>()

const { t } = useTranslation()

const options = [
const allOptions = [
{ value: 'public-service', label: t('Service public') },
{ value: 'local-authority', label: t('Collectivité territoriale') },
{ value: 'company', label: t('Entreprise') },
{ value: 'association', label: t('Association') },
{ value: 'user', label: t('Utilisateur') },
]

const options = computed(() =>
props.exclude.length > 0
? allOptions.filter(o => !props.exclude.includes(o.value))
: allOptions,
)
</script>
31 changes: 29 additions & 2 deletions datagouv-components/src/components/Search/GlobalSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
v-model="producerType"
:facets="getFacets('producer_type')"
:loading="searchResultsStatus === 'pending'"
:exclude="currentType === 'organizations' ? ['user'] : []"
:style="{ order: getOrder('producer_type') }"
/>
<DatasetBadgeFilter
Expand Down Expand Up @@ -241,6 +242,14 @@
<ReuseHorizontalCard :reuse="(result as Reuse)" />
</slot>
</template>
<template v-else-if="currentType === 'organizations'">
<slot
name="organization"
:organization="result"
>
<OrganizationHorizontalCard :organization="(result as Organization)" />
</slot>
</template>
</li>
</ul>
<Pagination
Expand Down Expand Up @@ -312,7 +321,7 @@
<script setup lang="ts">
import { computed, watch, useTemplateRef, type Ref } from 'vue'
import { useRouteQuery } from '@vueuse/router'
import { RiCloseCircleLine, RiDatabase2Line, RiRobot2Line, RiLineChartLine, RiLightbulbLine } from '@remixicon/vue'
import { RiBuilding2Line, RiCloseCircleLine, RiDatabase2Line, RiRobot2Line, RiLineChartLine, RiLightbulbLine } from '@remixicon/vue'
import magnifyingGlassSrc from '../../../assets/illustrations/magnifying_glass.svg?url'
import { useTranslation } from '../../composables/useTranslation'
import { useDebouncedRef } from '../../composables/useDebouncedRef'
Expand All @@ -322,8 +331,9 @@ import { useFetch } from '../../functions/api'
import { getLink } from '../../functions/pagination'
import type { Dataset } from '../../types/datasets'
import type { Dataservice } from '../../types/dataservices'
import type { Organization } from '../../types/organizations'
import type { Reuse } from '../../types/reuses'
import type { GlobalSearchConfig, SearchType, DatasetSearchResponse, DataserviceSearchResponse, ReuseSearchResponse, FacetItem } from '../../types/search'
import type { GlobalSearchConfig, SearchType, DatasetSearchResponse, DataserviceSearchResponse, ReuseSearchResponse, OrganizationSearchResponse, FacetItem } from '../../types/search'
import { getDefaultGlobalSearchConfig } from '../../types/search'
import BrandedButton from '../BrandedButton.vue'
import LoadingBlock from '../LoadingBlock.vue'
Expand All @@ -332,6 +342,7 @@ import RadioGroup from '../RadioGroup.vue'
import RadioInput from '../RadioInput.vue'
import DatasetCard from '../DatasetCard.vue'
import DataserviceCard from '../DataserviceCard.vue'
import OrganizationHorizontalCard from '../OrganizationHorizontalCard.vue'
import ReuseHorizontalCard from '../ReuseHorizontalCard.vue'
import SearchInput from './SearchInput.vue'
import Sidemenu from './Sidemenu.vue'
Expand Down Expand Up @@ -458,6 +469,7 @@ watch(currentType, () => {
const datasetsEnabled = computed(() => props.config.some(c => c.class === 'datasets'))
const dataservicesEnabled = computed(() => props.config.some(c => c.class === 'dataservices'))
const reusesEnabled = computed(() => props.config.some(c => c.class === 'reuses'))
const organizationsEnabled = computed(() => props.config.some(c => c.class === 'organizations'))

// Create stable params for each type
const stableParamsOptions = {
Expand All @@ -480,11 +492,16 @@ const reusesParams = useStableQueryParams({
...stableParamsOptions,
typeConfig: props.config.find(c => c.class === 'reuses'),
})
const organizationsParams = useStableQueryParams({
...stableParamsOptions,
typeConfig: props.config.find(c => c.class === 'organizations'),
})

// URLs that return null when type is not enabled
const datasetsUrl = computed(() => datasetsEnabled.value ? '/api/2/datasets/search/' : null)
const dataservicesUrl = computed(() => dataservicesEnabled.value ? '/api/2/dataservices/search/' : null)
const reusesUrl = computed(() => reusesEnabled.value ? '/api/2/reuses/search/' : null)
const organizationsUrl = computed(() => organizationsEnabled.value ? '/api/2/organizations/search/' : null)

// Reset page on filter/sort change
const filtersForReset = computed(() => ({
Expand Down Expand Up @@ -564,6 +581,10 @@ const { data: reusesResults, status: reusesStatus } = await useFetch<ReuseSearch
reusesUrl,
{ params: reusesParams, lazy: true, server: initialType === 'reuses' },
)
const { data: organizationsResults, status: organizationsStatus } = await useFetch<OrganizationSearchResponse<Organization>>(
organizationsUrl,
{ params: organizationsParams, lazy: true, server: initialType === 'organizations' },
)

const typesMeta = {
datasets: {
Expand All @@ -584,6 +605,12 @@ const typesMeta = {
results: reusesResults,
status: reusesStatus,
},
organizations: {
icon: RiBuilding2Line,
name: t('Organisations'),
results: organizationsResults,
status: organizationsStatus,
},
} as const

const searchResults = computed(() => typesMeta[currentType.value].results.value)
Expand Down
6 changes: 5 additions & 1 deletion datagouv-components/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { Weight, WellType } from './types/ui'
import type { User, UserReference } from './types/users'
import type { Report, ReportSubject, ReportReason } from './types/reports'
import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions } from './types/search'
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'

import ActivityList from './components/ActivityList/ActivityList.vue'
import UserActivityList from './components/ActivityList/UserActivityList.vue'
Expand Down Expand Up @@ -55,6 +55,7 @@ import LabelTag from './components/DatasetLabelTag.vue'
import LoadingBlock from './components/LoadingBlock.vue'
import MarkdownViewer from './components/MarkdownViewer.vue'
import OrganizationCard from './components/OrganizationCard.vue'
import OrganizationHorizontalCard from './components/OrganizationHorizontalCard.vue'
import OrganizationLogo from './components/OrganizationLogo.vue'
import OrganizationNameWithCertificate from './components/OrganizationNameWithCertificate.vue'
import OwnerType from './components/OwnerType.vue'
Expand Down Expand Up @@ -221,10 +222,12 @@ export {
getDefaultDatasetConfig,
getDefaultDataserviceConfig,
getDefaultReuseConfig,
getDefaultOrganizationConfig,
getDefaultGlobalSearchConfig,
defaultDatasetSortOptions,
defaultDataserviceSortOptions,
defaultReuseSortOptions,
defaultOrganizationSortOptions,
}

// Vue Plugin
Expand Down Expand Up @@ -273,6 +276,7 @@ export {
Tag,
MarkdownViewer,
OrganizationCard,
OrganizationHorizontalCard,
OrganizationLogo,
OrganizationNameWithCertificate,
OwnerType,
Expand Down
30 changes: 29 additions & 1 deletion datagouv-components/src/types/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export type OrganizationSearchSort = 'reuses' | 'datasets' | 'followers' | 'view

export type OrganizationSearchFilters = {
badge?: OrganizationBadgeFilter
producer_type?: ProducerType
}

export type OrganizationSearchQueryParams = BaseSearchQueryParams<OrganizationSearchSort> & OrganizationSearchFilters
Expand Down Expand Up @@ -315,7 +316,16 @@ export type ReuseSearchConfig = {
sortOptions?: SortOption<ReuseSearchSort>[]
}

export type SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig
export type OrganizationSearchConfig = {
class: 'organizations'
name?: string
hiddenFilters?: HiddenFilter<OrganizationSearchFilters>[]
basicFilters?: (keyof OrganizationSearchFilters)[]
advancedFilters?: (keyof OrganizationSearchFilters)[]
sortOptions?: SortOption<OrganizationSearchSort>[]
}

export type SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig | OrganizationSearchConfig

export type SearchType = SearchTypeConfig['class']

Expand All @@ -340,6 +350,13 @@ export const defaultReuseSortOptions: SortOption<ReuseSearchSort>[] = [
{ value: '-datasets', label: 'Nombre de jeux de données' },
]

export const defaultOrganizationSortOptions: SortOption<OrganizationSearchSort>[] = [
{ value: '-created', label: 'Date de création' },
{ value: '-followers', label: `Nombre d'abonnés` },
{ value: '-datasets', label: 'Nombre de jeux de données' },
{ value: '-reuses', label: 'Nombre de réutilisations' },
]

export function getDefaultDatasetConfig(overrides?: Partial<Omit<DatasetSearchConfig, 'class'>>): DatasetSearchConfig {
return {
class: 'datasets',
Expand Down Expand Up @@ -370,10 +387,21 @@ export function getDefaultReuseConfig(overrides?: Partial<Omit<ReuseSearchConfig
}
}

export function getDefaultOrganizationConfig(overrides?: Partial<Omit<OrganizationSearchConfig, 'class'>>): OrganizationSearchConfig {
return {
class: 'organizations',
basicFilters: ['producer_type'],
advancedFilters: [],
sortOptions: defaultOrganizationSortOptions,
...overrides,
}
}

export function getDefaultGlobalSearchConfig(): GlobalSearchConfig {
return [
getDefaultDatasetConfig(),
getDefaultDataserviceConfig(),
getDefaultReuseConfig(),
getDefaultOrganizationConfig(),
]
}
7 changes: 7 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,13 @@ export default defineNuxtConfig({
meta: { key: 'search' },
},
)

// Replace the existing /organizations page with the unified search page
const orgRoute = pages.find(p => p.path === '/organizations')
if (orgRoute) {
orgRoute.file = resolve(__dirname, 'pages/datasets/search.vue')
orgRoute.meta = { ...orgRoute.meta, key: 'search' }
}
}
},
},
Expand Down
Loading
Loading