Skip to content

Commit 0c8889a

Browse files
committed
feat: enhance jsonforms-renderer and schemas-bar components with improved prop handling and styling; add photo control renderer
1 parent 3095417 commit 0c8889a

File tree

4 files changed

+174
-15
lines changed

4 files changed

+174
-15
lines changed

apps/web/src/components/core/jsonforms-renderer.vue

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,16 @@ export default defineNuxtComponent({
6969
components: {
7070
JsonForms,
7171
},
72-
setup({ mode, schemaName, manualSchema, manualUiSchema, modelValue, baseUrlValidation, baseUrlSchema }) {
72+
setup(props) {
7373
const renderers = Object.freeze([...quasarRenderers])
7474
75-
const employeeType = computed(() => modelValue?.inetOrgPerson?.employeeType || 'LOCAL')
75+
const employeeType = computed(() => {
76+
// `modelValue` can be the full identity object or directly inetOrgPerson.
77+
return props.modelValue?.employeeType || props.modelValue?.inetOrgPerson?.employeeType || 'LOCAL'
78+
})
79+
7680
77-
if (!schemaName && (!manualSchema || !manualUiSchema)) {
81+
if (!props.schemaName && (!props.manualSchema || !props.manualUiSchema)) {
7882
throw new Error('Either schemaName or manualSchema/manualUiSchema props must be provided')
7983
}
8084
@@ -90,10 +94,10 @@ export default defineNuxtComponent({
9094
return err[0].message
9195
}
9296
93-
if (!schemaName) {
97+
if (!props.schemaName) {
9498
return {
95-
schema: computed(() => manualSchema),
96-
uischema: computed(() => manualUiSchema),
99+
schema: computed(() => props.manualSchema),
100+
uischema: computed(() => props.manualUiSchema),
97101
pending: computed(() => false),
98102
error: computed(() => null),
99103
refresh: () => {},
@@ -108,7 +112,7 @@ export default defineNuxtComponent({
108112
pending,
109113
error,
110114
refresh,
111-
} = useHttp<any>(`${baseUrlValidation}${schemaName}`, {
115+
} = useHttp<any>(`${props.baseUrlValidation}${props.schemaName}`, {
112116
method: 'GET',
113117
})
114118
@@ -117,18 +121,18 @@ export default defineNuxtComponent({
117121
pending: pendingUi,
118122
error: errorUi,
119123
refresh: refreshUi,
120-
} = useHttp<any>(`${baseUrlSchema}${schemaName}`, {
124+
} = useHttp<any>(`${props.baseUrlSchema}${props.schemaName}`, {
121125
method: 'POST',
122126
params: {
123-
mode,
127+
mode: props.mode,
124128
},
125129
query: {
126-
mode,
130+
mode: props.mode,
127131
},
128132
watch: [employeeType],
129-
body: {
130-
employeeType,
131-
},
133+
body: computed(() => ({
134+
employeeType: employeeType.value,
135+
})),
132136
})
133137
134138
const schema = computed(() => result.value?.data)

apps/web/src/components/pages/identities/schemas-bar.vue

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template lang="pug">
2-
div
2+
div.schemas-bar.full-height
33
.sesame-sticky-space
44
q-bar.q-px-none.sesame-sticky-bar(:style="{ top: topOffset || '0px' }")
55
q-tabs(
@@ -75,7 +75,7 @@ div
7575
span(v-else-if="schemas.length === 0") Tous les schémas sont déjà ajoutés
7676
span(v-else-if="!readonly") Ajouter un schéma
7777
span(v-else) Impossible d'ajouter un schéma en mode lecture seule
78-
q-tab-panels(v-model="tab" keep-alive)
78+
q-tab-panels.schemas-bar__panels(v-model="tab" keep-alive)
7979
slot(name='items' :tabs="tabs")
8080
q-tab-panel.q-pa-none(v-for="key in ['inetOrgPerson', ...tabs]" :key="key" :name="key")
8181
div.q-pa-md Unknown schema "{{ key }}"
@@ -207,3 +207,18 @@ export default defineNuxtComponent({
207207
},
208208
})
209209
</script>
210+
211+
<style lang="scss" scoped>
212+
.schemas-bar {
213+
display: flex;
214+
flex-direction: column;
215+
min-height: 0;
216+
overflow: hidden;
217+
}
218+
219+
.schemas-bar__panels {
220+
flex: 1 1 auto;
221+
min-height: 0;
222+
overflow: auto;
223+
}
224+
</style>

apps/web/src/jsonforms/controls/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export { default as numericControlRenderer } from './numeric.vue'
3434
export { default as TextareaControlRenderer } from './textarea.vue'
3535
export { default as AutocompleteControlRenderer } from './autocomplete.vue'
3636
export { default as FileUploadControlRenderer } from './file-upload.vue'
37+
export { default as PhotoControlRenderer } from './photo.vue'
3738
export { default as AclControlRenderer } from './acl.vue'
3839

3940
import InputControlRendererComponent from './input.vue'
@@ -47,6 +48,7 @@ import NumericControlRendererComponent from './numeric.vue'
4748
import TextareaControlRendererComponent from './textarea.vue'
4849
import AutocompleteControlRendererComponent from './autocomplete.vue'
4950
import FileUploadControlRendererComponent from './file-upload.vue'
51+
import PhotoControlRendererComponent from './photo.vue'
5052
import AclControlRendererComponent from './acl.vue'
5153

5254
const inputControlRendererEntry: JsonFormsRendererRegistryEntry = {
@@ -104,6 +106,11 @@ const fileUploadControlRendererEntry: JsonFormsRendererRegistryEntry = {
104106
tester: rankWith(3, and(or(isStringControl, isObjectArrayControl, isObjectControl), optionIs('format', 'file'))), // Rend prioritaire les contrôles string, object ou array avec options.format === 'file'
105107
}
106108

109+
const photoControlRendererEntry: JsonFormsRendererRegistryEntry = {
110+
renderer: PhotoControlRendererComponent,
111+
tester: rankWith(20, and(or(isStringControl, isObjectArrayControl, isObjectControl), and(optionIs('format', 'file'), optionIs('storage', 'picture')))),
112+
}
113+
107114
const aclControlRendererEntry: JsonFormsRendererRegistryEntry = {
108115
renderer: AclControlRendererComponent,
109116
tester: rankWith(30, and(isPrimitiveArrayControl, optionIs('format', 'acl'))),
@@ -120,6 +127,7 @@ export const controlsRenderers = [
120127
numericControlRendererEntry,
121128
textareaControlRendererEntry,
122129
autocompleteControlRendererEntry,
130+
photoControlRendererEntry,
123131
fileUploadControlRendererEntry,
124132
aclControlRendererEntry,
125133
]
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<template lang="pug">
2+
control-wrapper(
3+
v-bind="controlWrapper"
4+
v-model:is-hovered="isHovered"
5+
:styles="styles"
6+
:is-focused="isFocused"
7+
:applied-options="appliedOptions"
8+
)
9+
.photo-control.q-mb-md(:class="styles.control.input")
10+
.text-caption.text-grey-8.q-mb-xs(v-if="computedLabel") {{ computedLabel }}
11+
q-img(
12+
v-if="photoUrl && !hasError"
13+
:src="photoUrl"
14+
:ratio="1"
15+
fit="contain"
16+
spinner-color="primary"
17+
class="photo-control__image"
18+
:height="appliedOptions.height || '400px'"
19+
@error="onImageError"
20+
)
21+
template(#error)
22+
.photo-control__placeholder
23+
q-icon(name="mdi-account" size="48px" color="grey-6")
24+
span.text-caption.text-grey-7 Photo indisponible
25+
.photo-control__placeholder(v-else)
26+
q-icon(name="mdi-account" size="48px" color="grey-6")
27+
span.text-caption.text-grey-7 Aucune photo
28+
.text-caption.text-negative.q-mt-xs(v-if="control.errors") {{ control.errors }}
29+
</template>
30+
31+
<script lang="ts">
32+
import { type ControlElement } from '@jsonforms/core'
33+
import { defineComponent, computed, ref, watch } from 'vue'
34+
import { rendererProps, type RendererProps, useJsonFormsControl } from '@jsonforms/vue'
35+
import { QImg, QIcon } from 'quasar'
36+
import { isArray } from 'radash'
37+
import { ControlWrapper } from '../common'
38+
import { determineClearValue, useJsonForms, useQuasarControl } from '../utils'
39+
40+
const controlRenderer = defineComponent({
41+
name: 'PhotoControlRenderer',
42+
components: {
43+
ControlWrapper,
44+
QImg,
45+
QIcon,
46+
},
47+
props: {
48+
...rendererProps<ControlElement>(),
49+
},
50+
setup(props: RendererProps<ControlElement>) {
51+
const jsonFormsControl = useJsonFormsControl(props)
52+
const clearValue = determineClearValue(undefined)
53+
const api = useQuasarControl(jsonFormsControl, (value) => value ?? clearValue, 100)
54+
const jsonforms = useJsonForms()
55+
const auth = useAuth()
56+
57+
const hasError = ref(false)
58+
59+
const employeeNumber = computed(() => {
60+
const value = (jsonforms as any)?.core?.data?.employeeNumber
61+
if (isArray(value)) return value[0]
62+
return value
63+
})
64+
65+
const employeeType = computed(() => {
66+
const value = (jsonforms as any)?.core?.data?.employeeType
67+
if (isArray(value)) return value[0]
68+
return value
69+
})
70+
71+
const photoUrl = computed(() => {
72+
const query = new URLSearchParams()
73+
const employeeNumberValue = employeeNumber.value
74+
const employeeTypeValue = employeeType.value
75+
76+
if (!employeeNumberValue || !employeeTypeValue) return ''
77+
if (!auth.user?._id || !auth.user?.sseToken) return ''
78+
79+
query.append('filters[:inetOrgPerson.employeeNumber]', `${employeeNumberValue}`)
80+
query.append('filters[:inetOrgPerson.employeeType]', `${employeeTypeValue}`)
81+
query.append('id', `${auth.user._id}`)
82+
query.append('key', `${auth.user.sseToken}`)
83+
84+
return `/api/management/identities/photo/raw?${query.toString()}`
85+
})
86+
87+
watch(photoUrl, () => {
88+
hasError.value = false
89+
})
90+
91+
const onImageError = () => {
92+
hasError.value = true
93+
}
94+
95+
return {
96+
...api,
97+
photoUrl,
98+
hasError,
99+
onImageError,
100+
}
101+
},
102+
})
103+
104+
export default controlRenderer
105+
</script>
106+
107+
<style lang="scss" scoped>
108+
.photo-control {
109+
width: 100%;
110+
max-width: none;
111+
}
112+
113+
.photo-control__image {
114+
width: 100%;
115+
border-radius: 8px;
116+
border: 1px solid rgba(0, 0, 0, 0.12);
117+
overflow: hidden;
118+
}
119+
120+
.photo-control__placeholder {
121+
display: flex;
122+
flex-direction: column;
123+
align-items: center;
124+
justify-content: center;
125+
gap: 4px;
126+
width: 100%;
127+
min-height: 180px;
128+
border-radius: 8px;
129+
border: 1px dashed rgba(0, 0, 0, 0.2);
130+
background: rgba(0, 0, 0, 0.03);
131+
}
132+
</style>

0 commit comments

Comments
 (0)