Skip to content

Commit fc7e08c

Browse files
author
Sebastien
committed
complete site module migration without wkt features
1 parent 3197888 commit fc7e08c

35 files changed

Lines changed: 3755 additions & 47 deletions

opensilex-front/front/opensilex.front.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ menu:
6060
credentials:
6161
- organization-access
6262
icon: bi#bi-geo-alt
63-
title: SiteView.title
64-
description: SiteView.description
63+
title: component.common.organization.sites
64+
description: component.site.description
6565
- id: persons
6666
label: component.menu.security.persons
6767
route:
@@ -307,8 +307,8 @@ routes:
307307
# - path: /organization/details/:uri
308308
# component: opensilex-OrganizationDetailView
309309
# icon: ik#ik-globe
310-
# - path: /organization/site/details/:uri
311-
# component: opensilex-SiteView
310+
- path: /organization/site/details/:uri
311+
component: opensilex-SiteDetailView
312312
# - path: /experiment/details/:uri
313313
# component: opensilex-ExperimentView
314314
# rdfType: vocabulary:Experiment
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<template>
2+
<opensilex-Button
3+
@click="emit('click')"
4+
variant="outline-success"
5+
icon="fa#plus"
6+
:small="small"
7+
:label="label"
8+
:disabled="disabled"
9+
/>
10+
</template>
11+
12+
<script setup lang="ts">
13+
const props = withDefaults(
14+
defineProps<{
15+
label?: string
16+
small?: boolean
17+
disabled?: boolean
18+
}>(),
19+
{
20+
label: undefined,
21+
small: false,
22+
disabled: false
23+
}
24+
)
25+
26+
const emit = defineEmits<{
27+
(e: 'click'): void
28+
}>()
29+
30+
const { label, small, disabled } = props
31+
</script>
32+
33+
<style scoped lang="scss"></style>

opensilex-front/front/src/components/common/buttons/DeleteButton.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<!-- Modal Bootstrap 5 -->
1313
<teleport to="body">
1414
<div
15-
class="modal fade"
15+
class="modal fade delete-confirm-modal"
1616
tabindex="-1"
1717
ref="modalRef"
1818
aria-labelledby="deleteModalLabel"
@@ -78,6 +78,19 @@ onMounted(() => {
7878
7979
function openModal() {
8080
modalInstance?.show()
81+
82+
setTimeout(() => {
83+
// force z-index modal au dessus pour les modales supperpo
84+
if (modalRef.value) (modalRef.value as any).style.zIndex = '10050'
85+
86+
// force z-index du backdrop Bootstrap (celui créé en dernier)
87+
const backdrops = document.querySelectorAll('.modal-backdrop')
88+
const last = backdrops[backdrops.length - 1] as HTMLElement | undefined
89+
if (last) {
90+
last.classList.add('delete-confirm-backdrop')
91+
last.style.zIndex = '10040'
92+
}
93+
})
8194
}
8295
8396
function closeModal() {
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
<template>
2+
<div v-if="addressState">
3+
<!-- Street address -->
4+
<opensilex-InputForm
5+
v-model:value="addressState.streetAddress"
6+
:label="t('AddressForm.streetAddress.label')"
7+
type="text"
8+
:placeholder="t('AddressForm.streetAddress.placeholder')"
9+
@input="onAddressInput"
10+
@blur="onAddressBlur"
11+
/>
12+
13+
<!-- Autocomplete -->
14+
<div ref="autocompleteMenuRef" class="autocompleteMenu btn-group-vertical w-100">
15+
<button
16+
v-for="address in autocompleteAddressList"
17+
:key="address.readableAddress"
18+
type="button"
19+
class="btn btn-light text-start js-autocompleteButton"
20+
@click.prevent.stop="onAddressAutocompleteClick(address)"
21+
>
22+
{{ address.readableAddress }}
23+
</button>
24+
</div>
25+
26+
<!-- Locality -->
27+
<opensilex-InputForm
28+
v-model:value="addressState.locality"
29+
:label="t('AddressForm.locality.label')"
30+
type="text"
31+
:placeholder="t('AddressForm.locality.placeholder')"
32+
/>
33+
34+
<div class="row">
35+
<div class="col-12 col-lg-8">
36+
<!-- Region -->
37+
<opensilex-InputForm
38+
v-model:value="addressState.region"
39+
:label="t('AddressForm.region.label')"
40+
type="text"
41+
:placeholder="t('AddressForm.region.placeholder')"
42+
/>
43+
</div>
44+
45+
<div class="col-12 col-lg-4">
46+
<!-- Postal code -->
47+
<opensilex-InputForm
48+
v-model:value="addressState.postalCode"
49+
:label="t('AddressForm.postalCode.label')"
50+
type="text"
51+
:placeholder="t('AddressForm.postalCode.placeholder')"
52+
/>
53+
</div>
54+
</div>
55+
56+
<!-- Country -->
57+
<opensilex-InputForm
58+
v-model:value="addressState.countryName"
59+
:label="t('AddressForm.countryName.label')"
60+
type="text"
61+
:placeholder="t('AddressForm.countryName.placeholder')"
62+
/>
63+
</div>
64+
</template>
65+
66+
<script setup lang="ts">
67+
import { inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'
68+
import { useI18n } from 'vue-i18n'
69+
import type OpenSilexVuePlugin from '@/models/OpenSilexVuePlugin'
70+
import type IGeocodingService from "../../../services/geocoding/IGeocodingService";
71+
import type { GeocodingAddressResult } from "../../../services/geocoding/IGeocodingService";
72+
import type { FacilityAddressDTO } from 'opensilex-core/index'
73+
74+
const { t } = useI18n()
75+
const AUTOCOMPLETE_TIMEOUT_MS = 250
76+
77+
const props = defineProps<{
78+
address: FacilityAddressDTO
79+
}>()
80+
81+
const emit = defineEmits<{
82+
(e: 'update:address', v: FacilityAddressDTO): void
83+
}>()
84+
85+
const $opensilex = inject<OpenSilexVuePlugin>('$opensilex')!
86+
const geocodingService = $opensilex.getService<IGeocodingService>('IGeocodingService')
87+
88+
const addressState = ref<FacilityAddressDTO>(props.address)
89+
90+
watch(
91+
() => props.address,
92+
(v) => {
93+
addressState.value = v
94+
},
95+
{ deep: true }
96+
)
97+
98+
watch(
99+
addressState,
100+
(v) => emit('update:address', v),
101+
{ deep: true }
102+
)
103+
104+
const autocompleteMenuRef = ref<HTMLElement | null>(null)
105+
const autocompleteAddressList = ref<GeocodingAddressResult[]>([])
106+
let addressInputTimeout: ReturnType<typeof setTimeout> | undefined
107+
108+
function clearAutocompleteMenu() {
109+
autocompleteAddressList.value = []
110+
}
111+
112+
function onDocumentClick(e: MouseEvent) {
113+
// Si click dans le menu autocomplete -> ne pas fermer
114+
const menu = autocompleteMenuRef.value
115+
if (menu && e.target && menu.contains(e.target as Node)) return
116+
clearAutocompleteMenu()
117+
}
118+
119+
onMounted(() => {
120+
document.addEventListener('click', onDocumentClick)
121+
})
122+
123+
onBeforeUnmount(() => {
124+
document.removeEventListener('click', onDocumentClick)
125+
if (addressInputTimeout) clearTimeout(addressInputTimeout)
126+
})
127+
128+
function onAddressBlur() {
129+
if (addressInputTimeout) clearTimeout(addressInputTimeout)
130+
}
131+
132+
function onAddressInput() {
133+
if (addressInputTimeout) clearTimeout(addressInputTimeout)
134+
addressInputTimeout = setTimeout(() => {
135+
onAddressInputPause(addressState.value?.streetAddress)
136+
}, AUTOCOMPLETE_TIMEOUT_MS)
137+
}
138+
139+
async function onAddressInputPause(partialAddress?: string) {
140+
if (!partialAddress || !partialAddress.trim()) {
141+
autocompleteAddressList.value = []
142+
return
143+
}
144+
const lang = $opensilex.getLang()
145+
try {
146+
const addresses = await geocodingService.search(partialAddress, { lang })
147+
autocompleteAddressList.value = addresses ?? []
148+
} catch (e) {
149+
console.warn('Error while fetching address suggestions', e)
150+
}
151+
}
152+
153+
function onAddressAutocompleteClick(address: GeocodingAddressResult) {
154+
if (!addressState.value) return
155+
156+
addressState.value.countryName = address.country
157+
addressState.value.postalCode = address.postcode
158+
addressState.value.region = address.state ? address.state : address.county
159+
addressState.value.locality = address.city
160+
161+
let streetAddress = ''
162+
if (address.street && address.street.length > 0) {
163+
if (address.houseNumber && address.houseNumber.length > 0) {
164+
streetAddress += `${address.houseNumber}, `
165+
}
166+
streetAddress += address.street
167+
} else if ((address as any).name) {
168+
streetAddress = (address as any).name
169+
}
170+
171+
addressState.value.streetAddress = streetAddress
172+
autocompleteAddressList.value = []
173+
}
174+
</script>
175+
176+
<style scoped lang="scss">
177+
.autocompleteMenu {
178+
position: absolute;
179+
z-index: 1000;
180+
max-height: 240px;
181+
overflow: auto;
182+
}
183+
</style>
184+
185+
<i18n>
186+
en:
187+
AddressForm:
188+
streetAddress:
189+
label: "Address"
190+
placeholder: "Number and street name (start typing to see suggestions...)"
191+
locality:
192+
label: "City"
193+
placeholder: "City"
194+
region:
195+
label: "Region"
196+
placeholder: "State or region"
197+
postalCode:
198+
label: "Postal code"
199+
placeholder: "Zip code"
200+
countryName:
201+
label: "Country"
202+
placeholder: "Country name"
203+
fr:
204+
AddressForm:
205+
streetAddress:
206+
label: "Adresse"
207+
placeholder: "Numéro et nom de rue (commencer à taper pour voir des suggestions...)"
208+
locality:
209+
label: "Ville"
210+
placeholder: "Ville"
211+
region:
212+
label: "Région"
213+
placeholder: "Région ou état"
214+
postalCode:
215+
label: "Code postal"
216+
placeholder: "Code postal"
217+
countryName:
218+
label: "Pays"
219+
placeholder: "Nom du pays"
220+
</i18n>

opensilex-front/front/src/components/common/forms/CustomTreeselect.vue

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
</template>
2020

2121
<script setup lang="ts">
22-
import { ref, watch, computed, onMounted, nextTick } from 'vue'
22+
import { ref, watch, computed, onMounted, nextTick, useAttrs } from 'vue'
2323
import { NTreeSelect } from 'naive-ui'
2424
import { debounce } from 'lodash-es'
2525
@@ -76,6 +76,18 @@ const emit = defineEmits<{
7676
(e: 'enterKey', value: KeyboardEvent): void
7777
}>()
7878
79+
const attrs = useAttrs()
80+
81+
// filterable: si l’appelant le force via $attrs, il gagne.
82+
// sinon: filterable seulement si searchMethod
83+
const isFilterable = computed(() => {
84+
const attrVal = (attrs as any).filterable
85+
if (typeof attrVal === 'boolean') return attrVal
86+
if (attrVal === '' || attrVal === 'true') return true
87+
if (attrVal === 'false') return false
88+
return !!props.searchMethod
89+
})
90+
7991
// state
8092
const treeref = ref<InstanceType<typeof NTreeSelect> | null>(null)
8193
const options = ref<TreeOpt[]>([]) // options Naive {key,label}
@@ -107,7 +119,7 @@ const treeSelectBindings = computed(() => ({
107119
checkable: props.checkable,
108120
options: options.value,
109121
clearable: true,
110-
filterable: !!props.searchMethod,
122+
filterable: isFilterable.value,
111123
disabled: props.disabled,
112124
placeholder: props.placeholder,
113125
value: value.value,
@@ -272,9 +284,11 @@ async function runSearch(rawQuery: string, overrideLimit?: number) {
272284
emit('resultCount', resultCount.value)
273285
}
274286
const debounceSearch = debounce(runSearch, 250)
275-
function onSearchChange(q: string) {
276-
console.log('[CTS] pattern ->', q)
277-
debounceSearch(q) }
287+
288+
// si pas de searchMethod: Naive filtre localement -> ne rien faire
289+
function onSearchChange(q: string) {
290+
if (props.searchMethod) debounceSearch(q)
291+
}
278292
279293
function handleFocus() {
280294
// if (!options.value.length) runSearch('.*')

0 commit comments

Comments
 (0)