Преглед изворни кода

v8-3565 nuxt3 upgrade and partial restoration of organization page

Olivier Massot пре 3 година
родитељ
комит
3f16ce8b8c
51 измењених фајлова са 1656 додато и 1341 уклоњено
  1. 1 1
      assets/css/global.scss
  2. 10 14
      components/Layout/Dialog.vue
  3. 1 1
      components/Ui/Button/Delete.vue
  4. 10 10
      components/Ui/Button/Submit.vue
  5. 1 1
      components/Ui/Card.vue
  6. 5 2
      components/Ui/Collection.vue
  7. 1 1
      components/Ui/DataTable.vue
  8. 50 0
      components/Ui/ExpansionPanel.vue
  9. 64 29
      components/Ui/Form.vue
  10. 84 76
      components/Ui/Image.vue
  11. 51 24
      components/Ui/Input/Enum.vue
  12. 57 26
      components/Ui/Input/Text.vue
  13. 61 0
      components/Ui/ItemFromUri.vue
  14. 6 4
      composables/data/useAp2iRequestService.ts
  15. 1 1
      composables/data/useEntityFetch.ts
  16. 1 1
      composables/data/useImageFetch.ts
  17. 14 13
      composables/form/useFieldViolation.ts
  18. 12 0
      composables/layout/useRedirectToLogin.ts
  19. 4 0
      doc/Débugger en production.md
  20. 0 9
      lang/breadcrumbs/fr-FR.js
  21. 0 19
      lang/content/parameters/fr-FR.js
  22. 0 44
      lang/content/subscription/fr-FR.js
  23. 0 120
      lang/enum/fr-FR.js
  24. 0 135
      lang/field/fr-FR.js
  25. 0 28
      lang/form/fr-FR.js
  26. 0 25
      lang/fr-FR.js
  27. 570 0
      lang/fr-FR.json
  28. 0 26
      lang/help/fr-FR.js
  29. 0 222
      lang/layout/fr-FR.js
  30. 0 8
      lang/menuKey/fr-FR.js
  31. 0 24
      lang/rulesAndErrors/fr-FR.js
  32. 11 0
      middleware/auth.ts
  33. 19 2
      nuxt.config.ts
  34. 28 25
      package.json
  35. 412 403
      pages/organization/index.vue
  36. 45 18
      pages/poc/[id].vue
  37. 54 0
      pages/poc/display.vue
  38. 3 0
      pages/poc/i18n.vue
  39. 16 14
      pages/poc/index.vue
  40. 2 2
      plugins/ability.ts
  41. 2 3
      plugins/casl.ts
  42. 22 0
      plugins/cleave.ts
  43. 24 6
      plugins/init.server.ts
  44. 0 0
      public/images/default/men-1.png
  45. 0 0
      public/images/default/picture.jpeg
  46. 0 0
      public/images/default/women-1.png
  47. 1 0
      services/data/entityManager.ts
  48. 3 2
      services/data/imageManager.ts
  49. 6 0
      services/error/UnauthorizedError.ts
  50. 1 1
      store/profile/organization.ts
  51. 3 1
      tsconfig.json

+ 1 - 1
assets/css/global.scss

@@ -1,5 +1,5 @@
 header .v-toolbar__content{
-  padding-right: 0px;
+  padding-right: 0;
 }
 
 .e-grid .e-gridheader {

+ 10 - 14
components/Layout/Dialog.vue

@@ -1,7 +1,7 @@
 <!-- Fenêtre de dialogue -->
 <template>
   <v-dialog
-    v-model="show"
+    :value="show"
     persistent
     max-width="800"
     :content-class="contentClass"
@@ -34,19 +34,15 @@
   </v-dialog>
 </template>
 
-<script lang="ts">
-import { defineComponent } from '@nuxtjs/composition-api'
-
-export default defineComponent({
-  props: {
-    show: {
-      type: Boolean,
-      required: true
-    },
-    contentClass: {
-      type: String,
-      required: false
-    }
+<script setup lang="ts">
+const props = defineProps({
+  show: {
+    type: Boolean,
+    required: true
+  },
+  contentClass: {
+    type: String,
+    required: false
   }
 })
 </script>

+ 1 - 1
components/Ui/Button/Delete.vue

@@ -39,7 +39,7 @@ import {usePageStore} from "~/store/page";
 
 const props = defineProps({
     model: {
-      type: Object as () => typeof ApiResource,
+      type: Object,
       required: true
     },
     entity: {

+ 10 - 10
components/Ui/Button/Submit.vue

@@ -1,14 +1,15 @@
 <template>
-  <v-btn class="mr-4 ot_green ot_white--text" :class="otherActions ? 'pr-0' : ''" @click="onClick(mainAction)" ref="mainBtn">
+  <v-btn class="mr-4 ot_green ot_white--text" :class="hasOtherActions ? 'pr-0' : ''" @click="submitAction(mainAction)" ref="mainBtn">
+
     {{ $t(mainAction) }}
 
-    <v-divider class="ml-3" :vertical="true" v-if="otherActions"></v-divider>
+    <v-divider class="ml-3" :vertical="true" v-if="hasOtherActions"></v-divider>
 
     <v-menu
       :top="dropDirection === 'top'"
       offset-y
       left
-      v-if="otherActions"
+      v-if="hasOtherActions"
       :nudge-top="dropDirection === 'top' ? 6 : 0"
       :nudge-bottom="dropDirection === 'bottom' ? 6 : 0"
     >
@@ -29,7 +30,7 @@
           class="subAction"
           v-if="index > 0"
         >
-          <v-list-item-title v-text="$t(action)" @click="onClick(action)" />
+          <v-list-item-title v-text="$t(action)" @click="submitAction(action)" />
         </v-list-item>
       </v-list>
     </v-menu>
@@ -37,7 +38,6 @@
 </template>
 
 <script setup lang="ts">
-import {useNuxtApp} from "#app";
 import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
 
 const props = defineProps({
@@ -52,23 +52,23 @@ const props = defineProps({
     }
 })
 
-const { emit } = useNuxtApp()
+const emit = defineEmits(['submit'])
 
-const mainBtn: Ref<any> = ref(null)
+const mainBtn: Ref = ref(null)
 const menuSize = computed(()=>{
   // Btn size + 40px de padding
   return mainBtn.value?.$el.clientWidth + 40
 })
 
-const onClick = (action: string) => {
+const submitAction = (action: string) => {
   emit('submit', action)
 }
 
-const mainAction: ComputedRef<any> = computed(()=>{
+const mainAction: ComputedRef<string> = computed(()=>{
   return props.actions[0] as string
 })
 
-const otherActions: ComputedRef<boolean> = computed(()=>{
+const hasOtherActions: ComputedRef<boolean> = computed(()=>{
   return props.actions.length > 1
 })
 

+ 1 - 1
components/Ui/Card.vue

@@ -45,7 +45,7 @@ const props = defineProps({
     required: true
   },
   model: {
-    type: Function,
+    type: Object,
     required: true
   },
   entity: {

+ 5 - 2
components/Ui/Collection.vue

@@ -2,7 +2,7 @@
 
 <template>
   <main>
-    <v-skeleton-loader v-if="fetchState.pending" :type="loaderType" />
+    <v-skeleton-loader v-if="pending" :type="loaderType" />
     <div v-else>
 
       <!-- Content -->
@@ -25,10 +25,11 @@
 import {computed, ComputedRef, toRefs, ToRefs} from "@vue/reactivity";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {Collection} from "~/types/data";
+import ApiResource from "~/models/ApiResource";
 
 const props = defineProps({
   model: {
-    type: Function,
+    type: Object,
     required: true
   },
   parent: {
@@ -54,4 +55,6 @@ const { data: collection, pending } = await fetchCollection(model.value, parent.
 
 const items: ComputedRef<Collection> = computed(() => collection.value ?? { items: [], pagination: {}, totalItems: 0 })
 
+console.log('%%%', items.value)
+
 </script>

+ 1 - 1
components/Ui/DataTable.vue

@@ -54,7 +54,7 @@ const props = defineProps({
     required: true
   },
   model: {
-    type: Function,
+    type: Object,
     required: true
   },
   headers: {

+ 50 - 0
components/Ui/ExpansionPanel.vue

@@ -0,0 +1,50 @@
+<!--
+Panneaux déroulants de type "accordéon"
+
+@see https://vuetifyjs.com/en/components/expansion-panels/
+-->
+
+<template>
+  <v-expansion-panel :id="id">
+    <v-expansion-panel-header color="ot_light_grey">
+      <v-icon class="ot_white--text ot_green icon">
+        {{ icon }}
+      </v-icon>
+      {{ $t(id) }}
+    </v-expansion-panel-header>
+    <v-expansion-panel-content>
+      <slot />
+    </v-expansion-panel-content>
+  </v-expansion-panel>
+</template>
+
+<script setup lang="ts">
+const props = defineProps({
+  id: {
+    type: String,
+    required: true
+  },
+  icon: {
+    type: String,
+    required: false,
+    default: null
+  }
+})
+</script>
+
+<style scoped>
+  .icon{
+    width: 47px;
+    height: 47px;
+    padding: 10px;
+    margin-right: 10px;
+    flex: none !important;
+  }
+  .v-expansion-panel-header{
+    padding: 0;
+    padding-right: 20px;
+  }
+  .v-expansion-panel--active > .v-expansion-panel-header{
+    min-height: 47px;
+  }
+</style>

+ 64 - 29
components/Ui/Form.vue

@@ -10,6 +10,8 @@ Formulaire générique
       ref="form"
       lazy-validation
       :readonly="readonly"
+      @submit.prevent=""
+      @change="onChange"
     >
       <!-- Top action bar -->
       <v-container fluid class="container btnActions">
@@ -75,7 +77,7 @@ Formulaire générique
 
 <script setup lang="ts">
 
-import {computed, ComputedRef, Ref} from "@vue/reactivity";
+import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
 import {AnyJson} from "~/types/data";
 import {FORM_FUNCTION, SUBMIT_TYPE, TYPE_ALERT} from "~/types/enums";
 import {useNuxtApp, useRouter} from "#app";
@@ -87,13 +89,17 @@ import {usePageStore} from "~/store/page";
 
 const props = defineProps({
   model: {
-    type: Object as () => typeof ApiModel,
+    type: Function as any as () => typeof ApiModel,
     required: true
   },
   entity: {
-    type: Object as () => Ref<ApiModel>,
+    type: Object as () => ApiModel,
     required: true
   },
+  onChanged: {
+    type: Function,
+    required: false
+  },
   submitActions: {
     type: Object,
     required: false,
@@ -106,21 +112,39 @@ const props = defineProps({
 })
 
 const { i18n } = useNuxtApp()
-
 const router = useRouter()
-
 const { em } = useEntityManager()
 
+const isValid: Ref<boolean> = ref(true)
+const errors: Ref<Array<string>> = ref([])
+
 /**
- * Réference au component v-form
+ * Référence au component v-form
  */
-const form: Ref<any> = ref(null)
+const form: Ref = ref(null)
+
+const readonly: ComputedRef<boolean> = computed(() => {
+  return useFormStore().readonly
+})
+
+/**
+ * Utilise la méthode validate() de v-form pour valider le formulaire et mettre à jour les variables isValid et errors
+ *
+ * @see https://vuetifyjs.com/en/api/v-form/#functions-validate
+ */
+const validate = async function () {
+  const validation = await form.value.validate()
+
+  isValid.value = validation.valid
+  errors.value = validation.errors
+}
 
 /**
  * Handle events if the form is dirty to prevent submission
  * @param e
  */
-const preventingEventHandler = (e: any) => {
+// TODO: voir si encore nécessaire avec le @submit.prevent
+const preventSubmit = (e: any) => {
   // Cancel the event
   e.preventDefault()
   // Chrome requires returnValue to be set
@@ -129,26 +153,43 @@ const preventingEventHandler = (e: any) => {
 
 /**
  * Définit l'état dirty (modifié) du formulaire
- * // TODO: renommer, on ne se rend pas compte au nom que cette méthode fait plus que changer l'état dirty
  */
 const setIsDirty = (dirty: boolean) => {
   useFormStore().setDirty(dirty)
+
+  // If dirty, add the preventSubmit event listener
+  // TODO: voir si encore nécessaire avec le @submit.prevent
   if (process.browser) {
     if (dirty) {
-      window.addEventListener('beforeunload', preventingEventHandler)
+      window.addEventListener('beforeunload', preventSubmit)
     } else {
-      window.removeEventListener('beforeunload', preventingEventHandler)
+      window.removeEventListener('beforeunload', preventSubmit)
+    }
+  }
+}
+
+/**
+ *  Update store when form is changed (if valid)
+ */
+const onFormChange = async () => {
+  await validate()
+
+  if (isValid.value) {
+    em.save(props.model, props.entity)
+    setIsDirty(true)
+
+    if (props.onChanged) {
+      // Execute the custom onChange method, if defined
+      // TODO: voir quelles variables passer à cette méthode custom ; voir même si cette méthode est utilisée
+      props.onChanged()
     }
   }
 }
 
-const readonly: ComputedRef<boolean> = computed(() => {
-  return useFormStore().readonly
-})
 
 // <--- TODO: revoir
 /**
- * Action Sauvegarder qui redirige vers la page d'edition si on est en mode create
+ * Action Sauvegarder qui redirige vers la page d'édition si on est en mode create
  * @param route
  * @param id
  * @param router
@@ -170,7 +211,7 @@ function saveAndGoTo(route: Route, router: any){
 }
 
 /**
- * Factory des fonction permettant d'assurer l'étape suivant à la soumission d'un formulaire
+ * Factory des fonctions permettant d'assurer l'étape suivant à la soumission d'un formulaire
  *
  * @param args
  * @param response
@@ -189,16 +230,8 @@ const nextStep = (next: string | null, response: AnyJson) => {
   nextStepFactory(props.submitActions[next], response, router)[next]()
 }
 
-// --->
+// ---> Fin du todo
 
-/**
- * Utilise la méthode validate() de v-form pour valider le formulaire
- *
- * @see https://vuetifyjs.com/en/api/v-form/#functions-validate
- */
-const validate = function () {
-  return form.value.validate()
-}
 
 /**
  * Soumet le formulaire
@@ -206,7 +239,9 @@ const validate = function () {
  * @param next
  */
 const submit = async (next: string|null = null) => {
-  if (!await validate()) {
+  await validate()
+
+  if (!isValid.value) {
     usePageStore().addAlerts(TYPE_ALERT.ALERT, ['invalid_form'])
     return
   }
@@ -214,16 +249,16 @@ const submit = async (next: string|null = null) => {
   setIsDirty(false)
 
   try {
-    const updatedEntity = await em.persist(props.model, props.entity.value)
+    const updatedEntity = await em.persist(props.model, props.entity)
 
     usePageStore().addAlerts(TYPE_ALERT.SUCCESS, ['saveSuccess'])
 
-    nextStep(next, updatedEntity)
+    // nextStep(next, updatedEntity)
 
   } catch (error: any) {
 
     if (error.response.status === 422 && error.response.data['violations']) {
-        const violations: Array<string> = []
+        const violations: Array<string> = [] // TODO: cette variable est-elle utile?
         let fields: AnyJson = {}
 
         for (const violation of error.response.data['violations']) {

+ 84 - 76
components/Ui/Image.vue

@@ -2,8 +2,8 @@
   <main>
     <div class="image-wrapper" :style="{width: width + 'px'}">
       <v-img
-        :src="imgSrcReload ? imgSrcReload : imageLoaded"
-        :lazy-src="require(`/assets/images/byDefault/${imageByDefault}`)"
+        :src="imgSrcReload ? imgSrcReload : image"
+        :lazy-src="defaultImagePath"
         :height="height"
         :width="width"
         aspect-ratio="1"
@@ -13,10 +13,12 @@
             class="fill-height ma-0"
             align="center"
             justify="center"
+            v-if="pending"
           >
             <v-progress-circular
               :indeterminate="true"
-              color="grey lighten-1">
+              color="grey lighten-1"
+            >
             </v-progress-circular>
           </v-row>
         </template>
@@ -31,7 +33,7 @@
           :field="field"
           :ownerId="ownerId"
           @update="$emit('update', $event, field); openUpload=false"
-          @reload="fetch();openUpload=false"
+          @reload="onReload"
           @reset="reset"
         ></UiInputImage>
       </div>
@@ -40,81 +42,87 @@
 </template>
 
 
-<script lang="ts">
-import {defineComponent, onUnmounted, ref, Ref, useContext, watch} from '@nuxtjs/composition-api'
-import {useImageProvider} from "~/composables/data/useImageProvider";
-import {WatchStopHandle} from "@vue/composition-api";
-
-export default defineComponent({
-  props: {
-    id: {
-      type: Number,
-      required: false
-    },
-    field: {
-      type: String,
-      required: false
-    },
-    imageByDefault: {
-      type: String,
-      required: false,
-      default: 'default_pic.jpeg'
-    },
-    height: {
-      type: Number,
-      required: false
-    },
-    width: {
-      type: Number,
-      required: false
-    },
-    upload: {
-      type: Boolean,
-      required: false,
-      default: false
-    },
-    ownerId:{
-      type: Number,
-      required: false
-    }
-  },
-  fetchOnServer: false,
-  setup(props) {
-    const openUpload: Ref<Boolean> = ref(false)
-    const imgSrcReload: Ref<any> = ref(null)
-    const {$dataProvider, $config} = useContext()
-    const {getOne, provideImg} = useImageProvider($dataProvider, $config)
-
-    const { imageLoaded, fetch } = getOne(props.id, props.imageByDefault, props.height, props.width)
-    const unwatch: WatchStopHandle = watch(() => props.id, async (newValue, oldValue) => {
-      imgSrcReload.value = await provideImg(newValue as number, props.height, props.width)
-    })
-
-    /**
-     * Quand on souhaite faire un reset de l'image
-     */
-    const reset = () => {
-      imgSrcReload.value = null
-      imageLoaded.value = require(`assets/images/byDefault/${props.imageByDefault}`)
-      openUpload.value = false
-    }
+<script setup lang="ts">
+import {ref, Ref} from "@vue/reactivity";
+import {useContext} from "unctx";
+import {useNuxtApp} from "#app";
+import {useImageFetch} from "~/composables/data/useImageFetch";
+import {onUnmounted, watch, WatchStopHandle} from "@vue/runtime-core";
 
-    /**
-     * Lorsqu'on démonte le component on supprime le watcher
-     */
-    onUnmounted(() => {
-      unwatch()
-    })
-
-    return {
-      imgSrcReload,
-      imageLoaded,
-      openUpload,
-      fetch,
-      reset
-    }
+const props = defineProps({
+  id: {
+    type: Number,
+    required: false
+  },
+  field: {
+    type: String,
+    required: false
+  },
+  defaultImage: {
+    type: String,
+    required: false,
+    default: 'picture.jpeg'
+  },
+  height: {
+    type: Number,
+    required: false
+  },
+  width: {
+    type: Number,
+    required: false
+  },
+  upload: {
+    type: Boolean,
+    required: false,
+    default: false
+  },
+  ownerId:{
+    type: Number,
+    required: false
   }
 })
+
+// fetchOnServer: false,  // TODO: je ne sais pas encore par quoi remplacer ça...
+
+if (!props.id) {
+  throw new Error("Image's id is missing")
+}
+
+const defaultImagePath = `/images/default/${props.defaultImage}`
+
+const openUpload: Ref<Boolean> = ref(false)
+const imgSrcReload: Ref<any> = ref(null)
+const { $config } = useNuxtApp()
+
+const { fetch } = useImageFetch()
+
+
+const { data: image, pending } = fetch(props.id, props.defaultImage, props.height, props.width)
+
+const unwatch: WatchStopHandle = watch(() => props.id, async (newValue, oldValue) => {
+  imgSrcReload.value = await fetch(newValue as number, props.defaultImage, props.height, props.width)
+})
+
+const onReload = () => {
+  fetch()
+  openUpload.value = false
+}
+
+/**
+ * Quand on souhaite faire un reset de l'image
+ */
+const reset = () => {
+  imgSrcReload.value = null
+  image.value = require(defaultImagePath)
+  openUpload.value = false
+}
+
+/**
+ * Lorsqu'on démonte le component on supprime le watcher
+ */
+onUnmounted(() => {
+  unwatch()
+})
 </script>
 
 <style lang="scss">

+ 51 - 24
components/Ui/Input/Enum.vue

@@ -6,24 +6,23 @@ Liste déroulante dédiée à l'affichage d'objets Enum
 
 <template>
   <main>
-    <v-skeleton-loader
-      v-if="$fetchState.pending"
-      type="list-item"
-      loading
-    />
+<!--    <v-skeleton-loader-->
+<!--      v-if="pending"-->
+<!--      type="list-item"-->
+<!--      loading-->
+<!--    />-->
 
     <v-select
-      v-else
-      :label="$t(fieldLabel)"
-      :value="data"
+      :value="modelValue"
+      :label="$t(label ?? field)"
       :items="items"
       item-text="label"
       item-value="value"
       :rules="rules"
       :disabled="readonly"
-      :error="error || !!violation"
-      :error-messages="errorMessage || violation ? $t(violation) : ''"
-      @change="onChange($event)"
+      :error="error || !!fieldViolations"
+      :error-messages="errorMessage || (fieldViolations ? $t(fieldViolations) : '')"
+      @change="updateViolationState($event); $emit('update:modelValue', $event.target.value)"
     />
   </main>
 </template>
@@ -36,38 +35,66 @@ import {useEnumFetch} from "~/composables/data/useEnumFetch";
 import {Enum} from "~/types/data";
 
 const props = defineProps({
-  enumType: {
-    type: String,
-    required: true
-  },
-  label: {
-    type: String,
+  /**
+   * v-model
+   */
+  modelValue: {
+    String,
     required: false,
     default: null
   },
+  /**
+   * Nom de l'Enum utilisée pour peupler la liste
+   */
+  enum: {
+    type: String,
+    required: true
+  },
+  /**
+   * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
+   * - Utilisé par la validation
+   * - Laisser null si le champ ne s'applique pas à une entité
+   */
   field: {
     type: String,
     required: false,
     default: null
   },
-  data: {
+  /**
+   * Label du champ
+   * Si non défini, c'est le nom de propriété qui est utilisé
+   */
+  label: {
     type: String,
     required: false,
     default: null
   },
+  /**
+   * Définit si le champ est en lecture seule
+   */
   readonly: {
     type: Boolean,
     required: false
   },
+  /**
+   * Règles de validation
+   * @see https://vuetify.cn/en/components/forms/#validation-with-submit-clear
+   */
   rules: {
     type: Array,
     required: false,
     default: () => []
   },
+  /**
+   * Le champ est-il actuellement en état d'erreur
+   */
   error: {
     type: Boolean,
     required: false
   },
+  /**
+   * Si le champ est en état d'erreur, quel est le message d'erreur ?
+   */
   errorMessage: {
     type: String,
     required: false,
@@ -75,18 +102,18 @@ const props = defineProps({
   }
 })
 
-const { emit } = useNuxtApp()
-
-const fieldLabel = props.label ?? props.field
+if (typeof props.enum === 'undefined') {
+  throw new Error("missing 'enum' property for input")
+}
 
-const { enumType } = props
+const { emit } = useNuxtApp()
 
-const { violation, onChange } = useFieldViolation(props.field, emit)
+const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 
 const items: Ref<Enum> = ref([])
 
 const { fetch } = useEnumFetch()
-const { data: fetched, pending } = fetch(enumType)
+const { data: fetched, pending } = fetch(props.enum)
 
 items.value = fetched.value || []
 

+ 57 - 26
components/Ui/Input/Text.vue

@@ -6,66 +6,103 @@ Champs de saisie de texte
 
 <template>
   <v-text-field
+    :value="modelValue"
     autocomplete="off"
-    :value="data"
-    :label="$t(label_field)"
+    :label="$t(label ?? field)"
     :rules="rules"
     :disabled="readonly"
-    :type="type === 'password' ? (show ? 'text' : type) : type"
-    :error="error || !!violation"
-    :error-messages="errorMessage || (violation ? $t(violation) : '')"
-    @change="onChange($event); $emit('change', $event)"
-    @input="$emit('input', $event, field)"
-    v-mask="mask"
+    :type="(type === 'password' && show) ? 'text' : type"
+    :error="error || !!fieldViolations"
+    :error-messages="errorMessage || (fieldViolations ? $t(fieldViolations) : '')"
+    v-cleave="mask"
     :append-icon="type === 'password' ? (show ? 'mdi-eye' : 'mdi-eye-off') : ''"
     @click:append="show = !show"
+    @input="$emit('update:modelValue', $event.target.value)"
+    @change="updateViolationState($event); $emit('change', $event)"
   />
 </template>
 
 <script setup lang="ts">
-import {mask} from 'vue-the-mask';
 import {ref} from "@vue/reactivity";
 import {useFieldViolation} from "~/composables/form/useFieldViolation";
+import {useNuxtApp} from "#app";
+import {useI18n} from "vue-i18n";
+import {mask} from "vue-the-mask";
 
 const props = defineProps({
-  label: {
-    type: String,
+  /**
+   * v-model
+   */
+  modelValue: {
+    String,
     required: false,
     default: null
   },
+  /**
+   * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
+   * - Utilisé par la validation
+   * - Laisser null si le champ ne s'applique pas à une entité
+   */
   field: {
     type: String,
     required: false,
     default: null
   },
-  type: {
+  /**
+   * Label du champ
+   * Si non défini, c'est le nom de propriété qui est utilisé
+   */
+  label: {
     type: String,
     required: false,
     default: null
   },
-  data: {
-    type: [String, Number],
+  /**
+   * Type d'input HTML
+   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/button
+   */
+  type: {
+    type: String,
     required: false,
     default: null
   },
+  /**
+   * Définit si le champ est en lecture seule
+   */
   readonly: {
     type: Boolean,
-    required: false
+    required: false,
+    default: false
   },
+  /**
+   * Règles de validation
+   * @see https://vuetify.cn/en/components/forms/#validation-with-submit-clear
+   */
   rules: {
     type: Array,
     required: false,
     default: () => []
   },
+  /**
+   * Le champ est-il actuellement en état d'erreur
+   */
   error: {
     type: Boolean,
-    required: false
+    required: false,
+    default: false
   },
+  /**
+   * Si le champ est en état d'erreur, quel est le message d'erreur?
+   */
   errorMessage: {
     type: String,
     required: false,
     default: null
   },
+  /**
+   * Masque de saisie
+   * @see https://github.com/nosir/cleave.js/
+   */
   mask: {
     type: [Array, Boolean],
     required: false,
@@ -73,20 +110,14 @@ const props = defineProps({
   }
 })
 
-const { app, emit } = useNuxtApp()
+const { app } = useNuxtApp()
+const i18n = useI18n()
 
-const { violation, onChange } = useFieldViolation(props.field, emit)
+const { fieldViolations, updateViolationState } = useFieldViolation(props.field)
 
 const show = ref(false)
 
-const label_field = props.label ?? props.field
-
-app.directive('mask', {
-    mask: (el: any, binding: any, vnode: any, oldVnode: any) => {
-      if (!binding.value) return;
-      mask(el, binding, vnode, oldVnode);
-    }
-})
+defineEmits(['update:modelValue'])
 </script>
 
 <style scoped>

+ 61 - 0
components/Ui/ItemFromUri.vue

@@ -0,0 +1,61 @@
+<!--
+Espace permettant de récupérer un item via une uri et de gérer son affichage via un slot
+-->
+<template>
+  <main>
+    <v-skeleton-loader
+      v-if="pending"
+      :type="loaderType"
+    />
+    <div v-else>
+      <slot name="item.text" v-bind="{item}" />
+    </div>
+    <slot />
+  </main>
+</template>
+
+<script setup lang="ts">
+// TODO: renommer en EntityFromUri? voir si ce component est encore nécessaire, ou si ça ne peut pas être une méthode le l'entity manager
+
+import {Query} from "pinia-orm";
+import Url from "~/services/utils/url";
+import {useEntityFetch} from "~/composables/data/useEntityFetch";
+import {object} from "@ucast/core";
+import {computed, ComputedRef} from "@vue/reactivity";
+import {schema} from "@pinia-orm/normalizr";
+import ApiResource from "~/models/ApiResource";
+
+const props = defineProps({
+  uri: {
+    type: String,
+    required: false,
+    default: null
+  },
+  model: {
+    type: Object,
+    required: true
+  },
+  query: {
+    type: Object as () => Query,
+    required: true
+  },
+  loaderType: {
+    type: String,
+    required: false,
+    default: 'text'
+  }
+})
+
+const id = Url.extractIdFromUri(props.uri)
+if (id === null) {
+  throw new Error('Uri parsing error : no id found')
+}
+
+const { fetch } = useEntityFetch()
+
+const { data, pending } = fetch(props.model, id)
+
+const item: ComputedRef<ApiResource|null> = computed(() => {
+  return data.value
+})
+</script>

+ 6 - 4
composables/data/useAp2iRequestService.ts

@@ -1,11 +1,12 @@
 import {useProfileAccessStore} from "~/store/profile/access";
 import {FetchContext, FetchOptions} from "ohmyfetch";
 import {TYPE_ALERT} from "~/types/enums";
-import {useRuntimeConfig} from "#app";
+import {navigateTo, useRuntimeConfig} from "#app";
 import ApiRequestService from "~/services/data/apiRequestService";
 import {AssociativeArray} from "~/types/data";
 import {Ref} from "@vue/reactivity";
 import {usePageStore} from "~/store/page";
+import UnauthorizedError from "~/services/error/UnauthorizedError";
 
 /**
  * Retourne une instance de ApiRequestService configurée pour interroger l'api Ap2i
@@ -44,6 +45,8 @@ export const useAp2iRequestService = () => {
 
         options.headers = { ...options.headers, ...headers }
 
+        // $axios.setToken(`${store.state.profile.access.bearer}`, 'Bearer')
+
         pending.value = true
         console.log('Request : ' + request + ' (SSR: ' + process.server + ')')
     }
@@ -74,8 +77,7 @@ export const useAp2iRequestService = () => {
         pending.value = false
 
         if (response && response.status === 401) {
-            // navigateTo('/login')
-            console.error('Unauthorized')
+            throw new UnauthorizedError('Ap2i - Unauthorized')
         }
         else if (response && response.status === 403) {
             usePageStore().addAlerts(TYPE_ALERT.ALERT, ['forbidden'])
@@ -96,7 +98,7 @@ export const useAp2iRequestService = () => {
         onResponseError
     }
 
-    // Utilise la fonction `create` de ohmyfetch pour générer un fetcher dédié à l'interrogation de Ap2i
+    // Utilise la fonction `create` d'ohmyfetch pour générer un fetcher dédié à l'interrogation de Ap2i
     const fetcher = $fetch.create(config)
 
     return { apiRequestService: new ApiRequestService(fetcher), pending: pending }

+ 1 - 1
composables/data/useEntityFetch.ts

@@ -13,7 +13,7 @@ export const useEntityFetch = (lazy: boolean = false): useEntityFetchReturnType
 
     const fetch = (model: typeof ApiResource, id: number) => useAsyncData(
         model.entity + '_' + id, // TODO: je me demande si on a besoin de cette clé? (https://v3.nuxtjs.org/api/composables/use-async-data#params)
-        () => em.fetch(model, id),
+        () => em.fetch(model, id, true),
         { lazy }
     )
 

+ 1 - 1
composables/data/useImageFetch.ts

@@ -20,7 +20,7 @@ export const useImageFetch = (lazy: boolean = false): useImageFetchReturnType =>
     ): FetchResult<any> => useFetch(
         // @ts-ignore
         async () => {
-            const image: Ref<String> = ref(defaultImage ? require(`assets/images/byDefault/${defaultImage}`) : '')
+            const image: Ref<String> = ref(defaultImage ? `assets/images/byDefault/${defaultImage}` : '')
 
             if (id !== null) {
                 try {

+ 14 - 13
composables/form/useFieldViolation.ts

@@ -1,33 +1,34 @@
-import { AnyStore } from '~/types/interfaces'
-import {ComputedRef} from "@vue/reactivity";
+import {computed, ComputedRef} from "@vue/reactivity";
 import {useFormStore} from "~/store/form";
+import {useNuxtApp} from "#app";
 
 /**
  * Composable pour gérer l'apparition de message d'erreurs de validation d'un champ de formulaire
  *
  * @param field
- * @param emit
  */
-export function useFieldViolation(field: string, emit: any){
-  const violation: ComputedRef<string> = computed(()=>{
+export function useFieldViolation(field: string) {
+  const { emit } = useNuxtApp()
+
+  const fieldViolations: ComputedRef<string> = computed(()=> {
     return useGet(useFormStore().violations, field, '')
   })
 
   /**
    * Lorsque la valeur d'un champ change, on supprime le fait qu'il puisse être "faux" dans le store, et on émet sa nouvelle valeur au parent
-   * @param emit
-   * @param fieldValue
-   * @param changeField
+   * @param field
+   * @param value
    */
-  function onChange (emit:any, fieldValue:any, changeField:string) {
+  function updateViolationState(field: string, value: any) {
     //@ts-ignore
-    useFormStore().setViolations(useOmit(useFormStore().violations, changeField))
+    useFormStore().setViolations(useOmit(useFormStore().violations, field))
 
-    emit('update', fieldValue, changeField)
+    console.log('X', useFormStore().violations)
+    emit('update', value, field)
   }
 
   return {
-    onChange: (fieldValue:any) => onChange(emit, fieldValue, field),
-    violation
+    fieldViolations,
+    updateViolationState: (fieldValue: any) => updateViolationState(fieldValue, field)
   }
 }

+ 12 - 0
composables/layout/useRedirectToLogin.ts

@@ -0,0 +1,12 @@
+import {navigateTo, useRuntimeConfig} from "#app";
+
+export const useRedirectToLogin = () => {
+    const runtimeConfig = useRuntimeConfig()
+
+    return () => {
+        if (!runtimeConfig.baseUrlAdminLegacy) {
+            throw new Error('Configuration error : no redirection target')
+        }
+        navigateTo(runtimeConfig.baseUrlAdminLegacy + '/login', {external: true})
+    }
+}

+ 4 - 0
doc/Débugger en production.md

@@ -0,0 +1,4 @@
+
+
+1. Tester l'api v1 / v2 avec postman
+2. Vérifier les lots avec pm2 : `pm2 logs app` 

+ 0 - 9
lang/breadcrumbs/fr-FR.js

@@ -1,9 +0,0 @@
-export default (context, locale) => {
-  return ({
-    item: 'Détails',
-    organization_breadcrumbs: 'Fiche de la structure',
-    subscription_breadcrumbs: 'Mon abonnement',
-    address_breadcrumbs: 'Adresse postale',
-    contact_points_breadcrumbs: 'Points de contact'
-  })
-}

+ 0 - 19
lang/content/parameters/fr-FR.js

@@ -1,19 +0,0 @@
-export default (context, locale) => {
-  return ({
-    'help_super_admin': 'Le compte super-admin possède tous les droits de gestion sur votre logiciel. On l’utilise surtout pour la gestion de votre site internet et, à la première connexion au logiciel, afin de créer des comptes pour tous membres de votre structure. Enfin, il peut également être utile en cas de dépannage dans certaines situations particulières.',
-    yourWebsiteAddressIs: 'L\'adresse de votre site web est',
-    areYourSureYouWantToDisableYourOpentalentWebsite: 'Êtes-vous sûr(e) de vouloir désactiver votre site web Opentalent',
-    youRegisteredTheFollowingSubdomain: 'Vous avez enregistré le sous-domaine suivant',
-    subdomainIsCurrentlyActive: 'Le sous-domaine est actuellement actif',
-    doYouWantToActivateThisSubdomain: 'Voulez-vous activer ce sous-domaine',
-    activate: 'Activer',
-    active: 'Actif',
-    pleaseEnterYourNewSubdomain: 'Veuillez saisir votre nouveau sous-domaine',
-    subdomain_need_to_have_0_to_60_cars: 'Le sous-domaine doit comporter de 2 à 60 caractères',
-    this_subdomain_is_already_in_use: 'Ce sous-domaine est déjà utilisé',
-    this_subdomain_is_available: 'Ce sous-domaine est disponible',
-    subdomain_can_not_contain_spaces_or_special_cars: 'Le sous-domaine ne doit pas contenir d\'espaces ni de caractères spéciaux',
-    please_enter_a_value_for_the_subdomain: 'Veuillez saisir une valeur pour le sous-domaine',
-    validation_ongoing: 'Validation en cours',
-  })
-}

+ 0 - 44
lang/content/subscription/fr-FR.js

@@ -1,44 +0,0 @@
-/**
- * Specific translations for the /subscription page
- *
- * @param context
- * @param locale
- * @returns {{get_more_functionalities_with_version: string, only_for_cmf_members: string, contact_us_at: string, contact_us_for_show_and_demo: string, starting_from_x_eur_ttc_per_month: string, download_order_form: string, for_x_sms: string, for_only_x_eur_ttc_by_month: string, example: string, domain_name: string, and_benefit: string, public_price_x_ttc_a_year: string, product_sheet: string, get_your_own_domain_and_up_to_five_emails_for_only_x_eur_ttc_per_month: string, dummy_domain_name: string, website: string, version_x_up_to_x_students: string, download_cmf_order_form: string, send_sms_from_app_to_your_members: string, freely_try_our_software: string, starting_from_x_eur_ttc_per_ssm: string, yearly_paid_giving_x_eur_ttc_per_year: string, excluding_license_and_training_fees: string, a_suitable_solution_for_your_artistic_school: string, dummy_email_address: string, associated_mail_address: string, switch_to_version: string, or_by_mail_at: string, of_accounts_for_teachers_and_students: string, of_a_complete_website: string}}
- */
-export default (context, locale) => {
-  return ({
-    until: 'Jusqu\'au',
-    get_more_functionalities_with_version: 'Bénéficiez de plus de fonctionnalités avec la version',
-    for_only_x_eur_ttc_by_month: 'Pour seulement {price} TTC par mois',
-    convert_price_to_sms: 'soit {nb_sms} SMS',
-    yearly_paid_giving_x_eur_ttc_per_year: 'Payable annuellement, soit {price} TTC / an',
-    only_for_cmf_members: 'Offre réservée aux adhérents CMF',
-    public_price_x_ttc_a_year: 'Prix public: {price} TTC/an',
-    product_sheet: 'Fiche produit',
-    download_order_form: 'Télécharger le bon de commande',
-    download_cmf_order_form: 'Télécharger le bon de commande CMF',
-    a_suitable_solution_for_your_artistic_school: 'Une solution économique adaptée à votre établissement d\'enseignement artistique',
-    starting_from_x_eur_ttc_per_month: 'A partir de {price} TTC par mois',
-    version_x_up_to_x_students: 'Version {product} jusqu\'à {max_students} étudiants',
-    excluding_license_and_training_fees: 'Hors frais de licence d\'utilisation et de formation',
-    freely_try_our_software: 'Essayez notre logiciel en toute liberté',
-    contact_us_for_show_and_demo: 'Contactez-nous sans plus tarder pour obtenir une présentation ainsi qu\'un accès de démonstration',
-    contact_us_at: 'Contactez-nous au',
-    or_by_mail_at: 'ou par mail à l\'adresse',
-    switch_to_version: 'Passez à la version',
-    and_benefit: 'et bénéficiez',
-    of_accounts_for_teachers_and_students: 'de comptes pour vos professeurs et élèves',
-    of_a_complete_website: 'd\'un site internet complet',
-    send_sms: 'Envoyez des SMS',
-    to_your_members_from_app: 'à vos membres / élèves depuis votre logiciel',
-    starting_from_x_eur_ttc_per_sms: 'A partir de {price} TTC / sms',
-    for_x_sms: 'pour {amount} SMS',
-    get_your_own_domain_and_up_to_five_emails_for_only_x_eur_ttc_per_month: 'Bénéficiez de votre propre nom de domaine et 5 adresses email pour seulement {price} TTC / mois',
-    example: 'Exemple',
-    domain_name: 'Nom de domaine',
-    dummy_domain_name: 'ma-structure.fr',
-    associated_mail_address: 'Adresse email associée',
-    dummy_email_address: 'contact@ma-structure.fr',
-    sms: 'SMS'
-  })
-}

+ 0 - 120
lang/enum/fr-FR.js

@@ -1,120 +0,0 @@
-export default (context, locale) => {
-  return ({
-    GUARDIANS: 'Tuteurs uniquement',
-    STUDENTS: 'Élèves uniquement',
-    STUDENTS_AND_THEIR_GUARDIANS: 'Élèves et leurs tuteurs',
-    ANNUAL: 'Annuel',
-    HALF: 'Semestriel',
-    QUARTERLY: 'Trimestriel',
-    MONTHLY: 'Mensuel',
-    BY_EDUCATION: 'Par enseignement',
-    BY_TEACHER: 'Par professeur',
-    CATEGORY_ORCHESTRE: 'Orchestre',
-    CATEGORY_AMBULATORY: 'Musique ambulatoire',
-    CATEGORY_OTHER: 'Autres activités',
-    CATEGORY_CHORUS: 'Chorale / Groupe vocal',
-    CATEGORY_BAND: 'Ensemble',
-    BRASS_BAND: 'Brass band',
-    HUNTING_HORNS: 'Trompes de chasse',
-    PHILHARMONIC_ORCHESTRA: 'Orchestre philharmonique',
-    ACCORDION_ORCHESTRA: 'Orchestre d\'accordéons',
-    HARMONY_ORCHESTRA: 'Orchestre d\'harmonie',
-    ORCHESTRA_CLASS: 'Classe d\'orchestre',
-    SYMPHONY_ORCHESTRA: 'Orchestre symphonique',
-    STRING_ORCHESTRA: 'Orchestre à cordes',
-    PLUCKED_ORCHESTRA: 'Orchestre à plectres',
-    FANFARE_BAND: 'Orchestre de fanfare',
-    BAGAD: 'Bagad',
-    BANDAS: 'Bandas ou Fanfare de rue',
-    BATTERY_FANFARE: 'Batterie fanfare',
-    BATTUCADA: 'Battucada',
-    FOLKLORIC_BAND: 'Ensemble folklorique',
-    FIFE_AND_DRUM: 'Fifres et tambours',
-    MARCHING_BAND: 'Marching band ou Show parade',
-    CHILDRENS_CHOIR: 'Choeur d\'enfants',
-    FEMAL_CHOIR: 'Choeur de femmes',
-    MENS_CHOIR: 'Choeur d\'hommes',
-    MIXED_CHORUS: 'Choeur mixte',
-    VOCAL_BAND_UP_16: 'Ensemble vocal (jusqu\'à 16)',
-    CLARINET_CHOIR: 'Ensemble de clarinettes',
-    COPPER_BAND: 'Ensemble de cuivres',
-    FLUTE_ENSEMBLE: 'Ensemble de flûtes',
-    SAXOPHONES_BAND: 'Ensemble de saxophones',
-    VIOLIN_BAND: 'Ensemble de violons',
-    PERCUSSION_BAND: 'Ensemble de percussions',
-    CURRENT_MUSIC_GROUP: 'Groupe de Musique actuelle',
-    CHAMBER_MUSIC_ENSEMBLE: 'Ensemble de Musique de chambre',
-    TRADITIONAL_MUSIC_ENSEMBLE: 'Ensemble de Musique traditionnelle',
-    JAZZ_BAND: 'Ensemble de Jazz',
-    EDUCATION: 'Enseignement',
-    CHEERLEADER: 'Majorettes',
-    TROOP: 'Troupe',
-    BIG_BAND: 'Big band',
-    PRODUCT_ARTIST: 'Opentalent Artist',
-    PRODUCT_ARTIST_PREMIUM: 'Opentalent Artist Premium',
-    PRODUCT_SCHOOL: 'Opentalent School',
-    PRODUCT_SCHOOL_PREMIUM: 'Opentalent School Premium',
-    PRODUCT_MANAGER: 'Opentalent Manager',
-    LOCAL_AUTHORITY: 'Collectivité territoriale (Mairie, SIVOM, SIVU, EPIC, …)',
-    ASSOCIATION_LAW_1901: 'Association loi 1901 ou assimilée (Droit local, ...)',
-    COMMERCIAL_SOCIETY: 'Entreprise commerciale (SARL, SAS, EURL, Autoentrepreneur, …)',
-    ARTISTIC_EDUCATION_ONLY: 'Enseignement artistique seul',
-    ARTISTIC_PRACTICE_EDUCATION: 'Pratique et enseignement artistique',
-    ARTISTIC_PRACTICE_ONLY: 'Pratique artistique seule',
-    DELEGATION: 'Délégation',
-    DEPARTEMENTAL_FEDERATION: 'Fédération départementale',
-    GROUPMENT: 'Groupement',
-    LOCAL_FEDERATION: 'Fédération locale',
-    MUSIC_OPENTALENT: 'Opentalent',
-    NATIONAL_FEDERATION: 'Fédération nationale',
-    REGIONAL_FEDERATION: 'Fédération régionale',
-    CESMD: 'CESMD Centre d\'études supérieures de musique et de danse',
-    CNSMD: 'CNSMD Conservatoire national supérieur de musique',
-    CRC: 'CRC Conservatoire à rayonnement communal',
-    CRD: 'CRD Conservatoire à rayonnement départemental',
-    CRI: 'CRI Conservatoire à rayonnement intercommunal',
-    CRR: 'CRR Conservatoire à rayonnement régional',
-    EENC: 'EENC Établissement d\'enseignement artistique non classé',
-    EMP: 'EMP Ecole de musique privée',
-    MULTIPLE: 'Multiple',
-    UNIQUE: 'Unique',
-    MAIN_BUILDING: 'Etablissement principal',
-    SECONDARY_SCHOOL: 'Etablissement secondaire',
-    ACTALIANS: 'Actalians',
-    AFDAS: 'Afdas',
-    AGEFOS: 'Agefos',
-    AGEFOS_PME: 'Agefos pme',
-    ANFA: 'Anfa',
-    ANFH: 'Anfh',
-    APCMA: 'Apcma',
-    CNFPT: 'Cnfpt',
-    CONSTRUCTYS: 'Constructys',
-    FAF_TT: 'Faf-tt',
-    FAFIEC: 'Fafiec',
-    FAFIH: 'Fafih',
-    FAFSEA: 'Fafsea',
-    FIF_PL: 'Fif pl',
-    FONGECIF: 'Fongecif',
-    FORCO: 'Forco',
-    INTERGROS: 'Intergros',
-    OPCA3_PLUS: 'Opca3+',
-    OPCAIM: 'Opcaim',
-    OPCALIA: 'Opcalia',
-    OPCALIM: 'Opcalim',
-    OPCA_BAIA: 'Opca baia',
-    OPCA_DEFI: 'Opca defi',
-    OPCA_TRANSPORTS: 'Opca transports',
-    UNIFAF: 'Unifaf',
-    UNIFORMATION: 'Uniformation',
-    VIVEA: 'Vivea',
-    ADDRESS_PRACTICE: 'Adresse de pratique',
-    ADDRESS_HEAD_OFFICE: 'Adresse du siège social',
-    ADDRESS_CONTACT: 'Adresse de contact',
-    ADDRESS_BILL: 'Adresse de facturation',
-    ADDRESS_OTHER: 'Autre adresse',
-    PRINCIPAL: 'Contact principal',
-    BILL: 'Contact de facturation',
-    OTHER: 'Autre',
-    CONTACT: 'Contact'
-  })
-}

+ 0 - 135
lang/field/fr-FR.js

@@ -1,135 +0,0 @@
-export default (context, locale) => {
-  return ({
-    parameters: 'Paramètres',
-    cycle: 'Cycle',
-    timing: 'Durée d\'un enseignement (en minutes)',
-    educationTiming: 'Durée d\'un enseignement (en minutes)',
-    superAdmin: 'Compte super-admin',
-    username: 'Login de connexion',
-    residenceArea: 'Zones de résidence',
-    desactivateOpentalentSiteWeb: 'Désactiver le site opentalent',
-    reactivateOpentalentSiteWeb: 'Réactiver le site Opentalent',
-    passwordSMS: 'Mot de passe SMS',
-    usernameSMS: 'Nom d\'utilisateur SMS',
-    smsSenderName: 'Personnaliser le nom de l\'expéditeur SMS',
-    attendance: 'Absences',
-    sendAttendanceEmail: 'Prévenir automatiquement la famille par mail en cas d\'absence non justifiée',
-    sendAttendanceSms: 'Prévenir automatiquement la famille par sms en cas d\'absence non justifiée',
-    bulletinReceiver: 'Adresser le bulletin à',
-    bulletinEditWithoutEvaluation: 'Editer également les bulletins ne contenant aucune évaluation',
-    bulletinShowAverages: 'Afficher les moyennes',
-    bulletinShowAbsences: 'Afficher les absences',
-    bulletinViewTestResults: 'Afficher les résultats des examens',
-    bulletinShowEducationWithoutEvaluation: 'Afficher les enseignements ne contenant aucune évaluation',
-    bulletinDisplayLevelAcquired: 'Affichage niveau acquis',
-    bulletinSignatureDirector: 'Un cadre « Tampon / Signature » pour l\'administration',
-    bulletinPrintAddress: 'L\'adresse postale de l\'élève ou son tuteur',
-    bulletinWithTeacher: 'Le nom du professeur',
-    bulletin_parameters: 'Bulletins',
-    sms: 'Sms',
-    web_parameters: 'Site internet',
-    averageMax: 'Note maximale pour les notes du suivi pédagogique (entre 1 et 100)',
-    educational_follow_up: 'Suivi pédagogique',
-    advancedEducationNotationType: 'Type de grilles d\'évaluation',
-    educationPeriodicity: 'Périodicité des évaluations',
-    editCriteriaNotationByAdminOnly: 'Autoriser uniquement l\'administration à modifier les critères d\'évaluation',
-    trackingValidation: 'Contrôle et validation du suivi pédagogique par l\'administration',
-    publicationDirectors: 'Directeur(s) de publication',
-    otherWebsite: 'Autre site web',
-    newSubDomain: 'Nouveau sous domaine',
-    yourSubdomains: 'Vos sous-domaines',
-    timezone: 'Fuseau horaire',
-    qrCode: 'QrCode pour la licence',
-    studentsAreAdherents: 'Les élèves sont également adhérents de l\'association',
-    showAdherentList: 'Afficher la liste des adhérents et leurs coordonnées',
-    endCourseDate: 'Date de fin des cours ',
-    startCourseDate: 'Date de début des cours ',
-    generalParams: 'Paramètres généraux',
-    financialDate: 'Début de la saison financière',
-    musicalDate: 'Début de la saison d\'activité',
-    title: 'Titre',
-    link: 'Lien',
-    organizationArticle: 'Coups de projecteur',
-    youtube: 'Lien YouTube',
-    first_subscription: 'Date de première adhésion',
-    bicInvalid: 'Code BIC invalide',
-    ibanInvalid: 'IBAN invalide',
-    invalid_bic: 'Votre BIC est invalide',
-    invalid_iban: 'Votre IBAN est invalide',
-    importAddress: 'Importer l\'adresse d\'une des personnes de votre structure',
-    addressCountry: 'Pays',
-    legalInformation: 'Informations légales',
-    agrements: 'Agréments',
-    salary: 'Salariés',
-    network: 'Informations réseau',
-    communication: 'Communication',
-    legalStatus: 'Statut juridique',
-    siretNumber: 'N° SIRET',
-    apeNumber: 'Code APE',
-    waldecNumber: 'RNA (ancien Waldec)',
-    identifierCmf: 'Matricule CMF',
-    identifierFfec: 'Matricule FFEC',
-    ffecApproval: 'N° agrément FFEC',
-    description: 'Description',
-    typeOfPractices: 'Type de pratiques',
-    otherPractice: 'Autres ensembles (préciser)',
-    principalType: 'Type principal',
-    contact_point: 'Point de contact',
-    name: 'Nom',
-    acronym: 'Sigle',
-    creationDate: 'Date de création',
-    prefectureName: 'Préfecture ou sous-préfecture',
-    prefectureNumber: 'Numéro de déclaration',
-    declarationDate: 'Date de déclaration',
-    tvaNumber: 'TVA Intracommunautaire',
-    schoolCategory: "Catégorie d'école",
-    typeEstablishment: "Type d'établissement",
-    typeEstablishmentDetail: 'Détails du type',
-    youngApproval: 'Jeunesse-éducation populaire',
-    trainingApproval: 'Organisme de formation',
-    otherApproval: 'Si autre, lesquels',
-    collectiveAgreement: 'Nom de la convention collective',
-    opca: "Nom de l'OPCA",
-    icomNumber: 'N° ICOM',
-    urssafNumber: 'N° URSSAF',
-    email: 'E-mail',
-    emailInvalid: 'E-mail invalide',
-    telphone: 'Téléphone',
-    telphoneInvalid: 'Téléphone invalide',
-    faxNumber: 'Fax',
-    faxNumberInvalid: 'Fax invalide',
-    mobilPhone: 'Portable',
-    mobilPhoneInvalid: 'Portable invalide',
-    actions: 'Actions',
-    twitter: 'Lien Twitter',
-    facebook: 'Lien Facebook',
-    instagram: 'Lien Instagram',
-    image: 'Image',
-    portailVisibility: "Répertorier la structure dans l'annuaire du portail Opentalent",
-    pedagogicBudget: 'Budget pédagogique',
-    budget: 'Montant du dernier budget réalisé',
-    isPedagogicIsPrincipalActivity: "L'activité principale de la stucture est « la pédagogie des arts du cirque »",
-    bank_account: 'IBAN',
-    bankName: 'Nom de la banque',
-    bic: 'Code BIC',
-    iban: 'IBAN',
-    holder: 'Titulaire du compte',
-    debitAddress: 'Domiciliation',
-    principal: 'Principal',
-    address_postal: 'Adresses postales',
-    address: 'Adresse',
-    address_postal_type: 'Nature',
-    addressOwner: 'Chez',
-    streetAddress: 'Adresses',
-    streetAddressSecond: 'Adresses suite',
-    streetAddressThird: 'Adresses suite 2',
-    postalCode: 'Code postal',
-    addressCity: 'Ville',
-    country: 'Pays',
-    addresstype: 'Nature',
-    contactpoint_type: 'Type de contact',
-    phoneNumberInvalid: 'Numéro de téléphone invalide',
-    logo: 'Logo',
-    subdomain: 'Sous-domaine',
-  })
-}

+ 0 - 28
lang/form/fr-FR.js

@@ -1,28 +0,0 @@
-export default (context, locale) => {
-  return ({
-    upload_image: 'Sélectionner une image',
-    of: 'de',
-    allResult: 'Tous',
-    itemsPerPage: 'Nombre de résultats par page',
-    autocomplete_research: 'Aucun résultat ne correspond à votre recherche',
-    add: 'Ajouter',
-    save: 'Enregistrer',
-    save_and_back: 'Enregistrer et retour',
-    back: 'Retour',
-    cancel: 'Annuler',
-    delete: 'Supprimer',
-    confirm_to_delete: 'Vous êtes sur le point de supprimer un élément.',
-    saveSuccess: 'Sauvegarde effectuée',
-    deleteSuccess: 'Suppression effectuée',
-    quit_form: 'Quitter le formulaire',
-    save_and_quit: 'Sauvegarder et quitter le formulaire',
-    back_to_form: 'Retourner au formulaire',
-    caution: 'Attention',
-    updateMap: 'Mise à jour de la carte',
-    start_your_research: 'Commencer à écrire pour rechercher...',
-    no_coordinate_corresponding: 'Aucune coordonnées GPS ne correspondent à votre adresse',
-    quit_without_saving_warning: 'Vous souhaitez quitter ce formulaire sans avoir enregistré',
-    please_wait: 'Veuillez patienter',
-    download: 'Télécharger'
-  })
-}

+ 0 - 25
lang/fr-FR.js

@@ -1,25 +0,0 @@
-import layout from '@/lang/layout/fr-FR'
-import enums from '@/lang/enum/fr-FR'
-import fields from '@/lang/field/fr-FR'
-import rulesAndErrors from '@/lang/rulesAndErrors/fr-FR'
-import form from '@/lang/form/fr-FR'
-import breadcrumbs from '@/lang/breadcrumbs/fr-FR'
-import menuKey from '@/lang/menuKey/fr-FR'
-import help from '@/lang/help/fr-FR'
-import contentSubscription from '@/lang/content/subscription/fr-FR'
-import contentParameters from '@/lang/content/parameters/fr-FR'
-
-export default (context, locale) => {
-  return {
-    ...layout(context, locale),
-    ...enums(context, locale),
-    ...fields(context, locale),
-    ...rulesAndErrors(context, locale),
-    ...form(context, locale),
-    ...breadcrumbs(context, locale),
-    ...menuKey(context, locale),
-    ...help(context, locale),
-    ...contentSubscription(context, locale),
-    ...contentParameters(context, locale),
-  }
-}

+ 570 - 0
lang/fr-FR.json

@@ -0,0 +1,570 @@
+{
+  "fr": {
+    "item": "Détails",
+    "organization_breadcrumbs": "Fiche de la structure",
+    "subscription_breadcrumbs": "Mon abonnement",
+    "address_breadcrumbs": "Adresse postale",
+    "contact_points_breadcrumbs": "Points de contact",
+    "help_super_admin": "Le compte super-admin possède tous les droits de gestion sur votre logiciel. On l’utilise surtout pour la gestion de votre site internet et, à la première connexion au logiciel, afin de créer des comptes pour tous membres de votre structure. Enfin, il peut également être utile en cas de dépannage dans certaines situations particulières.",
+    "yourWebsiteAddressIs": "L'adresse de votre site web est",
+    "areYourSureYouWantToDisableYourOpentalentWebsite": "Êtes-vous sûr(e) de vouloir désactiver votre site web Opentalent",
+    "youRegisteredTheFollowingSubdomain": "Vous avez enregistré le sous-domaine suivant",
+    "subdomainIsCurrentlyActive": "Le sous-domaine est actuellement actif",
+    "doYouWantToActivateThisSubdomain": "Voulez-vous activer ce sous-domaine",
+    "activate": "Activer",
+    "active": "Actif",
+    "pleaseEnterYourNewSubdomain": "Veuillez saisir votre nouveau sous-domaine",
+    "subdomain_need_to_have_0_to_60_cars": "Le sous-domaine doit comporter de 2 à 60 caractères",
+    "this_subdomain_is_already_in_use": "Ce sous-domaine est déjà utilisé",
+    "this_subdomain_is_available": "Ce sous-domaine est disponible",
+    "subdomain_can_not_contain_spaces_or_special_cars": "Le sous-domaine ne doit pas contenir d'espaces ni de caractères spéciaux",
+    "please_enter_a_value_for_the_subdomain": "Veuillez saisir une valeur pour le sous-domaine",
+    "validation_ongoing": "Validation en cours",
+    "until": "Jusqu'au",
+    "get_more_functionalities_with_version": "Bénéficiez de plus de fonctionnalités avec la version",
+    "for_only_x_eur_ttc_by_month": "Pour seulement {price} TTC par mois",
+    "convert_price_to_sms": "soit {nb_sms} SMS",
+    "yearly_paid_giving_x_eur_ttc_per_year": "Payable annuellement, soit {price} TTC / an",
+    "only_for_cmf_members": "Offre réservée aux adhérents CMF",
+    "public_price_x_ttc_a_year": "Prix public: {price} TTC/an",
+    "product_sheet": "Fiche produit",
+    "download_order_form": "Télécharger le bon de commande",
+    "download_cmf_order_form": "Télécharger le bon de commande CMF",
+    "a_suitable_solution_for_your_artistic_school": "Une solution économique adaptée à votre établissement d'enseignement artistique",
+    "starting_from_x_eur_ttc_per_month": "A partir de {price} TTC par mois",
+    "version_x_up_to_x_students": "Version {product} jusqu'à {max_students} étudiants",
+    "excluding_license_and_training_fees": "Hors frais de licence d'utilisation et de formation",
+    "freely_try_our_software": "Essayez notre logiciel en toute liberté",
+    "contact_us_for_show_and_demo": "Contactez-nous sans plus tarder pour obtenir une présentation ainsi qu'un accès de démonstration",
+    "contact_us_at": "Contactez-nous au",
+    "or_by_mail_at": "ou par mail à l'adresse",
+    "switch_to_version": "Passez à la version",
+    "and_benefit": "et bénéficiez",
+    "of_accounts_for_teachers_and_students": "de comptes pour vos professeurs et élèves",
+    "of_a_complete_website": "d'un site internet complet",
+    "send_sms": "Envoyez des SMS",
+    "to_your_members_from_app": "à vos membres / élèves depuis votre logiciel",
+    "starting_from_x_eur_ttc_per_sms": "A partir de {price} TTC / sms",
+    "for_x_sms": "pour {amount} SMS",
+    "get_your_own_domain_and_up_to_five_emails_for_only_x_eur_ttc_per_month": "Bénéficiez de votre propre nom de domaine et 5 adresses email pour seulement {price} TTC / mois",
+    "example": "Exemple",
+    "domain_name": "Nom de domaine",
+    "dummy_domain_name": "ma-structure.fr",
+    "associated_mail_address": "Adresse email associée",
+    "dummy_email_address": "contact[at]ma-structure.fr",
+    "GUARDIANS": "Tuteurs uniquement",
+    "STUDENTS": "Élèves uniquement",
+    "STUDENTS_AND_THEIR_GUARDIANS": "Élèves et leurs tuteurs",
+    "ANNUAL": "Annuel",
+    "HALF": "Semestriel",
+    "QUARTERLY": "Trimestriel",
+    "MONTHLY": "Mensuel",
+    "BY_EDUCATION": "Par enseignement",
+    "BY_TEACHER": "Par professeur",
+    "CATEGORY_ORCHESTRE": "Orchestre",
+    "CATEGORY_AMBULATORY": "Musique ambulatoire",
+    "CATEGORY_OTHER": "Autres activités",
+    "CATEGORY_CHORUS": "Chorale / Groupe vocal",
+    "CATEGORY_BAND": "Ensemble",
+    "BRASS_BAND": "Brass band",
+    "HUNTING_HORNS": "Trompes de chasse",
+    "PHILHARMONIC_ORCHESTRA": "Orchestre philharmonique",
+    "ACCORDION_ORCHESTRA": "Orchestre d'accordéons",
+    "HARMONY_ORCHESTRA": "Orchestre d'harmonie",
+    "ORCHESTRA_CLASS": "Classe d'orchestre",
+    "SYMPHONY_ORCHESTRA": "Orchestre symphonique",
+    "STRING_ORCHESTRA": "Orchestre à cordes",
+    "PLUCKED_ORCHESTRA": "Orchestre à plectres",
+    "FANFARE_BAND": "Orchestre de fanfare",
+    "BAGAD": "Bagad",
+    "BANDAS": "Bandas ou Fanfare de rue",
+    "BATTERY_FANFARE": "Batterie fanfare",
+    "BATTUCADA": "Battucada",
+    "FOLKLORIC_BAND": "Ensemble folklorique",
+    "FIFE_AND_DRUM": "Fifres et tambours",
+    "MARCHING_BAND": "Marching band ou Show parade",
+    "CHILDRENS_CHOIR": "Choeur d'enfants",
+    "FEMAL_CHOIR": "Choeur de femmes",
+    "MENS_CHOIR": "Choeur d'hommes",
+    "MIXED_CHORUS": "Choeur mixte",
+    "VOCAL_BAND_UP_16": "Ensemble vocal (jusqu'à 16)",
+    "CLARINET_CHOIR": "Ensemble de clarinettes",
+    "COPPER_BAND": "Ensemble de cuivres",
+    "FLUTE_ENSEMBLE": "Ensemble de flûtes",
+    "SAXOPHONES_BAND": "Ensemble de saxophones",
+    "VIOLIN_BAND": "Ensemble de violons",
+    "PERCUSSION_BAND": "Ensemble de percussions",
+    "CURRENT_MUSIC_GROUP": "Groupe de Musique actuelle",
+    "CHAMBER_MUSIC_ENSEMBLE": "Ensemble de Musique de chambre",
+    "TRADITIONAL_MUSIC_ENSEMBLE": "Ensemble de Musique traditionnelle",
+    "JAZZ_BAND": "Ensemble de Jazz",
+    "EDUCATION": "Enseignement",
+    "CHEERLEADER": "Majorettes",
+    "TROOP": "Troupe",
+    "BIG_BAND": "Big band",
+    "PRODUCT_ARTIST": "Opentalent Artist",
+    "PRODUCT_ARTIST_PREMIUM": "Opentalent Artist Premium",
+    "PRODUCT_SCHOOL": "Opentalent School",
+    "PRODUCT_SCHOOL_PREMIUM": "Opentalent School Premium",
+    "PRODUCT_MANAGER": "Opentalent Manager",
+    "LOCAL_AUTHORITY": "Collectivité territoriale (Mairie, SIVOM, SIVU, EPIC, …)",
+    "ASSOCIATION_LAW_1901": "Association loi 1901 ou assimilée (Droit local, ...)",
+    "COMMERCIAL_SOCIETY": "Entreprise commerciale (SARL, SAS, EURL, Autoentrepreneur, …)",
+    "ARTISTIC_EDUCATION_ONLY": "Enseignement artistique seul",
+    "ARTISTIC_PRACTICE_EDUCATION": "Pratique et enseignement artistique",
+    "ARTISTIC_PRACTICE_ONLY": "Pratique artistique seule",
+    "DELEGATION": "Délégation",
+    "DEPARTEMENTAL_FEDERATION": "Fédération départementale",
+    "GROUPMENT": "Groupement",
+    "LOCAL_FEDERATION": "Fédération locale",
+    "MUSIC_OPENTALENT": "Opentalent",
+    "NATIONAL_FEDERATION": "Fédération nationale",
+    "REGIONAL_FEDERATION": "Fédération régionale",
+    "CESMD": "CESMD Centre d'études supérieures de musique et de danse",
+    "CNSMD": "CNSMD Conservatoire national supérieur de musique",
+    "CRC": "CRC Conservatoire à rayonnement communal",
+    "CRD": "CRD Conservatoire à rayonnement départemental",
+    "CRI": "CRI Conservatoire à rayonnement intercommunal",
+    "CRR": "CRR Conservatoire à rayonnement régional",
+    "EENC": "EENC Établissement d'enseignement artistique non classé",
+    "EMP": "EMP École de musique privée",
+    "MULTIPLE": "Multiple",
+    "UNIQUE": "Unique",
+    "MAIN_BUILDING": "Établissement principal",
+    "SECONDARY_SCHOOL": "Établissement secondaire",
+    "ACTALIANS": "Actalians",
+    "AFDAS": "Afdas",
+    "AGEFOS": "Agefos",
+    "AGEFOS_PME": "Agefos pme",
+    "ANFA": "Anfa",
+    "ANFH": "Anfh",
+    "APCMA": "Apcma",
+    "CNFPT": "Cnfpt",
+    "CONSTRUCTYS": "Constructys",
+    "FAF_TT": "Faf-tt",
+    "FAFIEC": "Fafiec",
+    "FAFIH": "Fafih",
+    "FAFSEA": "Fafsea",
+    "FIF_PL": "Fif pl",
+    "FONGECIF": "Fongecif",
+    "FORCO": "Forco",
+    "INTERGROS": "Intergros",
+    "OPCA3_PLUS": "Opca3+",
+    "OPCAIM": "Opcaim",
+    "OPCALIA": "Opcalia",
+    "OPCALIM": "Opcalim",
+    "OPCA_BAIA": "Opca baia",
+    "OPCA_DEFI": "Opca defi",
+    "OPCA_TRANSPORTS": "Opca transports",
+    "UNIFAF": "Unifaf",
+    "UNIFORMATION": "Uniformation",
+    "VIVEA": "Vivea",
+    "ADDRESS_PRACTICE": "Adresse de pratique",
+    "ADDRESS_HEAD_OFFICE": "Adresse du siège social",
+    "ADDRESS_CONTACT": "Adresse de contact",
+    "ADDRESS_BILL": "Adresse de facturation",
+    "ADDRESS_OTHER": "Autre adresse",
+    "PRINCIPAL": "Contact principal",
+    "BILL": "Contact de facturation",
+    "OTHER": "Autre",
+    "CONTACT": "Contact",
+    "cycle": "Cycle",
+    "timing": "Durée d'un enseignement (en minutes)",
+    "educationTiming": "Durée d'un enseignement (en minutes)",
+    "superAdmin": "Compte super-admin",
+    "username": "Login de connexion",
+    "residenceArea": "Zones de résidence",
+    "deactivateOpentalentSiteWeb": "Désactiver le site opentalent",
+    "reactivateOpentalentSiteWeb": "Réactiver le site Opentalent",
+    "passwordSMS": "Mot de passe SMS",
+    "usernameSMS": "Nom d'utilisateur SMS",
+    "smsSenderName": "Personnaliser le nom de l'expéditeur SMS",
+    "attendance": "Absences",
+    "sendAttendanceEmail": "Prévenir automatiquement la famille par mail en cas d'absence non justifiée",
+    "sendAttendanceSms": "Prévenir automatiquement la famille par sms en cas d'absence non justifiée",
+    "bulletinReceiver": "Adresser le bulletin à",
+    "bulletinEditWithoutEvaluation": "Editer également les bulletins ne contenant aucune évaluation",
+    "bulletinShowAverages": "Afficher les moyennes",
+    "bulletinShowAbsences": "Afficher les absences",
+    "bulletinViewTestResults": "Afficher les résultats des examens",
+    "bulletinShowEducationWithoutEvaluation": "Afficher les enseignements ne contenant aucune évaluation",
+    "bulletinDisplayLevelAcquired": "Affichage niveau acquis",
+    "bulletinSignatureDirector": "Un cadre « Tampon / Signature » pour l'administration",
+    "bulletinPrintAddress": "L'adresse postale de l'élève ou son tuteur",
+    "bulletinWithTeacher": "Le nom du professeur",
+    "bulletin_parameters": "Bulletins",
+    "sms": "Sms",
+    "web_parameters": "Site internet",
+    "averageMax": "Note maximale pour les notes du suivi pédagogique (entre 1 et 100)",
+    "educational_follow_up": "Suivi pédagogique",
+    "advancedEducationNotationType": "Type de grilles d'évaluation",
+    "educationPeriodicity": "Périodicité des évaluations",
+    "editCriteriaNotationByAdminOnly": "Autoriser uniquement l'administration à modifier les critères d'évaluation",
+    "trackingValidation": "Contrôle et validation du suivi pédagogique par l'administration",
+    "publicationDirectors": "Directeur(s) de publication",
+    "otherWebsite": "Autre site web",
+    "newSubDomain": "Nouveau sous domaine",
+    "yourSubdomains": "Vos sous-domaines",
+    "timezone": "Fuseau horaire",
+    "qrCode": "QrCode pour la licence",
+    "studentsAreAdherents": "Les élèves sont également adhérents de l'association",
+    "showAdherentList": "Afficher la liste des adhérents et leurs coordonnées",
+    "endCourseDate": "Date de fin des cours ",
+    "startCourseDate": "Date de début des cours ",
+    "generalParams": "Paramètres généraux",
+    "financialDate": "Début de la saison financière",
+    "musicalDate": "Début de la saison d'activité",
+    "title": "Titre",
+    "link": "Lien",
+    "organizationArticle": "Coups de projecteur",
+    "youtube": "Lien YouTube",
+    "first_subscription": "Date de première adhésion",
+    "bicInvalid": "Code BIC invalide",
+    "ibanInvalid": "IBAN invalide",
+    "invalid_bic": "Votre BIC est invalide",
+    "invalid_iban": "Votre IBAN est invalide",
+    "importAddress": "Importer l'adresse d'une des personnes de votre structure",
+    "addressCountry": "Pays",
+    "legalInformation": "Informations légales",
+    "agrements": "Agréments",
+    "salary": "Salariés",
+    "legalStatus": "Statut juridique",
+    "siretNumber": "N° SIRET",
+    "apeNumber": "Code APE",
+    "waldecNumber": "RNA (ancien Waldec)",
+    "identifierCmf": "Matricule CMF",
+    "identifierFfec": "Matricule FFEC",
+    "ffecApproval": "N° agrément FFEC",
+    "description": "Description",
+    "typeOfPractices": "Type de pratiques",
+    "otherPractice": "Autres ensembles (préciser)",
+    "principalType": "Type principal",
+    "contact_point": "Point de contact",
+    "name": "Nom",
+    "acronym": "Sigle",
+    "creationDate": "Date de création",
+    "prefectureName": "Préfecture ou sous-préfecture",
+    "prefectureNumber": "Numéro de déclaration",
+    "declarationDate": "Date de déclaration",
+    "tvaNumber": "TVA Intracommunautaire",
+    "schoolCategory": "Catégorie d'école",
+    "typeEstablishment": "Type d'établissement",
+    "typeEstablishmentDetail": "Détails du type",
+    "youngApproval": "Jeunesse-éducation populaire",
+    "trainingApproval": "Organisme de formation",
+    "otherApproval": "Si autre, lesquels",
+    "collectiveAgreement": "Nom de la convention collective",
+    "opca": "Nom de l'OPCA",
+    "icomNumber": "N° ICOM",
+    "urssafNumber": "N° URSSAF",
+    "email": "E-mail",
+    "emailInvalid": "E-mail invalide",
+    "telphone": "Téléphone",
+    "telphoneInvalid": "Téléphone invalide",
+    "faxNumber": "Fax",
+    "faxNumberInvalid": "Fax invalide",
+    "mobilPhone": "Portable",
+    "mobilPhoneInvalid": "Portable invalide",
+    "actions": "Actions",
+    "twitter": "Lien Twitter",
+    "facebook": "Lien Facebook",
+    "instagram": "Lien Instagram",
+    "image": "Image",
+    "portailVisibility": "Répertorier la structure dans l'annuaire du portail Opentalent",
+    "pedagogicBudget": "Budget pédagogique",
+    "budget": "Montant du dernier budget réalisé",
+    "isPedagogicIsPrincipalActivity": "L'activité principale de la structure est « la pédagogie des arts du cirque »",
+    "bank_account": "IBAN",
+    "bankName": "Nom de la banque",
+    "bic": "Code BIC",
+    "iban": "IBAN",
+    "holder": "Titulaire du compte",
+    "debitAddress": "Domiciliation",
+    "principal": "Principal",
+    "address_postal": "Adresses postales",
+    "address": "Adresse",
+    "address_postal_type": "Nature",
+    "addressOwner": "Chez",
+    "streetAddress": "Adresses",
+    "streetAddressSecond": "Adresses suite",
+    "streetAddressThird": "Adresses suite 2",
+    "postalCode": "Code postal",
+    "addressCity": "Ville",
+    "country": "Pays",
+    "addresstype": "Nature",
+    "contactpoint_type": "Type de contact",
+    "phoneNumberInvalid": "Numéro de téléphone invalide",
+    "logo": "Logo",
+    "subdomain": "Sous-domaine",
+    "upload_image": "Sélectionner une image",
+    "of": "de",
+    "allResult": "Tous",
+    "itemsPerPage": "Nombre de résultats par page",
+    "autocomplete_research": "Aucun résultat ne correspond à votre recherche",
+    "add": "Ajouter",
+    "save": "Enregistrer",
+    "save_and_back": "Enregistrer et retour",
+    "back": "Retour",
+    "cancel": "Annuler",
+    "delete": "Supprimer",
+    "confirm_to_delete": "Vous êtes sur le point de supprimer un élément.",
+    "saveSuccess": "Sauvegarde effectuée",
+    "deleteSuccess": "Suppression effectuée",
+    "quit_form": "Quitter le formulaire",
+    "save_and_quit": "Sauvegarder et quitter le formulaire",
+    "back_to_form": "Retourner au formulaire",
+    "caution": "Attention",
+    "updateMap": "Mise à jour de la carte",
+    "start_your_research": "Commencer à écrire pour rechercher...",
+    "no_coordinate_corresponding": "Aucune coordonnées GPS ne correspondent à votre adresse",
+    "quit_without_saving_warning": "Vous souhaitez quitter ce formulaire sans avoir enregistré",
+    "please_wait": "Veuillez patienter",
+    "download": "Télécharger",
+    "bulletinEditWithoutEvaluationHelp": "Dans le cas où la case n'est pas cochée, s'il n'y a aucune évaluation dans le bulletin, alors le bulletin n'est pas exporté",
+    "bulletinShowEducationWithoutEvaluationHelp": "Dans le cas où la case est cochée, alors on affiche le texte \"Aucune évaluation\" pour chaque enseignement sans évaluation",
+    "type_of_practices_autocomplete": "Sélectionnez parmi la liste des types de pratiques, une ou plusieurs pratiques correspondant aux activités de votre structure",
+    "logo_upload": "<div>Le logo est utilisée: </div>\n<ul><li>dans l'entête des documents que vos exportez</li><li>sur le site internet</li><li>dans la recherche d'une structure sur le site Opentalent ou sur le site d'une fédération si vous êtes membre de la CMF (voir <a target=\"_blank\" href=\"https://fmfaucigny.opentalent.fr/presentation/societes-adherentes\">exemple ici</a>)</li></ul>",
+    "communication_image_upload": "L'image est utilisé\n dans la description détaillée d'une structure sur le site Opentalent ou sur le site d'une fédération si vous êtes membre de la CMF (voir <a target=\"_blank\" href=\"https://fmfaucigny.opentalent.fr/presentation/societes-adherentes\">exemple ici</a>)",
+    "general_params": "Général",
+    "communication_params": "Communication",
+    "students_params": "Suivi des étudiants",
+    "education_params": "Enseignements",
+    "bills_params": "Facturation",
+    "secure_params": "Sécurité",
+    "back_to_dashboard": "Quittez les paramètres",
+    "universal_create_title_access": "Quel type de contact souhaitez-vous créer ?",
+    "universal_create_title_event": "Que souhaitez-vous ajouter à votre planning ?",
+    "universal_create_title_message": "Que souhaitez-vous envoyer ?",
+    "an_adherent": "Un adhérent",
+    "adherent_text_creation_card": "Ajoutez un membre à votre structure le statut d'adhérent",
+    "a_ca_member": "Un membre du CA",
+    "ca_member_text_creation_card": "Ajoutez un président, trésorier, secrétaire, membre actif... à votre Conseil d'Administration",
+    "modif_picture": "Modifier l'image",
+    "image_assistant": "Assistant de téléchargement",
+    "delete_assistant": "Assistant de suppression",
+    "creative_assistant": "Assistant de création",
+    "what_do_you_want_to_create": "Que souhaitez-vous créer ?",
+    "previous": "Étape précédente",
+    "add_any_type_material": "Ajoutez tout type de matériel ou de documents tels que des partitions à votre parc de matériel",
+    "a_materiel": "Un matériel",
+    "sen_email_letter": "Envoyez un email, un courrier, ou un SMS aux personnes de votre carnet d'adresses",
+    "a_correspondence": "Une correspondance",
+    "add_an_event_course": "Ajoutez un évènement, un cours, une prestation pédagogique, un examen... à votre planning",
+    "an_event": "Un évènement",
+    "add_new_person_student": "Ajoutez un nouveau membre parent, élève, professeur, personnel... à votre répertoire",
+    "a_person": "Une personne",
+    "other_event_text_creation_card": "Comprend entre autres: auditions, concerts, répétitions, spectacles, stages...",
+    "educational_services_text_creation_card": "Correspond aux interventions en milieu scolaire, pénitentiaire, ou hospitalier",
+    "exam_text_creation_card": "Permet d'organiser des examens avec la gestion des jurys, des convocations et des résultats",
+    "course_text_creation_card": "On associe les élèves à leurs enseignements, puis à leurs cours, qui peut être périodique ou ponctuel",
+    "other_event": "Autre événement",
+    "educational_services": "Prestations pédagogiques",
+    "exam": "Examen",
+    "course": "Cours",
+    "sms_text_creation_card": "Les SMS sont disponible sur option, vous devez disposer de suffisamment de crédit",
+    "letter_text_creation_card": "Un courrier est imprimé pour être envoyé par la Poste mais peut aussi être envoyé par mail",
+    "email_text_creation_card": "Les emails peuvent également être des newsletters / lettre d'information",
+    "an_sms": "Un sms",
+    "a_letter": "Un courrier",
+    "an_email": "Un email",
+    "another_type_of_contact": "Un autre type de contact",
+    "a_legal_entity": "Une personne morale",
+    "a_member_of_staff": "Un membre du personnel",
+    "a_teacher": "Un professeur",
+    "a_guardian": "Un tuteur",
+    "a_student": "Un élève",
+    "other_contact_text_creation_card": "Ajoutez un autre type de contact qui n'a pas été défini précédemment",
+    "moral_text_creation_card": "Ajoutez les structures qui vous soutiennent ou avec qui vous travaillez",
+    "personnel_text_creation_card": "Ajoutez un membre à votre personnel et donnez-lui un accès administratif",
+    "teacher_text_creation_card": "Ajoutez un professeur à votre personnel et donnez-lui un accès pédagogique",
+    "student_text_creation_card": "Inscrivez un nouvel élève via le formulaire de la vue famille",
+    "guardian_text_creation_card": "Ajoutez un tuteur à votre carnet d'adresses afin de l'associer ultérieurement à un élève",
+    "click_here": "cliquez ici",
+    "super_admin_switch_account": "Vous utilisez une connexion SWITCH. Afin de retourner sur votre compte veuillez",
+    "insurance_cmf_subscription": "Souscrire un contrat assurance CMF",
+    "renew_insurance_cmf": "Accéder au renouvellement de votre assurance CMF",
+    "upload_cotisation_invoice": "Télécharger la facture de votre appel de cotisation",
+    "cotisation_access": "Accéder au renouvellement de cotisation à ma fédération",
+    "information_new_online_registration": "Nouvelle préinscription",
+    "not_production_environment": "ATTENTION ! Vous êtes sur un environnement {env}.",
+    "multi_account_alert": "Vous êtes connecté, en tant que <strong>{fullname}</strong>, avec un accès famille. Utilisez l'icône",
+    "multi_account_alert_next": "en haut à droite pour changer les informations des autres membres de votre famille.",
+    "not_current_year": "Votre logiciel est actuellement placé dans une autre année que celle actuelle, et/ou affiche des données passées/futures.",
+    "not_current_year_reset": "Cliquez ici pour afficher les données de l'année actuelle.",
+    "welcome": "Accueil",
+    "address_book": "Répertoire",
+    "person": "Personnes",
+    "family_view": "Vue famille",
+    "education_student_next_year": "Gestion des inscriptions",
+    "commissions": "Commissions",
+    "my_network": "Répertoire du réseau",
+    "network": "Réseau",
+    "schedule": "Agenda",
+    "attendances": "Absences",
+    "equipment": "Parc matériel",
+    "education_state": "Suivi pédagogique",
+    "criteria_notations": "Critères d'évaluation",
+    "education_notation_configs": "Grilles d'évaluation",
+    "seizure_period": "Périodes de saisie",
+    "test_seizure": "Saisie des évaluations",
+    "test_validation": "Validation par évaluation",
+    "examen_results": "Résultats des examens",
+    "education_by_student_validation": "Validation par enseignement",
+    "billing": "Facturation",
+    "billing_product": "Produits",
+    "billing_products_by_student": "Produits par élève",
+    "billing_edition": "Édition des factures",
+    "billing_accounting": "Factures et avoirs",
+    "billing_payment_list": "Journal des règlements",
+    "pes_export": "Export JVS",
+    "berger_levrault_export": "Export Berger Levrault",
+    "jvs_export": "Export JVS",
+    "inbox": "Boite d'envoi",
+    "message_send": "Éléments envoyés",
+    "message_templates": "Modèles",
+    "communication": "Communication",
+    "donors": "Partenariats et dons",
+    "medals": "Médailles",
+    "stats": "Statistiques",
+    "report_activity": "Rapport d'activité",
+    "educations_quotas_by_education": "Quotas par enseignement",
+    "fede_stats": "Fédérations",
+    "structure_stats": "Structures",
+    "rate_cotisation": "Saisie du tarif",
+    "parameters_cotisation": "Paramétrer l'appel de cotisation",
+    "send_cotisation": "Appel des cotisations",
+    "state_cotisation": "Suivi des cotisations",
+    "pay_cotisation": "Saisie des règlements",
+    "check_cotisation": "Remise de chèques",
+    "ledger_cotisation": "Journal des règlements",
+    "magazine_cotisation": "Bulletin",
+    "ventilated_cotisation": "Cotisations ventilées par sous-total",
+    "pay_erase_cotisation": "Suppression de règlements",
+    "resume_cotisation": "État des transmissions",
+    "history_cotisation": "Historique des cotisations",
+    "call_cotisation": "Règlements à la fédération",
+    "history_structure_cotisation": "Cotisations fédérales",
+    "insurance_cotisation": "Assurance CMF",
+    "resume_all_cotisation": "Toutes les cotisations",
+    "resume_pay_cotisation": "Cotisation avec un règlement reçu ou en attente",
+    "cotisations": "Cotisations",
+    "all_accesses": "Toutes les personnes",
+    "admin2ios": "Administration 2ios",
+    "all_organizations": "Toutes les structures",
+    "tips": "Tips",
+    "actions_lead": "Actions à conduire",
+    "renewal_list": "Structures à relancer",
+    "settlements": "Règlements effectués",
+    "pending_settlements": "Règlements en attentes",
+    "outages_notice": "Coupure de service",
+    "degraded": "Clients",
+    "dgv": "Assurance CMF",
+    "cmf_cotisation": "Cotisation CMF",
+    "right_menu": "Droits version 5.9",
+    "tree_menu": "Gestion de l'arbre",
+    "website": "Site internet",
+    "advanced_modification": "Administration site internet",
+    "simple_modification": "Modifications simplifiées",
+    "create": "Créer",
+    "help_access": "Accès aide",
+    "configuration": "Configuration",
+    "organization_page": "Fiche de la structure",
+    "cmf_licence_generate": "Générer la licence CMF de la structure",
+    "cmf_structure_licence": "Licence CMF de la structure",
+    "your_cmf_licence": "Votre licence CMF",
+    "cmf_licence_details_url": "Consulter les avantages de la licence CMF",
+    "generate": "Générer",
+    "parameters": "Préférences",
+    "place": "Lieux",
+    "education": "Enseignements",
+    "tag": "Tags",
+    "activities": "Sections",
+    "billing_settings": "Facturation",
+    "online_registration_settings": "Pré-inscription(s) en ligne",
+    "transition_next_year": "Passage à l'année suivante",
+    "course_duplication": "Dupliquer les cours hebdomadaires",
+    "import": "Importer",
+    "schooling_year": "Année scolaire",
+    "season_year": "Saison",
+    "cotisation_year": "Année de cotisation",
+    "multiAccesses": "Mes structures",
+    "familyAccesses": "Changement d'utilisateur",
+    "display_data": "Afficher les données",
+    "past": "Passée",
+    "present": "Présent",
+    "future": "Future",
+    "notification": "Notifications",
+    "history_help": "Personnaliser la période d'affichage",
+    "period_choose": "Période à afficher",
+    "date_choose": "Veuillez sélectionner deux dates",
+    "my_list": "Mes listes",
+    "searchList": "Rechercher parmi mes listes personnalisées",
+    "template_systems": "Mails système",
+    "informations": "Informations",
+    "more_features": "Plus de fonctionnalités",
+    "client_id": "Numéro de client",
+    "version": "Version",
+    "services": "Services",
+    "bills": "Factures",
+    "paid": "Payée",
+    "unpaid": "Impayée",
+    "reference": "Référence",
+    "date": "Date",
+    "taxExcludedAmount": "Montant H.T.",
+    "taxIncludedAmount": "Montant TTC",
+    "status": "Statut",
+    "remaining_sms_credit": "Crédit SMS restant",
+    "paying_structure": "Établissement payeur",
+    "no_bill_to_display": "Aucune facture à afficher",
+    "my_account": "Mon compte",
+    "my_schedule_page": "Mon planning",
+    "attendance_bookings_menu": "Gestion des absences",
+    "my_attendance": "Mes absences",
+    "my_invitation": "Mes invitations",
+    "my_students": "Mes élèves",
+    "my_students_education_students": "Suivi pédagogique",
+    "my_education_students": "Mes évaluations",
+    "send_an_email": "Envoyer un email",
+    "my_documents": "Mes documents",
+    "my_profile": "Mon profil",
+    "adherent_list": "Liste des adhérents avec leurs coordonnées",
+    "my_subscription": "Mon abonnement",
+    "my_bills": "Mes factures",
+    "print_my_licence": "Imprimer ma licence CMF",
+    "logout": "Se déconnecter",
+    "all_notification": "Toutes les notifications",
+    "your_file": "Votre fichier",
+    "is_ready_to_be_downloaded": "est près à être téléchargé",
+    "your_message": "Votre message",
+    "has_been_sent": "a été envoyé",
+    "ready_to_be": "est prêt à être",
+    "none": "Aucun",
+    "please_confirm": "Veuillez confirmer",
+    "yes": "Oui",
+    "no": "Non",
+    "attendanceListMenuKey": "Absences",
+    "billAndBillCreditListMenuKey": "Facturation",
+    "equipmentListMenuKey": "Matériel",
+    "personRepertoryListMenuKey": "Répertoire",
+    "value_need_to_be_bigger_than_0": "La valeur doit être plus grande que 0",
+    "forbidden": "Vous ne possédez pas les droits nécessaires pour effectuer cette opération",
+    "wrong_mobyt_credentials": "Identifiants SMS incorrects",
+    "smsSenderName_error": "Seuls les caractères alphanumériques sont permis, sans espaces, sans accent et sans caractères spéciaux",
+    "between_1_and_10": "La valeur doit être comprise entre 0 et 10",
+    "between_0_and_100": "La valeur doit être comprise entre 0 et 10",
+    "invalid_form": "Formulaire invalide",
+    "required": "Ce champs est obligatoire",
+    "name_length_rule": "La longueur du nom doit être de moins de 128 caractères",
+    "siret_error": "N° de siret non valide",
+    "email_error": "Adresse email invalide",
+    "phone_error": "Numéro de téléphone invalide",
+    "ADDRESS_PRACTICE_non_unique": "Vous ne pouvez pas avoir 2 adresses de pratique",
+    "ADDRESS_HEAD_OFFICE_non_unique": "Vous ne pouvez pas avoir 2 adresses de siège social",
+    "ADDRESS_CONTACT_non_unique": "Vous ne pouvez pas avoir 2 adresses de contact",
+    "ADDRESS_BILL_non_unique": "Vous ne pouvez pas avoir 2 adresses de facturation",
+    "PRINCIPAL_non_unique": "Vous ne pouvez pas avoir 2 points de contact principaux",
+    "BILL_non_unique": "Vous ne pouvez pas avoir 2 points de contact de facturation",
+    "CONTACT_non_unique": "Vous ne pouvez pas avoir 2 points de contact",
+    "could_not_contact_server_please_try_again": "Le serveur n'a pas pu être contacté, veuillez réessayer un peu plus tard"
+  }
+}

+ 0 - 26
lang/help/fr-FR.js

@@ -1,26 +0,0 @@
-/**
- * Translations for the help tooltips
- *
- * @param context
- * @param locale
- * @returns {{get_more_functionalities_with_version: string, only_for_cmf_members: string, contact_us_at: string, contact_us_for_show_and_demo: string, starting_from_x_eur_ttc_per_month: string, download_order_form: string, for_x_sms: string, for_only_x_eur_ttc_by_month: string, example: string, domain_name: string, and_benefit: string, public_price_x_ttc_a_year: string, product_sheet: string, get_your_own_domain_and_up_to_five_emails_for_only_x_eur_ttc_per_month: string, dummy_domain_name: string, website: string, version_x_up_to_x_students: string, download_cmf_order_form: string, send_sms_from_app_to_your_members: string, freely_try_our_software: string, starting_from_x_eur_ttc_per_ssm: string, yearly_paid_giving_x_eur_ttc_per_year: string, excluding_license_and_training_fees: string, a_suitable_solution_for_your_artistic_school: string, dummy_email_address: string, associated_mail_address: string, switch_to_version: string, or_by_mail_at: string, of_accounts_for_teachers_and_students: string, of_a_complete_website: string}}
- */
-export default (context, locale) => {
-  return ({
-    bulletinEditWithoutEvaluationHelp:'Dans le cas où la case n\'est pas cochée, s\'il n\'y a aucune évaluation dans le bulletin, alors le bulletin n\'est pas exporté',
-    bulletinShowEducationWithoutEvaluationHelp: 'Dans le cas où la case est cochée, alors on affiche le texte "Aucune évaluation" pour chaque enseignement sans évaluation',
-    type_of_practices_autocomplete: 'Sélectionnez parmi la liste des types de pratiques, une ou plusieurs pratiques correspondant aux activités de votre structure',
-    logo_upload: '<div>Le logo est utilisée: </div>' +
-      `\n<ul>` +
-      '    <li>dans l\'entête des documents que vos exportez</li>' +
-      '    <li>sur le site internet</li>' +
-      '    <li>dans la recherche d\'une structure sur le site Opentalent ou sur le site d\'une fédération si vous êtes membre de la CMF ' +
-      '        (voir <a target="_blank" href="https://fmfaucigny.opentalent.fr/presentation/societes-adherentes">exemple ici</a>)</li>' +
-      '</ul>',
-    communication_image_upload: 'L\'image est utilisé\n' +
-      'dans la description détaillée d\'une structure sur le site Opentalent ou ' +
-      'sur le site d\'une fédération si vous êtes membre de la CMF ' +
-      '(voir <a target="_blank" href="https://fmfaucigny.opentalent.fr/presentation/societes-adherentes">exemple ici</a>)'
-
-  })
-}

+ 0 - 222
lang/layout/fr-FR.js

@@ -1,222 +0,0 @@
-export default (context, locale) => {
-  return ({
-    general_params:'Général',
-    communication_params:'Communication',
-    students_params:'Suivi des étudiants',
-    education_params:'Enseignements',
-    bills_params:'Facturation',
-    secure_params:'Sécurité',
-    back_to_dashboard:'Quittez les paramètres',
-    universal_create_title_access: 'Quel type de contact souhaitez-vous créer ?',
-    universal_create_title_event: 'Que souhaitez-vous ajouter à votre planning ?',
-    universal_create_title_message: 'Que souhaitez-vous envoyer ?',
-    an_adherent: 'Un adhérent',
-    adherent_text_creation_card: 'Ajoutez un membre à votre structure le statut d\'adhérent',
-    a_ca_member: 'Un membre du CA',
-    ca_member_text_creation_card: 'Ajoutez un président, trésorier, secrétaire, membre actif... à votre Conseil d\'Administration',
-    modif_picture:'Modifier l\'image',
-    image_assistant:'Assistant de téléchargement',
-    delete_assistant:'Assistant de suppression',
-    creative_assistant:'Assistant de création',
-    what_do_you_want_to_create:'Que souhaitez-vous créer ?',
-    previous:'Étape précédente',
-    cancel:'Annuler',
-    add_any_type_material:'Ajoutez tout type de matériel ou de documents tels que des partitions à votre parc de matériel',
-    a_materiel:'Un matériel',
-    sen_email_letter:'Envoyez un email, un courrier, ou un SMS aux personnes de votre carnet d\'adresses',
-    a_correspondence:'Une correspondance',
-    add_an_event_course:'Ajoutez un évenement, un cours, une prestation pédagogique, un examen... à votre planning',
-    an_event:'Un évènement',
-    add_new_person_student:'Ajoutez un nouveau membre parent, élève, professeur, personnel... à votre répertoire',
-    a_person:'Une personne',
-    other_event_text_creation_card:'Comprend entre autres: auditions, concerts, répétitions, spectacles, stages...',
-    educational_services_text_creation_card:'Correspond aux interventions en milieu scolaire, pénitentiaire, ou hospitalier',
-    exam_text_creation_card:'Permet d\'organiser des examens avec la gestion des jurys, des convocations et des résultats',
-    course_text_creation_card:'On associe les élèves à leurs enseignements, puis à leurs cours, qui peut être périodique ou ponctuel',
-    other_event:'Autre événement',
-    educational_services:'Prestations pédagogiques',
-    exam:'Examen',
-    course:'Cours',
-    sms_text_creation_card: 'Les SMS sont disponible sur option, vous devez disposer de suffisament de crédit',
-    letter_text_creation_card: 'Un courrier est imprimé pour être envoyé par la Poste mais peut aussi être envoyé par mail',
-    email_text_creation_card: 'Les emails peuvent également être des newsletters / lettre d\'information',
-    an_sms: 'Un sms',
-    a_letter: 'Un courrier',
-    an_email: 'Un email',
-    another_type_of_contact: 'Un autre type de contact',
-    a_legal_entity: 'Une personne morale',
-    a_member_of_staff: 'Un membre du personnel',
-    a_teacher: 'Un professeur',
-    a_guardian: 'Un tuteur',
-    a_student: 'Un élève',
-    other_contact_text_creation_card: 'Ajoutez un autre type de contact qui n\'a pas été défini précédemment',
-    moral_text_creation_card: 'Ajoutez les structures qui vous soutiennent ou avec qui vous travaillez',
-    personnel_text_creation_card: 'Ajoutez un membre à votre personnel et donnez-lui un accès administratif',
-    teacher_text_creation_card: 'Ajoutez un professeur à votre personnel et donnez-lui un accès pédagogique',
-    student_text_creation_card: 'Inscrivez un nouvel élève via le formulaire de la vue famille',
-    guardian_text_creation_card: 'Ajoutez un tuteur à votre carnet d\'adresses afin de l\'associer ultérieurement à un élève',
-    click_here: 'cliquez ici',
-    super_admin_switch_account: 'Vous utilisez une connexion SWITCH. Afin de retourner sur votre compte veuillez',
-    insurance_cmf_subscription: 'Souscrire un contrat assurance CMF',
-    renew_insurance_cmf: 'Accéder au renouvellement de votre assurance CMF',
-    upload_cotisation_invoice: 'Télécharger la facture de votre appel de cotisation',
-    cotisation_access: 'Accéder au renouvellement de cotisation à ma fédération',
-    information_new_online_registration: 'Nouvelle préinscription',
-    not_production_environment: 'ATTENTION ! Vous êtes sur un environnement {env}.',
-    multi_account_alert: 'Vous êtes connecté, en tant que <strong>{fullname}</strong>, avec un accès famille. Utilisez l\'icône',
-    multi_account_alert_next: 'en haut à droite pour changer les informations des autres membres de votre famille.',
-    not_current_year: 'Votre logiciel est actuellement placé dans une autre année que celle actuelle, et/ou affiche des données passées/futures.',
-    not_current_year_reset: 'Cliquez ici pour afficher les données de l\'année actuelle.',
-    welcome: 'Accueil',
-    address_book: 'Répertoire',
-    person: 'Personnes',
-    family_view: 'Vue famille',
-    education_student_next_year: 'Gestion des inscriptions',
-    commissions: 'Commissions',
-    my_network: 'Répertoire du réseau',
-    network: 'Réseau',
-    schedule: 'Agenda',
-    attendances: 'Absences',
-    equipment: 'Parc matériel',
-    education_state: 'Suivi pédagogique',
-    criteria_notations: "Critères d'évaluation",
-    education_notation_configs: "Grilles d'évaluation",
-    seizure_period: 'Périodes de saisie',
-    test_seizure: 'Saisie des évaluations',
-    test_validation: 'Validation par évaluation',
-    examen_results: 'Résultats des examens',
-    education_by_student_validation: 'Validation par enseignement',
-    billing: 'Facturation',
-    billing_product: 'Produits',
-    billing_products_by_student: 'Produits par élève',
-    billing_edition: 'Édition des factures',
-    billing_accounting: 'Factures et avoirs',
-    billing_payment_list: 'Journal des règlements',
-    pes_export: 'Export JVS',
-    berger_levrault_export: 'Export Berger Levrault',
-    jvs_export: 'Export JVS',
-    inbox: 'Boite d\'envoi',
-    message_send: 'Éléments envoyés',
-    message_templates: 'Modèles',
-    communication: 'Communication',
-    donors: 'Partenariats et dons',
-    medals: 'Médailles',
-    stats: 'Statistiques',
-    report_activity: 'Rapport d\'activité',
-    educations_quotas_by_education: 'Quotas par enseignement',
-    fede_stats: 'Fédérations',
-    structure_stats: 'Structures',
-    rate_cotisation: 'Saisie du tarif',
-    parameters_cotisation: 'Paramètrer l\'appel de cotisation',
-    send_cotisation: 'Appel des cotisations',
-    state_cotisation: 'Suivi des cotisations',
-    pay_cotisation: 'Saisie des règlements',
-    check_cotisation: 'Remise de chèques',
-    ledger_cotisation: 'Journal des règlements',
-    magazine_cotisation: 'Bulletin',
-    ventilated_cotisation: 'Cotisations ventilées par sous-total',
-    pay_erase_cotisation: 'Suppression de règlements',
-    resume_cotisation: 'Etat des transmissions',
-    history_cotisation: 'Historique des cotisations',
-    call_cotisation: 'Règlements à la fédération',
-    history_struture_cotisation: 'Cotisations fédérales',
-    insurance_cotisation: 'Assurance CMF',
-    resume_all_cotisation: 'Toutes les cotisations',
-    resume_pay_cotisation: 'Cotisation avec un règlement reçu ou en attente',
-    cotisations: 'Cotisations',
-    all_accesses: 'Toutes les personnes',
-    admin2ios: 'Administration 2ios',
-    all_organizations: 'Toutes les structures',
-    tips: 'Tips',
-    actions_lead: 'Actions à conduire',
-    renewall_list: 'Structures à relancer',
-    settlements: 'Règlements effectués',
-    pendings_settlements: 'Règlements en attentes',
-    outages_notice: 'Coupure de service',
-    degraded: 'Clients',
-    dgv: 'Assurance CMF',
-    cmf_cotisation: 'Cotisation CMF',
-    right_menu: 'Droits version 5.9',
-    tree_menu: 'Gestion de l\'arbre',
-    website: 'Site internet',
-    advanced_modification: 'Administration site internet',
-    simple_modification: 'Modifications simplifiées',
-    create: 'Créer',
-    help_access: 'Accès aide',
-    configuration: 'Configuration',
-    organization_page: 'Fiche de la structure',
-    cmf_licence_generate: 'Générer la licence CMF de la structure',
-    cmf_structure_licence: "Licence CMF de la structure",
-    your_cmf_licence: "Votre licence CMF",
-    cmf_licence_details_url: "Consulter les avantages de la licence CMF",
-    generate: "Générer",
-    parameters: 'Préférences',
-    place: 'Lieux',
-    education: 'Enseignements',
-    tag: 'Tags',
-    activities: 'Sections',
-    billing_settings: 'Facturation',
-    online_registration_settings: 'Pré-inscription(s) en ligne',
-    transition_next_year: 'Passage à l\'année suivante',
-    course_duplication: 'Dupliquer les cours hebdomadaires',
-    import: 'Importer',
-    schooling_year: 'Année scolaire',
-    season_year: 'Saison',
-    cotisation_year: 'Année de cotisation',
-    multiAccesses: 'Mes structures',
-    familyAccesses: 'Changement d\'utilisateur',
-    display_data: 'Afficher les données',
-    past: 'Passée',
-    present: 'Présent',
-    future: 'Future',
-    notification: 'Notifications',
-    history_help: 'Personnaliser la période d\'affichage',
-    period_choose: 'Période à afficher',
-    date_choose: 'Veuillez sélectionner deux dates',
-    my_list: 'Mes listes',
-    searchList: 'Rechercher parmi mes listes personnalisées',
-    template_systems: 'Mails système',
-    informations: 'Informations',
-    more_features: 'Plus de fonctionnalités',
-    client_id: 'Numéro de client',
-    version: 'Version',
-    services: 'Services',
-    bills: 'Factures',
-    paid: 'Payée',
-    unpaid: 'Impayée',
-    reference: 'Référence',
-    date: 'Date',
-    taxExcludedAmount: 'Montant H.T.',
-    taxIncludedAmount: 'Montant TTC',
-    status: 'Statut',
-    remaining_sms_credit: 'Crédit SMS restant',
-    paying_structure: 'Établissement payeur',
-    no_bill_to_display: 'Aucune facture à afficher',
-    my_account: 'Mon compte',
-    my_schedule_page: "Mon planning",
-    attendance_bookings_menu: "Gestion des absences",
-    my_attendance: "Mes absences",
-    my_invitation: "Mes invitations",
-    my_students: "Mes élèves",
-    my_students_education_students: "Suivi pédagogique",
-    my_education_students: "Mes évaluations",
-    send_an_email: "Envoyer un email",
-    my_documents: "Mes documents",
-    my_profile: "Mon profil",
-    adherent_list: "Liste des adhérents avec leurs coordonnées",
-    my_subscription: "Mon abonnement",
-    my_bills: "Mes factures",
-    print_my_licence: "Imprimer ma licence CMF",
-    logout: "Se déconnecter",
-    all_notification: "Toutes les notifications",
-    your_file: "Votre fichier",
-    is_ready_to_be_downloaded: "est près à être téléchargé",
-    your_message: "Votre message",
-    has_been_sent: "a été envoyé",
-    ready_to_be: "est prêt à être",
-    none: "Aucun",
-    please_confirm: "Veuillez confirmer",
-    yes: "Oui",
-    no: "Non",
-  })
-}

+ 0 - 8
lang/menuKey/fr-FR.js

@@ -1,8 +0,0 @@
-export default (context, locale) => {
-  return ({
-    attendanceListMenuKey: 'Absences',
-    billAndBillCreditListMenuKey: 'Facturation',
-    equipmentListMenuKey: 'Matériel',
-    personRepertoryListMenuKey: 'Répertoire'
-  })
-}

+ 0 - 24
lang/rulesAndErrors/fr-FR.js

@@ -1,24 +0,0 @@
-export default (context, locale) => {
-  return ({
-    value_need_to_be_bigger_than_0: 'La valeur doit être plus grande que 0',
-    forbidden: 'Vous ne possédez pas les droits nécessaires pour effectuer cette opération',
-    wrong_mobyt_credentials: 'Identifiants SMS incorrects',
-    smsSenderName_error: 'Seuls les caractères alphanumériques sont permis, sans espaces, sans accent et sans caractères spéciaux',
-    between_1_and_10: 'La valeur doit être comprise entre 0 et 10',
-    between_0_and_100: 'La valeur doit être comprise entre 0 et 10',
-    invalid_form: 'Formulaire invalide',
-    required: 'Ce champs est obligatoire',
-    name_length_rule: 'La longueur du nom doit être de moins de 128 caractères',
-    siret_error: 'N° de siret non valide',
-    email_error: 'Adresse email invalide',
-    phone_error: 'Numéro de téléphone invalide',
-    ADDRESS_PRACTICE_non_unique: 'Vous ne pouvez pas avoir 2 adresses de pratique',
-    ADDRESS_HEAD_OFFICE_non_unique: 'Vous ne pouvez pas avoir 2 adresses de siège social',
-    ADDRESS_CONTACT_non_unique: 'Vous ne pouvez pas avoir 2 adresses de contact',
-    ADDRESS_BILL_non_unique: 'Vous ne pouvez pas avoir 2 adresses de facturation',
-    PRINCIPAL_non_unique: 'Vous ne pouvez pas avoir 2 points de contact principaux',
-    BILL_non_unique: 'Vous ne pouvez pas avoir 2 points de contact de facturation',
-    CONTACT_non_unique: 'Vous ne pouvez pas avoir 2 points de contact',
-    could_not_contact_server_please_try_again: 'Le serveur n\'a pas pu être contacté, veuillez réessayer un peu plus tard',
-  })
-}

+ 11 - 0
middleware/auth.ts

@@ -0,0 +1,11 @@
+import {useProfileAccessStore} from "~/store/profile/access";
+import {defineNuxtRouteMiddleware, navigateTo} from "#app";
+
+export default defineNuxtRouteMiddleware(
+(to, from) => {
+      // Si l'utilisateur n'est pas connecté on le redirige vers la page login
+      if (!useProfileAccessStore().bearer) {
+        // TODO: vérifier qu'on passe bien par ici, car en général ça pète dans le init.server.ts
+        return navigateTo(process.env.baseUrlAdminLegacy + '/login')
+      }
+})

+ 19 - 2
nuxt.config.ts

@@ -83,7 +83,6 @@ export default {
     modules: [
         [
             '@pinia/nuxt',
-            '@pinia-orm/nuxt',
             {
                 autoImports: [
                     // automatically imports `usePinia()`
@@ -93,7 +92,9 @@ export default {
                 ],
             }
         ],
-        'nuxt-lodash'
+        '@pinia-orm/nuxt',
+        'nuxt-lodash',
+        '@nuxtjs/i18n',
     ],
     typescript: {
         strict: true
@@ -116,5 +117,21 @@ export default {
                 protocol: 'wss'
             }
         }
+    },
+    i18n: {
+        locales: [
+            {
+                code: 'fr',
+                file: 'fr-FR.json'
+            }
+        ],
+        lazy: true,
+        langDir: '~/lang/',
+        defaultLocale: 'fr',
+        vueI18n: {
+            legacy: false,
+            silentTranslationWarn: true,
+            silentFallbackWarn: true
+        }
     }
 }

+ 28 - 25
package.json

@@ -24,52 +24,55 @@
     "lint-fix": "eslint --fix --ext \".ts,.js,.vue\" --ignore-path .gitignore ."
   },
   "devDependencies": {
-    "@nuxt/test-utils-edge": "^3.0.0-rc.11-27727864.720da64",
-    "@nuxtjs/eslint-config": "^10.0.0",
+    "@nuxt/test-utils-edge": "^3.0.1-rc.0-27810184.d991a55",
+    "@nuxtjs/eslint-config": "^11.0.0",
     "@nuxtjs/eslint-config-typescript": "^11.0.0",
     "@nuxtjs/eslint-module": "^3.1.0",
+    "@types/cleave.js": "^1.4.7",
     "@types/event-source-polyfill": "^1.0.0",
-    "@types/jest": "^29.0.3",
-    "@types/lodash": "^4.14.185",
+    "@types/jest": "^29.2.3",
+    "@types/lodash": "^4.14.189",
     "@types/uuid": "^8.3.4",
-    "@typescript-eslint/eslint-plugin": "^5.39.0",
-    "@typescript-eslint/parser": "^5.39.0",
+    "@typescript-eslint/eslint-plugin": "^5.43.0",
+    "@typescript-eslint/parser": "^5.43.0",
     "@vue/eslint-config-standard": "^8.0.1",
-    "eslint": "^8.22.0",
+    "eslint": "^8.27.0",
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-nuxt": "^4.0.0",
     "eslint-plugin-prettier": "^4.2.1",
-    "eslint-plugin-vue": "^9.6.0",
-    "jest": "^29.0.3",
+    "eslint-plugin-vue": "^9.7.0",
+    "jest": "^29.3.1",
     "prettier": "^2.7.1",
-    "ts-jest": "^29.0.1",
-    "typescript": "^4.8.4",
-    "vitest": "^0.23.4",
+    "ts-jest": "^29.0.3",
+    "typescript": "^4.9.3",
+    "vitest": "^0.25.2",
     "vue-jest": "^3.0.7"
   },
   "dependencies": {
-    "@casl/ability": "^6.0.0",
-    "@casl/vue": "^2.1.3",
-    "@fortawesome/fontawesome-free": "^6.2.0",
+    "@casl/ability": "^6.3.2",
+    "@casl/vue": "^2.2.0",
+    "@fortawesome/fontawesome-free": "^6.2.1",
     "@nuxt/image": "^0.7.1",
-    "@nuxtjs/i18n": "^8.0.0-beta.1",
-    "@nuxtjs/i18n-edge": "npm:@nuxtjs/i18n-edge",
-    "@pinia-orm/nuxt": "^1.0.18",
-    "@pinia/nuxt": "^0.4.0",
+    "@nuxtjs/i18n": "^8.0.0-beta.4",
+    "@nuxtjs/i18n-edge": "8.0.0-beta.4-27808790.0c283db",
+    "@pinia-orm/nuxt": "^1.1.4",
+    "@pinia/nuxt": "^0.4.3",
     "@types/js-yaml": "^4.0.5",
     "@types/vue-the-mask": "^0.11.1",
+    "cleave.js": "^1.6.0",
     "date-fns": "^2.29.3",
     "event-source-polyfill": "^1.0.31",
     "js-yaml": "^4.1.0",
-    "libphonenumber-js": "^1.10.13",
-    "nuxt": "^3.0.0-rc.10",
-    "nuxt-lodash": "^2.3.0",
-    "pinia-orm": "1.0.3",
-    "sass": "^1.54.5",
+    "libphonenumber-js": "^1.10.14",
+    "nuxt": "^3.0.0",
+    "nuxt-lodash": "^2.4.0",
+    "pinia": "^2.0.23",
+    "pinia-orm": "^1.1.4",
+    "sass": "^1.56.1",
     "uuid": "^9.0.0",
     "vue-tel-input-vuetify": "^1.5.3",
     "vue-the-mask": "^0.11.1",
-    "vuetify": "3.0.0-beta.13",
+    "vuetify": "^3.0.1",
     "yaml-import": "^2.0.0"
   }
 }

+ 412 - 403
pages/organization/index.vue

@@ -4,445 +4,450 @@ Contient toutes les informations sur l'organization courante
 -->
 <template>
   <LayoutContainer>
-    <UiForm :model="model" :entity="organization">
-      <template #form.input="{model, entity}">
+    <UiForm v-if="!pending" :model="models().Organization" :entity="organization">
+      <template #form.input="{model, entity: organization}">
         <v-expansion-panels :value="panel" focusable accordion>
           <!-- Description -->
           <UiExpansionPanel id="description" icon="fa-info">
             <v-container fluid class="container">
               <v-row>
                 <v-col cols="12" sm="6">
-                  <UiInputText field="name" :data="entity['name']" @update="updateRepository" :rules="rules().nameRules" />
+                  <UiInputText field="name" v-model="organization.name" :rules="rules.name" />
                 </v-col>
 
                 <v-col cols="12" sm="6">
-                  <UiInputText field="acronym" :data="entity['acronym']" @update="updateRepository" />
+                  <UiInputText field="acronym" v-model="organization.acronym" />
                 </v-col>
 
                 <v-col v-if="organizationProfile.isInsideNetwork()" cols="12" sm="6">
-                  <UiInputText :label="organizationProfile.isCmf() ? 'identifierCmf' : 'identifierFfec'" field="identifier" :data="entity['identifier']" @update="updateRepository" />
+                  <UiInputText
+                      :label="organizationProfile.isCmf() ? 'identifierCmf' : 'identifierFfec'"
+                      field="identifier"
+                      v-model="organization.identifier"
+                  />
                 </v-col>
 
                 <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">
-                  <UiInputText field="ffecApproval" :data="entity['ffecApproval']" @update="updateRepository" />
+                  <UiInputText field="ffecApproval" v-model="organization.ffecApproval"/>
                 </v-col>
 
                 <v-col cols="12" sm="6">
-                  <UiInputText field="description" :data="entity['description']" @update="updateRepository" />
+                  <UiInputText field="description" v-model="organization.description"/>
                 </v-col>
 
                 <v-col cols="12" sm="6">
                   <div>
                     <span>{{ $t('logo') }}</span>
                     <UiHelp right>
-                      <p v-html="$t('logo_upload')" />
+                      <p v-html="$t('logo_upload')"/>
                     </UiHelp>
                   </div>
                   <UiImage
-                    :id="getIdFromUri(entity['logo'])"
+                    :id="getIdFromUri(organization.logo)"
                     :upload="true"
                     :width="200"
                     field="logo"
                     :ownerId="id"
-                    @update="updateRepository"
                   ></UiImage>
                 </v-col>
 
                 <v-col v-if="!organizationProfile.isManagerProduct()" cols="12" sm="6">
-                  <UiInputEnum field="principalType" :data="entity['principalType']" enum-type="organization_principal_type" @update="updateRepository" />
+                  <UiInputEnum field="principalType" v-model="organization.principalType" enum-type="organization_principal_type"/>
                 </v-col>
 
-                <v-col v-if="!organizationProfile.isFfec() && !organizationProfile.isManagerProduct() && !organizationProfile.isArtist()" cols="12" sm="6">
-                  <UiInputEnum field="schoolCategory" :data="entity['schoolCategory']" enum-type="organization_school_cat" @update="updateRepository" />
-                </v-col>
+<!--                <v-col v-if="!organizationProfile.isFfec() && !organizationProfile.isManagerProduct() && !organizationProfile.isArtist()" cols="12" sm="6">-->
+<!--                  <UiInputEnum field="schoolCategory" v-model="organization.schoolCategory" enum-type="organization_school_cat"/>-->
+<!--                </v-col>-->
 
-                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">
-                  <UiInputEnum field="typeEstablishment" :data="entity['typeEstablishment']" enum-type="organization_type_establishment" @update="updateRepository" />
-                </v-col>
+<!--                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">-->
+<!--                  <UiInputEnum field="typeEstablishment" v-model="organization.typeEstablishment" enum-type="organization_type_establishment"/>-->
+<!--                </v-col>-->
 
-                <v-col v-if="entity.typeEstablishment === 'MULTIPLE'" cols="12" sm="6">
-                  <UiInputEnum field="typeEstablishmentDetail" :data="entity['typeEstablishmentDetail']" enum-type="organization_type_establishment_detail" @update="updateRepository" />
-                </v-col>
+<!--                <v-col v-if="organization.typeEstablishment === 'MULTIPLE'" cols="12" sm="6">-->
+<!--                  <UiInputEnum field="typeEstablishmentDetail" v-model="organization.typeEstablishmentDetail" enum-type="organization_type_establishment_detail" />-->
+<!--                </v-col>-->
 
                 <v-col cols="12" sm="6" v-if="organizationProfile.isCmf()">
                   <div class="d-flex flex-row">
-                    <UiInputAutocomplete
-                      field="typeOfPractices"
-                      :items="typeOfPractices"
-                      :isLoading="typeOfPracticesFetchingState.pending"
-                      :item-text="['name']"
-                      :data="getIdsFromUris(entity['typeOfPractices'])"
-                      :translate="true"
-                      :multiple="true"
-                      group="category"
-                      :rules="rules().typeOfPractice"
-                      @update="updateRepository($event.map((id) => `/api/type_of_practices/${id}`), 'typeOfPractices')"
-                      class="flex"
-                    />
+<!--                    <UiInputAutocomplete-->
+<!--                      field="typeOfPractices"-->
+<!--                      :items="typeOfPractices"-->
+<!--                      :isLoading="typeOfPracticesFetchingState.pending"-->
+<!--                      :item-text="['name']"-->
+<!--                      :data="getIdsFromUris(organization.typeOfPractices)"-->
+<!--                      :translate="true"-->
+<!--                      :multiple="true"-->
+<!--                      group="category"-->
+<!--                      :rules="rules.typeOfPractice"-->
+<!--                      @update="updateRepository($event.map((id) => `/api/type_of_practices/${id}`), 'typeOfPractices')"-->
+<!--                      class="flex"-->
+<!--                    />-->
                     <UiHelp>
                       {{ $t('type_of_practices_autocomplete') }}
                     </UiHelp>
                   </div>
                 </v-col>
-                <v-col cols="12" sm="6" v-if="getIdsFromUris(entity['typeOfPractices']).indexOf(37) >= 0">
-                  <UiInputTextArea field="otherPractice" :data="entity['otherPractice']" @update="updateRepository" />
-                </v-col>
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
-
-          <!-- Adresses -->
-          <UiExpansionPanel id="address_postal" icon="fa-globe-europe">
-            <v-container fluid class="container">
-              <v-row>
-                <v-col cols="12" sm="12">
-                  <UiCollection
-                    :model="models().OrganizationAddressPostal"
-                    :parent="organization"
-                    loaderType="image"
-                    newLink="/organization/address/new"
-                  >
-                    <template #list.item="{items}">
-                      <v-container fluid>
-                        <v-row dense>
-                          <v-col
-                            v-for="item in items"
-                            :key="item.id"
-                            cols="4"
-                          >
-                            <UiCard
-                              :id="item.id"
-                              :link="`/organization/address/${item.id}`"
-                              :model="models().OrganizationAddressPostal"
-                            >
-                              <template #card.title>
-                                {{ $t(item.type) }}
-                              </template>
-                              <template #card.text>
-                                {{ item.addressPostal.streetAddress }} <br>
-                                <span v-if="item.addressPostal.streetAddressSecond">{{ item.addressPostal.streetAddressSecond }} <br></span>
-                                <span v-if="item.addressPostal.streetAddressThird">{{ item.addressPostal.streetAddressThird }} <br></span>
-                                {{ item.addressPostal.postalCode }} {{ item.addressPostal.addressCity }}<br>
-                                <span v-if="item.addressPostal.addressCountry">
-                                  <UiItemFromUri
-                                    :model="models().Country"
-                                    :query="repositories().countryRepository.query()"
-                                    :uri="item.addressPostal.addressCountry"
-                                  >
-                                    <template #item.text="{item}">
-                                      {{item.name}}
-                                    </template>
-                                  </UiItemFromUri>
-                                </span>
-                              </template>
-                            </UiCard>
-                          </v-col>
-                        </v-row>
-                      </v-container>
-                    </template>
-                  </UiCollection>
-                </v-col>
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
 
-          <!--  Point de Contact-->
-          <UiExpansionPanel id="contact_point" icon="fa-phone">
-            <v-container class="container">
-              <v-row>
-                <v-col cols="12" sm="12">
-                  <UiCollection
-                    :model="models().ContactPoint"
-                    :parent="organization"
-                    loaderType="image"
-                    newLink="/organization/contact_points/new"
-                  >
-                    <template #list.item="{items}">
-                      <v-container fluid>
-                        <v-row :dense="true">
-                          <v-col
-                            v-for="item in items"
-                            :key="item.id"
-                            cols="4"
-                          >
-                            <UiCard
-                              :id="item.id"
-                              :link="`/organization/contact_points/${item.id}`"
-                              :model="models().ContactPoint"
-                            >
-                              <template #card.title>
-                                {{ $t(item.contactType) }}
-                              </template>
-                              <template #card.text>
-                                <span v-if="item.email"><strong>{{ $t('email') }}</strong> : {{ item.email }} <br></span>
-                                <span v-if="item.emailInvalid" class="ot_danger--text"><v-icon class="ot_danger--text">mdi-alert</v-icon> <strong>{{ $t('emailInvalid') }}</strong> : {{ item.emailInvalid }} <br></span>
-
-                                <span v-if="item.telphone"><strong>{{ $t('telphone') }}</strong> : {{ formatPhoneNumber(item.telphone) }} <br></span>
-                                <span v-if="item.telphoneInvalid" class="ot_danger--text"><v-icon class="ot_danger--text">mdi-alert</v-icon> <strong>{{ $t('telphoneInvalid') }}</strong> : {{ formatPhoneNumber(item.telphoneInvalid) }} <br></span>
-
-                                <span v-if="item.mobilPhone"><strong>{{ $t('mobilPhone') }}</strong> : {{ formatPhoneNumber(item.mobilPhone) }} <br></span>
-                                <span v-if="item.mobilPhoneInvalid" class="ot_danger--text"><v-icon class="ot_danger--text">mdi-alert</v-icon> <strong>{{ $t('mobilPhoneInvalid') }}</strong> : {{ formatPhoneNumber(item.mobilPhoneInvalid) }} </span>
-                              </template>
-                            </UiCard>
-                          </v-col>
-                        </v-row>
-                      </v-container>
-                    </template>
-                  </UiCollection>
-                </v-col>
+                <!-- TODO: essayer de faire une condition plus explicite pour le v-if -->
+<!--                <v-col cols="12" sm="6" v-if="getIdsFromUris(organization.typeOfPractices).indexOf(37) >= 0">-->
+<!--                  <UiInputTextArea field="otherPractice" v-model="organization.otherPractice" />-->
+<!--                </v-col>-->
               </v-row>
             </v-container>
           </UiExpansionPanel>
 
-          <!-- Informations légales -->
-          <UiExpansionPanel id="legalInformation" icon="fa-gavel">
-            <v-container fluid class="container">
-              <v-row>
-                <v-col cols="12" sm="6">
-                  <UiInputText
-                    field="siretNumber"
-                    :data="entity['siretNumber']"
-                    :error="siretError"
-                    :error-message="siretErrorMessage"
-                    :rules="rules().siretRule"
-                    @update="checkSiretHook($event, 'siretNumber', updateRepository)"
-                  />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="apeNumber" :data="entity['apeNumber']" @update="updateRepository" />
-                </v-col>
-
-                <v-col v-if="entity['legalStatus'] === 'ASSOCIATION_LAW_1901'" cols="12" sm="6">
-                  <UiInputText field="waldecNumber" :data="entity['waldecNumber']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputDatePicker field="creationDate" :data="entity['creationDate']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="prefectureName" :data="entity['prefectureName']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="prefectureNumber" :data="entity['prefectureNumber']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputDatePicker field="declarationDate" :data="entity['declarationDate']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="tvaNumber" :data="entity['tvaNumber']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputEnum field="legalStatus" :data="entity['legalStatus']" enum-type="organization_legal" @update="updateRepository" />
-                </v-col>
-
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
-
-          <!--  Agréments -->
-          <UiExpansionPanel id="agrements" icon="fa-certificate">
-            <v-container class="container">
-              <v-row>
-                <v-col cols="12" sm="6">
-                  <UiInputText field="youngApproval" :data="entity['youngApproval']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="trainingApproval" :data="entity['trainingApproval']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="otherApproval" :data="entity['otherApproval']" @update="updateRepository" />
-                </v-col>
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
-
-          <!-- Salariés -->
-          <UiExpansionPanel id="salary" icon="fa-users">
-            <v-container class="container">
-              <v-row>
-                <v-col cols="12" sm="6">
-                  <UiInputText field="collectiveAgreement" :data="entity['collectiveAgreement']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputEnum field="opca" :data="entity['opca']" enum-type="organization_opca" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="icomNumber" :data="entity['icomNumber']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="urssafNumber" :data="entity['urssafNumber']" @update="updateRepository" />
-                </v-col>
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
-
-          <!-- Réseaux -->
-          <UiExpansionPanel v-if="organizationProfile.isInsideNetwork()" id="network" icon="fa-share-alt">
-            <v-container class="container">
-              <v-row>
-                <v-col cols="12" sm="12">
-                  <UiCollection
-                    :model="models().NetworkOrganization"
-                    :parent="organization"
-                    loaderType="text"
-                  >
-                    <template #list.item="{items}">
-                      <div v-for="item in items" :key="item.id">
-                        <span>{{ item.network.name }}</span> - <span>{{$t('first_subscription')}} : <UiTemplateDate :data="item.startDate" /></span>
-                      </div>
-                    </template>
-                  </UiCollection>
-                </v-col>
-                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">
-                  <UiInputText field="budget" :data="entity['budget']" type="number" @update="updateRepository" />
-                </v-col>
-
-                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">
-                  <UiInputCheckbox field="isPedagogicIsPrincipalActivity" :data="entity['isPedagogicIsPrincipalActivity']" @update="updateRepository" />
-                </v-col>
-
-                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">
-                  <UiInputText field="pedagogicBudget" :data="entity['pedagogicBudget']" type="number" @update="updateRepository" />
-                </v-col>
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
-
-          <!-- Communication -->
-          <UiExpansionPanel id="communication" icon="fa-rss">
-            <v-container class="container">
-              <v-row>
-                <v-col cols="12" sm="6">
-                  <UiInputText field="twitter" :data="entity['twitter']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="youtube" :data="entity['youtube']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="facebook" :data="entity['facebook']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputText field="instagram" :data="entity['instagram']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <UiInputCheckbox field="portailVisibility" :data="entity['portailVisibility']" @update="updateRepository" />
-                </v-col>
-
-                <v-col cols="12" sm="6">
-                  <div class="d-flex flex-column">
-                    <UiHelp class="d-flex flex-row">
-                      <span>{{ $t('image') }}</span>
-                      <p v-html="$t('communication_image_upload')" />
-                    </UiHelp>
-                    <UiImage
-                      :id="getIdFromUri(entity['image'])"
-                      :upload="true"
-                      :width="200"
-                      field="image"
-                      :ownerId="id"
-                      @update="updateRepository"
-                    ></UiImage>
-                  </div>
-                </v-col>
-
-                <v-col cols="12" sm="12">
-                  <UiCollection
-                    :model="models().OrganizationArticle"
-                    :parent="organization"
-                    loaderType="text"
-                  >
-                    <template #list.item="{items}">
-                      <h4 class="ot_grey--text font-weight-regular">{{$t('organizationArticle')}}</h4>
-                      <UiTemplateDataTable
-                        :headers="[
-                          { text: $t('title'), value: 'title' },
-                          { text: $t('link'), value: 'link' },
-                          { text: $t('date'), value: 'date' },
-                        ]"
-                        :items="items"
-                      >
-                        <template #item.date="{item}">
-                          <UiTemplateDate :data="item.date" />
-                        </template>
-                      </UiTemplateDataTable>
-                    </template>
-                  </UiCollection>
-                </v-col>
-
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
-
-          <!-- IBAN -->
-          <UiExpansionPanel id="bank_account" icon="fa-euro-sign">
-            <v-container class="container">
-              <v-row>
-                <v-col cols="12" sm="12">
-                  <UiCollection
-                    :model="models().BankAccount"
-                    :parent="organization"
-                    loaderType="image"
-                    newLink="/organization/bank_account/new"
-                  >
-                    <template #list.item="{items}">
-                      <v-container fluid>
-                        <v-row :dense="true">
-                          <v-col
-                            v-for="item in items"
-                            :key="item.id"
-                            cols="4"
-                          >
-                            <UiCard
-                              :id="item.id"
-                              :link="`/organization/bank_account/${item.id}`"
-                              :model="models().BankAccount"
-                            >
-                              <template #card.text>
-                                <span v-if="item.bankName"><strong>{{ $t('bankName') }}</strong> : {{ item.bankName }} <br></span>
-
-                                <span v-if="item.bic"><strong>{{ $t('bic') }}</strong> : {{ item.bic }} <br></span>
-                                <span v-if="item.bicInvalid" class="ot_danger--text"><v-icon class="ot_danger--text">mdi-alert</v-icon> <strong>{{ $t('bicInvalid') }}</strong> : {{ item.bicInvalid }} <br></span>
-
-                                <span v-if="item.iban"><strong>{{ $t('iban') }}</strong> : {{ item.iban }} <br></span>
-                                <span v-if="item.ibanInvalid" class="ot_danger--text"><v-icon class="ot_danger--text">mdi-alert</v-icon> <strong>{{ $t('ibanInvalid') }}</strong> : {{ item.ibanInvalid }} <br></span>
-
-                              </template>
-                            </UiCard>
-                          </v-col>
-                        </v-row>
-                      </v-container>
-                    </template>
-                  </UiCollection>
-                </v-col>
-              </v-row>
-            </v-container>
-          </UiExpansionPanel>
+          <!-- Adresses -->
+<!--          <UiExpansionPanel id="address_postal" icon="fa-globe-europe">-->
+<!--            <v-container fluid class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="12">-->
+<!--                  <UiCollection-->
+<!--                    :model="models().OrganizationAddressPostal"-->
+<!--                    :parent="entity"-->
+<!--                    loaderType="image"-->
+<!--                    newLink="/organization/address/new"-->
+<!--                  >-->
+<!--                    <template #list.item="{items}">-->
+<!--                      <v-container fluid>-->
+<!--                        <v-row dense>-->
+<!--                          <v-col-->
+<!--                            v-for="item in items"-->
+<!--                            :key="item.id"-->
+<!--                            cols="4"-->
+<!--                          >-->
+<!--                            <UiCard-->
+<!--                              :link="`/organization/address/${item.id}`"-->
+<!--                              :model="models().OrganizationAddressPostal"-->
+<!--                              :entity="item"-->
+<!--                            >-->
+<!--                              <template #card.title>-->
+<!--                                {{ $t(item.type) }}-->
+<!--                              </template>-->
+<!--                              <template #card.text>-->
+<!--                                {{ item.addressPostal.streetAddress }} <br>-->
+<!--                                <span v-if="item.addressPostal.streetAddressSecond">{{ item.addressPostal.streetAddressSecond }} <br></span>-->
+<!--                                <span v-if="item.addressPostal.streetAddressThird">{{ item.addressPostal.streetAddressThird }} <br></span>-->
+<!--                                {{ item.addressPostal.postalCode }} {{ item.addressPostal.addressCity }}<br>-->
+<!--                                <span v-if="item.addressPostal.addressCountry">-->
+<!--                                  <UiItemFromUri-->
+<!--                                    :model="models().Country"-->
+<!--                                    :query="repositories().countryRepository.query()"-->
+<!--                                    :uri="item.addressPostal.addressCountry"-->
+<!--                                  >-->
+<!--                                    <template #item.text="{item}">-->
+<!--                                      {{item.name}}-->
+<!--                                    </template>-->
+<!--                                  </UiItemFromUri>-->
+<!--                                </span>-->
+<!--                              </template>-->
+<!--                            </UiCard>-->
+<!--                          </v-col>-->
+<!--                        </v-row>-->
+<!--                      </v-container>-->
+<!--                    </template>-->
+<!--                  </UiCollection>-->
+<!--                </v-col>-->
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
+
+<!--          &lt;!&ndash;  Point de Contact&ndash;&gt;-->
+<!--          <UiExpansionPanel id="contact_point" icon="fa-phone">-->
+<!--            <v-container class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="12">-->
+<!--                  <UiCollection-->
+<!--                    :model="models().ContactPoint"-->
+<!--                    :parent="entity"-->
+<!--                    loaderType="image"-->
+<!--                    newLink="/organization/contact_points/new"-->
+<!--                  >-->
+<!--                    <template #list.item="{items}">-->
+<!--                      <v-container fluid>-->
+<!--                        <v-row :dense="true">-->
+<!--                          <v-col-->
+<!--                            v-for="item in items"-->
+<!--                            :key="item.id"-->
+<!--                            cols="4"-->
+<!--                          >-->
+<!--                            <UiCard-->
+<!--                              :link="`/organization/contact_points/${item.id}`"-->
+<!--                              :model="models().ContactPoint"-->
+<!--                              :entity="item"-->
+<!--                            >-->
+<!--                              <template #card.title>-->
+<!--                                {{ $t(item.contactType) }}-->
+<!--                              </template>-->
+<!--                              <template #card.text>-->
+<!--                                <span v-if="item.email"><strong>{{ $t('email') }}</strong> : {{ item.email }} <br></span>-->
+<!--                                <span v-if="item.emailInvalid" class="ot_danger&#45;&#45;text"><v-icon class="ot_danger&#45;&#45;text">mdi-alert</v-icon> <strong>{{ $t('emailInvalid') }}</strong> : {{ item.emailInvalid }} <br></span>-->
+
+<!--                                <span v-if="item.telphone"><strong>{{ $t('telphone') }}</strong> : {{ formatPhoneNumber(item.telphone) }} <br></span>-->
+<!--                                <span v-if="item.telphoneInvalid" class="ot_danger&#45;&#45;text"><v-icon class="ot_danger&#45;&#45;text">mdi-alert</v-icon> <strong>{{ $t('telphoneInvalid') }}</strong> : {{ formatPhoneNumber(item.telphoneInvalid) }} <br></span>-->
+
+<!--                                <span v-if="item.mobilPhone"><strong>{{ $t('mobilPhone') }}</strong> : {{ formatPhoneNumber(item.mobilPhone) }} <br></span>-->
+<!--                                <span v-if="item.mobilPhoneInvalid" class="ot_danger&#45;&#45;text"><v-icon class="ot_danger&#45;&#45;text">mdi-alert</v-icon> <strong>{{ $t('mobilPhoneInvalid') }}</strong> : {{ formatPhoneNumber(item.mobilPhoneInvalid) }} </span>-->
+<!--                              </template>-->
+<!--                            </UiCard>-->
+<!--                          </v-col>-->
+<!--                        </v-row>-->
+<!--                      </v-container>-->
+<!--                    </template>-->
+<!--                  </UiCollection>-->
+<!--                </v-col>-->
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
+
+<!--          &lt;!&ndash; Informations légales &ndash;&gt;-->
+<!--          <UiExpansionPanel id="legalInformation" icon="fa-gavel">-->
+<!--            <v-container fluid class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText-->
+<!--                    field="siretNumber"-->
+<!--                    :data="entity['siretNumber']"-->
+<!--                    :error="siretError"-->
+<!--                    :error-message="siretErrorMessage"-->
+<!--                    :rules="rules.siretRule"-->
+<!--                    @update="checkSiretHook($event, 'siretNumber')"-->
+<!--                  />-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="apeNumber" :data="entity['apeNumber']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col v-if="entity['legalStatus'] === 'ASSOCIATION_LAW_1901'" cols="12" sm="6">-->
+<!--                  <UiInputText field="waldecNumber" :data="entity['waldecNumber']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputDatePicker field="creationDate" :data="entity['creationDate']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="prefectureName" :data="entity['prefectureName']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="prefectureNumber" :data="entity['prefectureNumber']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputDatePicker field="declarationDate" :data="entity['declarationDate']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="tvaNumber" :data="entity['tvaNumber']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputEnum field="legalStatus" :data="entity['legalStatus']" enum-type="organization_legal"/>-->
+<!--                </v-col>-->
+
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
+
+<!--          &lt;!&ndash;  Agréments &ndash;&gt;-->
+<!--          <UiExpansionPanel id="agrements" icon="fa-certificate">-->
+<!--            <v-container class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="youngApproval" :data="entity['youngApproval']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="trainingApproval" :data="entity['trainingApproval']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="otherApproval" :data="entity['otherApproval']"/>-->
+<!--                </v-col>-->
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
+
+<!--          &lt;!&ndash; Salariés &ndash;&gt;-->
+<!--          <UiExpansionPanel id="salary" icon="fa-users">-->
+<!--            <v-container class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="collectiveAgreement" :data="entity['collectiveAgreement']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputEnum field="opca" :data="entity['opca']" enum-type="organization_opca"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="icomNumber" :data="entity['icomNumber']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="urssafNumber" :data="entity['urssafNumber']"/>-->
+<!--                </v-col>-->
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
+
+<!--          &lt;!&ndash; Réseaux &ndash;&gt;-->
+<!--          <UiExpansionPanel v-if="organizationProfile.isInsideNetwork()" id="network" icon="fa-share-alt">-->
+<!--            <v-container class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="12">-->
+<!--                  <UiCollection-->
+<!--                    :model="models().NetworkOrganization"-->
+<!--                    :parent="entity"-->
+<!--                    loaderType="text"-->
+<!--                  >-->
+<!--                    <template #list.item="{items}">-->
+<!--                      <div v-for="item in items" :key="item.id">-->
+<!--                        <span>{{ item.network.name }}</span> - <span>{{$t('first_subscription')}} : <UiTemplateDate :data="item.startDate" /></span>-->
+<!--                      </div>-->
+<!--                    </template>-->
+<!--                  </UiCollection>-->
+<!--                </v-col>-->
+<!--                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">-->
+<!--                  <UiInputText field="budget" :data="entity['budget']" type="number" />-->
+<!--                </v-col>-->
+
+<!--                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">-->
+<!--                  <UiInputCheckbox field="isPedagogicIsPrincipalActivity" :data="entity['isPedagogicIsPrincipalActivity']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col v-if="organizationProfile.isFfec()" cols="12" sm="6">-->
+<!--                  <UiInputText field="pedagogicBudget" :data="entity['pedagogicBudget']" type="number"/>-->
+<!--                </v-col>-->
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
+
+<!--          &lt;!&ndash; Communication &ndash;&gt;-->
+<!--          <UiExpansionPanel id="communication" icon="fa-rss">-->
+<!--            <v-container class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="twitter" :data="entity['twitter']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="youtube" :data="entity['youtube']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="facebook" :data="entity['facebook']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputText field="instagram" :data="entity['instagram']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <UiInputCheckbox field="portailVisibility" :data="entity['portailVisibility']"/>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="6">-->
+<!--                  <div class="d-flex flex-column">-->
+<!--                    <UiHelp class="d-flex flex-row">-->
+<!--                      <span>{{ $t('image') }}</span>-->
+<!--                      <p v-html="$t('communication_image_upload')"/>-->
+<!--                    </UiHelp>-->
+<!--                    <UiImage-->
+<!--                      :id="getIdFromUri(entity['image'])"-->
+<!--                      :upload="true"-->
+<!--                      :width="200"-->
+<!--                      field="image"-->
+<!--                      :ownerId="id"-->
+
+<!--                    ></UiImage>-->
+<!--                  </div>-->
+<!--                </v-col>-->
+
+<!--                <v-col cols="12" sm="12">-->
+<!--                  <UiCollection-->
+<!--                    :model="models().OrganizationArticle"-->
+<!--                    :parent="entity"-->
+<!--                    loaderType="text"-->
+<!--                  >-->
+<!--                    <template #list.item="{items}">-->
+<!--                      <h4 class="ot_grey&#45;&#45;text font-weight-regular">{{$t('organizationArticle')}}</h4>-->
+<!--                      <UiTemplateDataTable-->
+<!--                        :headers="[-->
+<!--                          { text: $t('title'), value: 'title' },-->
+<!--                          { text: $t('link'), value: 'link' },-->
+<!--                          { text: $t('date'), value: 'date' },-->
+<!--                        ]"-->
+<!--                        :items="items"-->
+<!--                      >-->
+<!--                        <template #item.date="{item}">-->
+<!--                          <UiTemplateDate :data="item.date" />-->
+<!--                        </template>-->
+<!--                      </UiTemplateDataTable>-->
+<!--                    </template>-->
+<!--                  </UiCollection>-->
+<!--                </v-col>-->
+
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
+
+<!--          &lt;!&ndash; IBAN &ndash;&gt;-->
+<!--          <UiExpansionPanel id="bank_account" icon="fa-euro-sign">-->
+<!--            <v-container class="container">-->
+<!--              <v-row>-->
+<!--                <v-col cols="12" sm="12">-->
+<!--                  <UiCollection-->
+<!--                    :model="models().BankAccount"-->
+<!--                    :parent="entity"-->
+<!--                    loaderType="image"-->
+<!--                    newLink="/organization/bank_account/new"-->
+<!--                  >-->
+<!--                    <template #list.item="{items}">-->
+<!--                      <v-container fluid>-->
+<!--                        <v-row :dense="true">-->
+<!--                          <v-col-->
+<!--                            v-for="item in items"-->
+<!--                            :key="item.id"-->
+<!--                            cols="4"-->
+<!--                          >-->
+<!--                            <UiCard-->
+<!--                              :id="item.id"-->
+<!--                              :link="`/organization/bank_account/${item.id}`"-->
+<!--                              :model="models().BankAccount"-->
+<!--                            >-->
+<!--                              <template #card.text>-->
+<!--                                <span v-if="item.bankName"><strong>{{ $t('bankName') }}</strong> : {{ item.bankName }} <br></span>-->
+
+<!--                                <span v-if="item.bic"><strong>{{ $t('bic') }}</strong> : {{ item.bic }} <br></span>-->
+<!--                                <span v-if="item.bicInvalid" class="ot_danger&#45;&#45;text"><v-icon class="ot_danger&#45;&#45;text">mdi-alert</v-icon> <strong>{{ $t('bicInvalid') }}</strong> : {{ item.bicInvalid }} <br></span>-->
+
+<!--                                <span v-if="item.iban"><strong>{{ $t('iban') }}</strong> : {{ item.iban }} <br></span>-->
+<!--                                <span v-if="item.ibanInvalid" class="ot_danger&#45;&#45;text"><v-icon class="ot_danger&#45;&#45;text">mdi-alert</v-icon> <strong>{{ $t('ibanInvalid') }}</strong> : {{ item.ibanInvalid }} <br></span>-->
+
+<!--                              </template>-->
+<!--                            </UiCard>-->
+<!--                          </v-col>-->
+<!--                        </v-row>-->
+<!--                      </v-container>-->
+<!--                    </template>-->
+<!--                  </UiCollection>-->
+<!--                </v-col>-->
+<!--              </v-row>-->
+<!--            </v-container>-->
+<!--          </UiExpansionPanel>-->
         </v-expansion-panels>
       </template>
     </UiForm>
   </LayoutContainer>
 </template>
 
-<script lang="ts">
+<script setup lang="ts">
 
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {TypeOfPractice} from "~/models/Organization/TypeOfPractice";
-import {reactive, ref} from "@vue/reactivity";
+import {computed, ComputedRef, reactive, ref} from "@vue/reactivity";
 import {$organizationProfile} from "~/services/profile/organizationProfile";
 import {useProfileOrganizationStore} from "~/store/profile/organization";
 import {useExtensionPanel} from "~/composables/layout/useExtensionPanel";
@@ -458,32 +463,33 @@ import {OrganizationAddressPostal} from "~/models/Organization/OrganizationAddre
 import {Country} from "~/models/Core/Country";
 import {NetworkOrganization} from "~/models/Network/NetworkOrganization";
 import {OrganizationArticle} from "~/models/Organization/OrganizationArticle";
+import {useI18n} from "vue-i18n";
 
 const id: number | null = useProfileOrganizationStore().id
 if (id === null) {
   throw new Error('Missing organization id')
 }
 
-const { em } = useEntityManager()
-
 const organizationProfile = reactive($organizationProfile())
 
+const { em } = useEntityManager()
 const { fetch, fetchCollection } = useEntityFetch()
+const { pending } = fetch(Organization, id)
 
-const model = Organization
-const { data: organization, pending: organizationPending } = fetch(Organization, id)
+const organization: ComputedRef<Organization> = computed(() => {
+  return em.find(Organization, id)
+})
 
 const { data: typeOfPractices, pending: typeOfPracticesPending } = fetchCollection(TypeOfPractice)
 
 const route = ref(useRoute())
 const { panel } = useExtensionPanel(route)
 
-
 const { siretError, siretErrorMessage, validateSiret } = useValidation().useValidateSiret()
 
 const validateSiretHook = async (siret: string, field: string, updateRepository: any) => {
   await validateSiret(siret)
-  if (organization.value !== null && !siretError.value) {
+  if (!siretError.value) {
     em.save(Organization, organization.value)
   }
 }
@@ -492,6 +498,7 @@ const formatPhoneNumber = (number: string): string => {
   return useI18nUtils().formatPhoneNumber(number)
 }
 
+// TODO: voir si l'extraction de cette id ne pourrait pas être faite en amont, au niveau des post-processors
 const getIdsFromUris = (uris: Array<string>) => {
   const ids:Array<any> = []
   for(const uri of uris){
@@ -500,27 +507,9 @@ const getIdsFromUris = (uris: Array<string>) => {
   return ids
 }
 
+// TODO: voir si l'extraction de cette id ne pourrait pas être faite en amont, au niveau des post-processors
 const getIdFromUri = (uri: string) => Url.extractIdFromUri(uri)
 
-function getValidationRules (i18n: any, $organizationProfile:any) {
-  return {
-    nameRules: [
-      (nameValue: string) => !!nameValue || i18n.t('required'),
-      (nameValue: string) => (nameValue || '').length <= 128 || i18n.t('name_length_rule')
-    ],
-    siretRule: [
-      (siretValue: string) => /^([0-9]{9}|[0-9]{14})$/.test(siretValue) || i18n.t('siret_error')
-    ],
-    typeOfPractice: [
-      (typeOfPracticeValue: Array<number>) => {
-        if(!$organizationProfile.isManagerProduct())
-          return typeOfPracticeValue.length > 0 || i18n.t('required')
-        return true
-      }
-    ]
-  }
-}
-
 const models = () => {
   return {
     Organization,
@@ -532,6 +521,26 @@ const models = () => {
     OrganizationArticle
   }
 }
+
+const i18n = useI18n()
+
+const rules = {
+  name: [
+    (nameValue: string) => !!nameValue || i18n.t('required'),
+    (nameValue: string) => (nameValue || '').length <= 128 || i18n.t('name_length_rule')
+  ],
+  siret: [
+    (siretValue: string) => /^([0-9]{9}|[0-9]{14})$/.test(siretValue) || i18n.t('siret_error')
+  ],
+  typeOfPractice: [
+    (typeOfPracticeValue: Array<number>) => {
+      if(!$organizationProfile().isManagerProduct())
+      return typeOfPracticeValue.length > 0 || i18n.t('required')
+      return true
+    }
+  ]
+}
+
 </script>
 
 <style scoped>

+ 45 - 18
pages/poc/[id].vue

@@ -5,20 +5,25 @@
         Pending...
       </div>
       <div v-else>
+        <h1>{{ $t('download') }}</h1>
         <p>Edit :{{ file }}</p>
 
-        <form @submit.prevent="" @change="onFileChange" class="my-3">
-          <v-text-field v-model="file.name" type="text" />
+        <UiForm v-if="!pending" :model="File" :entity="file">
+          <template #form.input="{model, entity: file}">
 
-          <v-select v-model="file.status" :items="['PENDING', 'READY', 'DELETED', 'ERROR']">
-          </v-select>
+            <UiInputText field="name" v-model="file.name" type="text" :rules="rules.nameRules" />
+            <UiInputEnum field="status" v-model="file.status" enum="file_status" />
 
-          <v-btn @click="cancel" class="ma-5">Annuler</v-btn>
-          <v-btn @click="save" class="ma-5">Enregistrer</v-btn>
-          <v-btn @click="deleteAndGoBack" class="ma-5">Supprimer</v-btn>
-          <v-btn @click="refresh" class="ma-5">Refresh</v-btn>
-          <v-btn @click="goBack" class="ma-5">Retour</v-btn>
-        </form>
+            <v-btn @click="cancel" class="ma-5">Annuler</v-btn>
+            <v-btn @click="save" class="ma-5">Enregistrer</v-btn>
+            <v-btn @click="deleteAndGoBack" class="ma-5">Supprimer</v-btn>
+            <v-btn @click="refresh" class="ma-5">Refresh</v-btn>
+            <v-btn @click="goBack" class="ma-5">Retour</v-btn>
+            <v-btn to="/poc/display" class="ma-5">Détails</v-btn>
+          </template>
+        </UiForm>
+
+        <v-btn to="/poc"></v-btn>
       </div>
     </div>
   </main>
@@ -28,32 +33,51 @@
 import {useEntityManager} from "~/composables/data/useEntityManager";
 import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
 import {File} from "~/models/Core/File";
-import {navigateTo, useRoute} from "#app";
+import {navigateTo, useNuxtApp, useRoute} from "#app";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
+import {useI18n} from "vue-i18n";
 
 const route = useRoute()
 
 const id: Ref<number> = ref(parseInt(route.params.id as string))
 
-const em = useEntityManager()
+const { em }= useEntityManager()
+
+const { fetch } = useEntityFetch()
 
-const { pending, refresh } = useEntityFetch(File, id)
+const { data: file, pending, refresh } = fetch(File, id.value)
 
 // Get file from store
-const file: ComputedRef<File> = computed( () => {
-  return em.find(File, id.value)
-})
+// const file: ComputedRef<File> = computed( () => {
+//   return em.find(File, id.value)
+// })
 
 // Update store when form is changed
-const onFileChange = () => {
-  em.save(File, file.value)
+// const onFileChange = () => {
+//   console.log(file.value)
+//   em.save(File, file.value)
+// }
+
+const i18n = useI18n()
+const rules = {
+  nameRules: [
+    (nameValue: string) => !!nameValue || i18n.t('required'),
+    (nameValue: string) => (nameValue || '').length <= 128 || i18n.t('name_length_rule'),
+    (nameValue: string) => (nameValue || '').length >= 2 || i18n.t('name_length_rule'),
+  ]
 }
 
 const save = async () => {
+  if (file.value === null) {
+    throw new Error('File not found')
+  }
   await em.persist(File, file.value)
 }
 
 const cancel = async () => {
+  if (file.value === null) {
+    throw new Error('File not found')
+  }
   if (em.isNewEntity(File, id.value)) {
     await em.delete(File, file.value)
   } else {
@@ -62,6 +86,9 @@ const cancel = async () => {
 }
 
 const deleteItem = async () => {
+  if (file.value === null) {
+    throw new Error('File not found')
+  }
   await em.delete(File, file.value)
 }
 

+ 54 - 0
pages/poc/display.vue

@@ -0,0 +1,54 @@
+<template>
+  <main>
+    <div>
+      <div v-if="pending">
+        Pending...
+      </div>
+      <div v-else>
+        {{ file.name }}
+
+        <div class="ma-3">
+          <p><nuxt-link to="/poc/1">Edit</nuxt-link></p>
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script setup lang="ts">
+
+
+import {useEntityManager} from "~/composables/data/useEntityManager";
+import {useEntityFetch} from "~/composables/data/useEntityFetch";
+import {File} from "~/models/Core/File";
+
+const { em }= useEntityManager()
+
+const { fetch } = useEntityFetch()
+
+const { data: file, pending, refresh } = fetch(File, 1)
+
+</script>
+
+<style>
+a {
+  color: blue;
+  cursor: pointer;
+}
+a:hover {
+  text-decoration: underline;
+}
+
+button {
+  border: grey solid 1px;
+  padding: 5px;
+  margin: 5px;
+  cursor: pointer;
+}
+button:hover {
+  text-decoration: underline;
+}
+button:focus {
+  background-color: lightgrey;
+}
+</style>

+ 3 - 0
pages/poc/i18n.vue

@@ -0,0 +1,3 @@
+<template>
+  <h1>{{ $t('download') }}</h1>
+</template>

+ 16 - 14
pages/poc/index.vue

@@ -11,14 +11,15 @@
           </li>
         </ul>
         <div class="ma-3">{{ totalItems }} results</div>
-        <div class="ma-3">
-          <button @click="goToPreviousPage" class="mr-3">Previous page ({{ pagination.previous }})</button>
-          <span class="mx-3">Page : {{ page }}</span>
-          <button @click="goToNextPage">Next page ({{ pagination.next }})</button>
-          <span class="mx-2"> (Last page : {{ pagination.last }})</span>
-        </div>
+<!--        <div class="ma-3">-->
+<!--          <button @click="goToPreviousPage" class="mr-3">Previous page ({{ pagination.previous }})</button>-->
+<!--          <span class="mx-3">Page : {{ page }}</span>-->
+<!--          <button @click="goToNextPage">Next page ({{ pagination.next }})</button>-->
+<!--          <span class="mx-2"> (Last page : {{ pagination.last }})</span>-->
+<!--        </div>-->
         <div class="ma-3">
           <nuxt-link to="/poc/new">Create</nuxt-link>
+          <p><nuxt-link to="/poc/1">Goto 1</nuxt-link></p>
         </div>
       </div>
     </div>
@@ -27,17 +28,23 @@
 
 <script setup lang="ts">
   import {useEntityManager} from "~/composables/data/useEntityManager";
-  import {computed, ComputedRef, Ref} from "@vue/reactivity";
+  import {computed, ComputedRef, ref, Ref} from "@vue/reactivity";
   import ApiResource from "~/models/ApiResource";
   import {File} from "~/models/Core/File";
   import {Collection, Pagination} from "~/types/data";
   import {useLazyAsyncData} from "#app";
+  import {useEntityFetch} from "~/composables/data/useEntityFetch";
 
-  const em = useEntityManager()
-
+  const { em } = useEntityManager()
 
   const page: Ref<number> = ref(1)
 
+  let { data: collection, pending, refresh } = useLazyAsyncData(
+      'files',
+      () => em.fetchCollection(File, null, { page: page.value })
+  )
+  console.log(collection.value)
+
   const totalItems: ComputedRef<number | undefined> = computed(() => collection.value?.totalItems)
 
   const pagination: ComputedRef<Pagination | undefined> = computed(() => {
@@ -48,11 +55,6 @@
     return collection.value?.items
   })
 
-  let { data: collection, pending, refresh } = useLazyAsyncData(
-      'files',
-      () => em.fetchAll(File, page.value)
-  )
-
   const goToPreviousPage = async function () {
     if (page.value > 1) {
       page.value--

+ 2 - 2
plugins/ability.ts

@@ -1,7 +1,7 @@
-import { Ability } from '@casl/ability'
+import { createMongoAbility } from '@casl/ability'
 import { $abilitiesUtils } from '~/services/rights/abilitiesUtils'
 
-export const ability = new Ability()
+export const ability = createMongoAbility()
 export default defineNuxtPlugin(() => {
     $abilitiesUtils(ability).setAbilities()
 })

+ 2 - 3
plugins/casl.ts

@@ -1,8 +1,7 @@
 import { abilitiesPlugin } from '@casl/vue';
 import { ability } from '@/plugins/ability'
+import { defineNuxtPlugin } from "nuxt/app";
 
 export default defineNuxtPlugin((nuxtApp) => {
-    nuxtApp.vueApp.use(abilitiesPlugin,ability, {
-            useGlobalProperties: true
-    })
+    nuxtApp.vueApp.use(abilitiesPlugin, ability, { useGlobalProperties: true })
 })

+ 22 - 0
plugins/cleave.ts

@@ -0,0 +1,22 @@
+// @ts-ignore
+import Cleave from 'cleave.js';
+import {defineNuxtPlugin} from "nuxt/app";
+
+/** Intégration de la librairie cleave.js
+ * @see https://github.com/nosir/cleave.js
+ * @see https://github.com/nosir/cleave.js/blob/master/doc/vue.md
+ */
+export default defineNuxtPlugin(nuxtApp => {
+    nuxtApp.vueApp.directive('cleave', {
+        mounted: (el: any, binding: any) => {
+            el.cleave = new Cleave(el, binding.value ?? {})
+        },
+        updated: (el: any) => {
+            const event = new Event('input', {bubbles: true});
+            setTimeout(function () {
+                el.value = el.cleave.properties.result
+                el.dispatchEvent(event)
+            }, 100);
+        }
+    })
+})

+ 24 - 6
plugins/init.server.ts

@@ -1,17 +1,35 @@
 import {defineNuxtPlugin, useCookie} from "nuxt/app";
 import {useProfileAccessStore} from "~/store/profile/access";
 import {useEntityManager} from "~/composables/data/useEntityManager";
+import UnauthorizedError from "~/services/error/UnauthorizedError";
+import {useRedirectToLogin} from "~/composables/layout/useRedirectToLogin";
 
 export default defineNuxtPlugin(async ({ssrContext}) => {
+    const redirectToLogin = useRedirectToLogin()
+
     const bearer = useCookie('BEARER')
     const accessId = useCookie('AccessId')
 
     const profileAccessStore = useProfileAccessStore()
-    profileAccessStore.$patch({
-        bearer: bearer.value,
-        id: parseInt(accessId.value)
-    })
 
-    const { em } = useEntityManager()
-    await em.refreshProfile()
+    if (accessId.value !== null) {
+        profileAccessStore.$patch({
+            bearer: bearer.value,
+            id: parseInt(accessId.value)
+        })
+    } else {
+        redirectToLogin()
+    }
+
+    const {em} = useEntityManager()
+
+    try {
+        await em.refreshProfile()
+    } catch (error) {
+        if (error instanceof UnauthorizedError) {
+            redirectToLogin()
+        } else {
+            throw error
+        }
+    }
 })

+ 0 - 0
assets/images/byDefault/men-1.png → public/images/default/men-1.png


+ 0 - 0
assets/images/byDefault/default_pic.jpeg → public/images/default/picture.jpeg


+ 0 - 0
assets/images/byDefault/women-1.png → public/images/default/women-1.png


+ 1 - 0
services/data/entityManager.ts

@@ -9,6 +9,7 @@ import ApiResource from "~/models/ApiResource";
 import {MyProfile} from "~/models/Access/MyProfile";
 import { v4 as uuid4 } from 'uuid';
 import {AssociativeArray, Collection} from "~/types/data.d";
+import {useCloneDeep} from "#imports";
 
 /**
  * Entity manager: make operations on the models defined with the Pinia-Orm library

+ 3 - 2
services/data/imageManager.ts

@@ -11,12 +11,13 @@ class ImageManager {
 
     public async get(id: number, height: number = 0, width: number = 0) {
 
-        let url = `api/files/${id}/download`
+        let url = `api/download/${id}`
 
         // Set requested size if needed
         if (height > 0 || width > 0) {
             // @see https://thumbor.readthedocs.io/en/latest/crop_and_resize_algorithms.html
-            url = Url.join(url, `${height}x${width}`)
+            // TODO: ajouter le support de ces options dans ap2i
+            // url = Url.join(url, `${height}x${width}`)
         }
 
         // Une image doit toujours avoir le time en options pour éviter les problème de cache

+ 6 - 0
services/error/UnauthorizedError.ts

@@ -0,0 +1,6 @@
+
+export default class UnauthorizedError extends Error {
+    constructor(message: string = "Unauthorized", ...args: any[]) {
+        super(message, ...args);
+    }
+}

+ 1 - 1
store/profile/organization.ts

@@ -1,5 +1,5 @@
 import { baseOrganizationState, organizationState } from '~/types/interfaces'
-import {defineStore} from "pinia";
+import { defineStore } from "pinia";
 
 export const useProfileOrganizationStore = defineStore('profileOrganization', {
   state: (): organizationState => {

+ 3 - 1
tsconfig.json

@@ -7,7 +7,9 @@
     "sourceMap": true,
     "experimentalDecorators": true,
     "types": [
-      "@nuxtjs/date-fns"
+      "@nuxtjs/date-fns",
+      "@nuxt/types",
+      "@nuxtjs/i18n"
     ],
     "exclude": [
       "node_modules",