| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- <!-- Search for member structures -->
- <template>
- <LayoutContainer class="map-view">
- <!-- Header -->
- <v-row>
- <v-layout>
- <h2 class="flex">
- {{ $t("member_companies") }}
- </h2>
- <v-btn-toggle mandatory dense @change="viewChanged">
- <v-btn>
- {{ $t("map") }}
- </v-btn>
- <v-btn>
- {{ $t("list") }}
- </v-btn>
- </v-btn-toggle>
- </v-layout>
- </v-row>
- <v-row>
- <!-- Map Column (hidden in 'list-view' mode)-->
- <v-col v-show="mapview" cols="6">
- <UiMapStructures
- ref="map"
- :structures="filteredStructures"
- @boundsUpdated="mapBoundsFilterChanged"
- />
- </v-col>
- <!-- Results column -->
- <v-col :cols="mapview ? 6 : 12">
- <!-- Search form -->
- <v-row>
- <v-form method="get" class="mt-8 w100">
- <v-container>
- <v-row>
- <v-col cols="6" class="py-2 px-1">
- <v-text-field
- v-model="textFilter"
- type="text"
- outlined
- clearable
- hide-details
- append-icon="mdi-magnify"
- :label="$t('what') + ' ?'"
- @click:append="search"
- @keydown.enter="search"
- />
- </v-col>
- <v-col cols="6" class="py-2 px-1">
- <UiSearchAddress
- ref="addressSearch"
- type="municipality"
- @change="locationFilterChanged"
- />
- </v-col>
- </v-row>
- <v-row>
- <v-col v-if="listview" cols="2" class="py-2 px-1">
- <v-btn class="h100" @click="reinitializeFilters">
- {{ $t('reinitialize') }}
- </v-btn>
- </v-col>
- <v-col :cols="listview ? 8 : 12">
- <v-row class="filters">
- <v-col :cols="3" class="py-2 px-1">
- <v-select
- v-model="practicesFilter"
- :label="$t('type')"
- :items="translatedPractices"
- item-value="id"
- item-text="label"
- filled
- hide-details
- @change="search"
- />
- </v-col>
- <v-col :cols="3" class="py-2 px-1">
- <v-select
- v-model="departmentFilter"
- :items="departments"
- item-value="code"
- item-text="label"
- :label="$t('department')"
- filled
- hide-details
- @change="search"
- />
- </v-col>
- <v-col :cols="3" class="py-2 px-1">
- <v-select
- v-model="federationFilter"
- :items="federations"
- item-value="id"
- item-text="name"
- :label="$t('federation')"
- filled
- hide-details
- @change="search"
- />
- </v-col>
- <v-col :cols="3" class="py-2 px-1">
- <v-select
- v-model="distanceFilter"
- :label="$t('distance')"
- :items="[
- {distance: 10, label: '10km'},
- {distance: 30, label: '30km'},
- {distance: 100, label: '100km'},
- {distance: 200, label: '200km'}
- ]"
- item-value="distance"
- item-text="label"
- filled
- hide-details
- @change="search"
- />
- </v-col>
- </v-row>
- </v-col>
- <v-col v-if="listview" cols="2" class="py-2 px-1 d-flex justify-end">
- <v-btn class="h100">
- {{ $t('search') }}
- </v-btn>
- </v-col>
- </v-row>
- <v-row v-show="mapview" class="px-2 pt-2">
- <v-btn @click="reinitializeFilters">
- {{ $t('reinitialize') }}
- </v-btn>
- <v-spacer />
- <v-btn @click="search">
- {{ $t('search') }}
- </v-btn>
- </v-row>
- </v-container>
- </v-form>
- </v-row>
- <!-- loading skeleton -->
- <div class="pt-4 mt-6">
- <v-container v-if="$fetchState.pending">
- <v-row v-for="i in 3" :key="i" justify="space-between" class="mt-1 mb-3">
- <v-col
- v-for="j in 2"
- :key="j"
- cols="12"
- sm="12"
- :md="mapview ? 6 : 12"
- class="py-2 px-1"
- >
- <v-skeleton-loader
- type="card"
- :loading="true"
- />
- </v-col>
- </v-row>
- </v-container>
- <!-- Results -->
- <v-data-iterator
- v-else
- :items="onMapFilteredStructures"
- :page.sync="page"
- :items-per-page="itemsPerPage"
- sort-by="name"
- hide-default-footer
- no-data-text=""
- >
- <template #header>
- <i class="results-count">{{ totalRecords }} {{ $t('results') }}</i>
- </template>
- <template #default="props">
- <v-row justify="space-between" class="mt-1 mb-3">
- <v-col
- v-for="structure in props.items"
- :key="structure.name"
- cols="12"
- sm="12"
- :md="mapview ? 6 : 12"
- class="py-2 px-1"
- >
- <v-card
- elevation="1"
- outlined
- :class="'structure-card pa-3 d-flex ' + (mapview ? 'flex-column' : 'flex-row align-items-center')"
- >
- <div class="d-flex justify-center">
- <v-img
- :src="structure.logoId ? ('https://api.opentalent.fr/app.php/_internal/secure/files/' + structure.logoId) : '/images/default.jpg'"
- alt="poster"
- height="80px"
- min-width="160px"
- max-width="80%"
- max-height="100%"
- :contain="true"
- style="margin: 12px;"
- />
- </div>
- <div class="d-flex flex-column">
- <v-chip-group v-if="structure.practices" active-class="primary--text">
- <v-chip v-for="practice in structure.practices" :key="practice" outlined small pill>
- {{ $t(practice) }}
- </v-chip>
- </v-chip-group>
- <v-card-title class="title">
- {{ structure.name }}
- </v-card-title>
- <v-card-text class="infos">
- <table>
- <tr>
- <td class="py-1 pr-2">
- <font-awesome-icon class="icon" :icon="['fas', 'map-marker-alt']" />
- </td>
- <td class="py-1">
- <span v-if="structure.streetAddress">{{ structure.streetAddress }}<br></span>
- <span v-if="structure.postalCode">{{ structure.postalCode }} </span>
- {{ structure.addressCity }}
- </td>
- </tr>
- <tr>
- <td class="py-1 pr-2">
- <font-awesome-icon class="icon" :icon="['fas', 'project-diagram']" />
- </td>
- <td class="py-1">
- {{ structure.n1Name }}
- </td>
- </tr>
- </table>
- </v-card-text>
- </div>
- <span class="flex-fill" />
- <v-card-actions :class="listview ? 'align-self-end' : ''">
- <v-btn class="see" :to="'/structures/' + structure.id" nuxt>
- <span style="margin-right: 6px;">{{ $t("see_more") }}</span>
- <font-awesome-icon :icon="['fa', 'caret-right']" />
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-col>
- </v-row>
- </template>
- <template #footer>
- <v-pagination
- v-model="page"
- :length="pageCount"
- total-visible="9"
- color="#e4611b"
- />
- </template>
- </v-data-iterator>
- </div>
- </v-col>
- </v-row>
- </LayoutContainer>
- </template>
- <script lang="ts">
- import Vue from 'vue'
- import departments from '@/enums/departments'
- import practices from '@/enums/practices'
- import sphericDistance from '@/services/utils/geo'
- import StructuresProvider from '~/services/data/StructuresProvider'
- import { LatLngBoundsExpression } from 'leaflet'
- export default Vue.extend({
- data () {
- return {
- structures: [] as Array<Structure>,
- filteredStructures: [] as Array<Structure>,
- federations: [] as Array<{ id: number | null, name: string | null }>,
- loading: true,
- page: 1,
- itemsPerPage: 8,
- mapview: true,
- departments: departments as {code: string, label: string}[],
- practices: practices as {id: string}[],
- textFilter: null as string | null,
- locationFilter: null as Address | null,
- practicesFilter: null as string | null,
- departmentFilter: null as string | null,
- federationFilter: null as number | null,
- distanceFilter: null as number | null,
- mapBoundsFilter: null as LatLngBoundsExpression | null,
- mapBoundsFilterStarted: false // map bounds filter is only activated when the map bounds are updated
- }
- },
- async fetch () {
- await new StructuresProvider(this.$axios).getAll(12097).then(
- (res) => {
- this.structures = res
- this.filteredStructures = res
- // populate federations filter
- for (const s of res) {
- const f = {
- id: s.n1Id,
- name: s.n1Name
- }
- if (!this.federations.includes(f)) {
- this.federations.push(f)
- }
- }
- })
- },
- computed: {
- onMapFilteredStructures (): Array<Structure> {
- if (this.mapBoundsFilterStarted) {
- return this.filteredStructures.filter((s) => {
- return this.matchMapBounds(s)
- })
- } else {
- return this.filteredStructures
- }
- },
- totalRecords (): number {
- return this.onMapFilteredStructures.length
- },
- pageCount (): number {
- return Math.floor(this.totalRecords / this.itemsPerPage) + 1
- },
- listview (): boolean {
- return !this.mapview
- },
- translatedPractices (): Array<{ id: string, label: string }> {
- const tPractices = []
- for (const practice of this.practices) {
- tPractices.push({ id: practice.id, label: this.$t(practice.id) as string })
- }
- return tPractices
- }
- },
- methods: {
- viewChanged (e: number) {
- this.mapview = (e === 0)
- },
- textFilterChanged (newVal: string) {
- this.textFilter = newVal
- },
- locationFilterChanged (newVal: Address) {
- this.locationFilter = newVal
- if (this.distanceFilter === null) {
- this.distanceFilter = 10
- }
- this.search()
- },
- fitMapToResults (): void {
- (this.$refs.map as any).zoomOnResults()
- },
- mapBoundsFilterChanged (newBounds: LatLngBoundsExpression) {
- this.mapBoundsFilterStarted = true
- this.mapBoundsFilter = newBounds
- },
- reinitializeFilters (): void {
- this.textFilter = null
- this.locationFilter = null
- this.practicesFilter = null
- this.departmentFilter = null
- this.federationFilter = null
- this.distanceFilter = null
- this.mapBoundsFilter = null
- this.$refs.addressSearch.clear()
- this.$refs.map.resetBounds()
- this.filteredStructures = this.structures
- },
- /**
- * Does the structure match the current textFilter
- * @param structure
- * @returns {boolean}
- */
- matchTextFilter (structure: Structure): boolean {
- if (!this.textFilter) { return true }
- return structure.name.toLowerCase().includes(this.textFilter.toLowerCase())
- },
- /**
- * Does the structure match the current locationFilter
- * @param structure
- * @returns {boolean}
- */
- matchLocationFilter (structure: Structure): boolean {
- if (!this.locationFilter) { return true }
- if (!structure.latitude || !structure.longitude) { return false }
- const radius = Number(this.distanceFilter) ?? 5
- return sphericDistance(
- this.locationFilter.latitude, this.locationFilter.longitude, structure.latitude, structure.longitude
- ) <= radius
- },
- /**
- * Does the structure match the current practicesFilter
- * @param structure
- * @returns {boolean}
- */
- matchPracticesFilter (structure: Structure): boolean {
- if (!this.practicesFilter) { return true }
- return structure.practices && structure.practices.includes(this.practicesFilter)
- },
- /**
- * Does the structure match the current departmentFilter
- * @param structure
- * @returns {boolean}
- */
- matchDepartmentFilter (structure: Structure): boolean {
- if (!this.departmentFilter) { return true }
- return structure.postalCode.startsWith(this.departmentFilter)
- },
- /**
- * Does the structure match the current federationFilter
- * @param structure
- * @returns {boolean}
- */
- matchFederationFilter (structure: Structure): boolean {
- if (!this.federationFilter) { return true }
- return structure.parents.includes(Number(this.federationFilter))
- },
- /**
- * Does the structure match the current federationFilter
- * @param structure
- * @returns {boolean}
- */
- matchMapBounds (structure: Structure): boolean {
- if (!this.mapBoundsFilter) { return true }
- return structure.latitude && structure.longitude &&
- typeof structure.latitude !== 'undefined' &&
- typeof structure.longitude !== 'undefined' &&
- this.mapBoundsFilter.getSouth() <= structure.latitude &&
- structure.latitude <= this.mapBoundsFilter.getNorth() &&
- this.mapBoundsFilter.getWest() <= structure.longitude &&
- structure.longitude <= this.mapBoundsFilter.getEast()
- },
- /**
- * Does the structure match each of the page filters
- * @param structure
- * @returns {boolean}
- */
- matchFilters (structure: Structure): boolean {
- return this.matchTextFilter(structure) &&
- this.matchLocationFilter(structure) &&
- this.matchPracticesFilter(structure) &&
- this.matchDepartmentFilter(structure) &&
- this.matchFederationFilter(structure)
- // this.matchMapBounds(structure)
- },
- /**
- * Update the filteredStructures array
- */
- search (): void {
- this.filteredStructures = this.structures.filter((s) => { return this.matchFilters(s) })
- this.fitMapToResults()
- }
- }
- })
- </script>
- <style scoped lang="scss">
- @import 'assets/style/variables.scss';
- h2 {
- color: $theme;
- }
- .structure-card {
- height: 100%;
- color: #666666;
- }
- .infos .col {
- padding: 6px 12px;
- }
- .title {
- word-break: normal;
- color: $theme;
- font-size: 18px;
- font-weight: 500;
- line-height: 1.6rem;
- }
- .icon {
- color: $theme;
- }
- .results-count {
- font-size: .8em;
- color: #666;
- }
- </style>
|