فهرست منبع

various style fixes, add the ParametersTable component (ongoing)

Olivier Massot 10 ماه پیش
والد
کامیت
cb33409846

+ 211 - 0
components/Layout/Parameters/Table.vue

@@ -0,0 +1,211 @@
+<template>
+  <div>
+    <UiLoadingPanel v-if="pending" />
+    <v-table v-else>
+      <thead>
+        <tr>
+          <td v-for="(_, i) in actualColumnProps">
+            {{ actualColumnLabels[i] ?? '' }}
+          </td>
+          <td>{{ $t('actions') }}</td>
+        </tr>
+      </thead>
+      <tbody v-if="items.value">
+        <tr
+          v-for="item in items.value"
+          :key="item.id"
+        >
+          <td
+            v-for="prop in actualColumnProps"
+            class="cycle-editable-cell"
+          >
+            {{ item[prop] }}
+          </td>
+
+          <td class="d-flex flex-row">
+            <v-btn
+              v-if="actions.includes(TABLE_ACTION.EDIT)"
+              :flat="true"
+              icon="fa fa-pen"
+              class="cycle-edit-icon mr-3"
+              @click="goToEditPage(item.id as number)"
+            />
+            <UiButtonDelete
+              v-if="actions.includes(TABLE_ACTION.DELETE)"
+              :entity="item"
+              :flat="true"
+            />
+          </td>
+        </tr>
+      </tbody>
+      <tbody v-else>
+        <tr class="theme-neutral">
+          <td>
+            <i>{{ $t('nothing_to_show') }}</i>
+          </td>
+          <td></td>
+        </tr>
+      </tbody>
+    </v-table>
+    <v-btn
+      v-if="actions.includes(TABLE_ACTION.ADD)"
+      :flat="true"
+      prepend-icon="fa fa-plus"
+      class="theme-primary mt-4"
+      @click="goToCreatePage"
+    >
+      {{ $t('add') }}
+    </v-btn>
+  </div>
+</template>
+
+<script setup lang="ts">
+
+import {TABLE_ACTION} from '~/types/enum/enums';
+import UrlUtils from '~/services/utils/urlUtils';
+import type ApiResource from '~/models/ApiResource';
+import {useEntityFetch} from '~/composables/data/useEntityFetch';
+
+const props = defineProps({
+  /**
+   * The model whom entities shall be to fetch
+   */
+  model: {
+    type: Object as PropType<typeof ApiResource>,
+    required: true
+  },
+  /**
+   * The base URL for the edit / create pages
+   * The resulting url will be constructed this way :
+   *
+   * Edition : {baseUrl}/{apiResource.entity}/{id}
+   * Creation : {baseUrl}/{apiResource.entity}/new
+   */
+  baseRoute: {
+    type: String,
+    required: false,
+    default: 'parameters'
+  },
+  /**
+   * If provided, limit the properties shown in the table to this list.
+   * Else, all the props are shown.
+   *
+   * Ex: ['id', 'name']
+   */
+  columnsProps: {
+    type: Array as PropType<Array<string>>,
+    required: false
+  },
+  /**
+   * If provided, add labels to the columns
+   *
+   * Ex: ['Identifier', 'Full Name']
+   *
+   * If not provided, a translation of the entity's props names will be looked for.
+   * If none is found, no title will be displayed.
+   */
+  columnsLabels: {
+    type: Array as PropType<Array<string>>,
+    required: false
+  },
+  /**
+   * List of the actions available for each record
+   */
+  actions: {
+    type: Array as PropType<Array<TABLE_ACTION>>,
+    required: false,
+    default: [TABLE_ACTION.EDIT, TABLE_ACTION.DELETE, TABLE_ACTION.ADD]
+  },
+  /**
+   * If provided, sort the record by the given property
+   */
+  sortBy: {
+    type: String,
+    required: false,
+    default: 'id'
+  }
+})
+
+const i18n = useI18n()
+
+const { fetchCollection } = useEntityFetch()
+
+const {
+  data: collection,
+  pending,
+} = fetchCollection(props.model)
+
+/**
+ * Return the properties to display in the table, or all the
+ * props of the model if not specified.
+ */
+const actualColumnProps = computed(() => {
+  if (props.columnsProps) {
+    return props.columnsProps
+  }
+  return Object.getOwnPropertyNames(new props.model())
+})
+
+/**
+ * Return the labels for the columns if provided, or try to
+ * find a translation for the property name if not specified.
+ */
+const actualColumnLabels: ComputedRef<string> = computed(() => {
+  if (props.columnsLabels) {
+    return props.columnsLabels
+  }
+  return actualColumnProps.value.map(prop => {
+    return i18n.t(prop) ?? ''
+  })
+})
+
+/**
+ * Fetch the collection of ApiResources of the given model, then
+ * map it according to the configuration.
+ */
+const items: ComputedRef<Array<object>> = computed(() => {
+  if (pending.value) {
+    return null
+  }
+
+  const items: Ref<object[]> = ref(collection.value.items)
+
+  if (props.columnsProps) {
+    items.value = items.value.map(item => {
+      const res = {}
+      for (const prop of props.columnsProps) {
+        res[prop] = item[prop]
+      }
+      return res
+    })
+  }
+
+  if (props.sortBy) {
+    items.value = items.value.sort((a, b) => {
+      return a[props.sortBy] > b[props.sortBy] ? 1 : -1
+    })
+  }
+
+  return items
+})
+
+/**
+ * Redirect to the edition page for the given item
+ * @param id
+ */
+const goToEditPage = (id: number) => {
+  navigateTo(UrlUtils.join(props.baseRoute, props.model.entity, id))
+}
+
+/**
+ * Redirect to the creation page for this model
+ */
+const goToCreatePage = () => {
+  navigateTo(UrlUtils.join(props.baseRoute, props.model.entity, 'new'))
+}
+
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 7 - 2
components/Ui/DatePicker.vue

@@ -6,6 +6,10 @@ Sélecteur de dates avec Vuetify
 
 <template>
   <v-layout row wrap>
+    <!--
+    TODO: remplacer par <v-date-input> quand celui ci ne sera plus expérimental
+    (@see https://vuetifyjs.com/en/components/date-inputs)
+    -->
     <v-menu
       v-model="menu"
       :close-on-content-click="false"
@@ -24,6 +28,9 @@ Sélecteur de dates avec Vuetify
           :label="label"
           :readonly="true"
           v-bind="attrs"
+          prepend-inner-icon="far fa-calendar"
+          variant="outlined"
+          density="compact"
         />
       </template>
 
@@ -77,8 +84,6 @@ const displayDate = computed({
         year: 'numeric',
         month: '2-digit',
         day: '2-digit',
-        hour: props.withTime ? '2-digit' : undefined,
-        minute: props.withTime ? '2-digit' : undefined,
       }).format(props.modelValue)
     }
     return props.modelValue.toLocaleDateString(i18n.locale.value)

+ 9 - 6
components/Ui/Form.vue

@@ -34,6 +34,7 @@ de quitter si des données ont été modifiées.
           </v-col>
         </v-row>
       </v-container>
+      <div v-else class="mt-12" />
 
       <!-- Content -->
       <slot v-bind="{ model, entity }" />
@@ -72,17 +73,17 @@ de quitter si des données ont été modifiées.
 
       <template #dialogBtn>
         <div class="confirmation-dlg-actions">
-          <v-btn class="theme-primary" @click="closeConfirmationDialog">
+          <v-btn class="theme-neutral" @click="closeConfirmationDialog">
             {{ $t('cancel') }}
           </v-btn>
 
-          <v-btn class="theme-primary" @click="saveAndQuit">
-            {{ $t('save_and_quit') }}
-          </v-btn>
-
           <v-btn class="theme-danger" @click="cancel">
             {{ $t('quit_with_no_saving') }}
           </v-btn>
+
+          <v-btn class="theme-primary" @click="saveAndQuit">
+            {{ $t('save_and_quit') }}
+          </v-btn>
         </div>
       </template>
     </LazyLayoutDialog>
@@ -161,7 +162,7 @@ const props = defineProps({
   actionPosition: {
     type: String as PropType<'top' | 'bottom' | 'both'>,
     required: false,
-    default: 'both',
+    default: 'bottom',
   },
 })
 
@@ -425,6 +426,8 @@ defineExpose({ validate })
   min-width: 255px;
   max-width: 255px;
   margin: 0 8px;
+  font-size: 13px;
+  font-weight: 600;
 }
 
 @media (max-width: 960px) {

+ 4 - 4
components/Ui/Input/Autocomplete.vue

@@ -20,7 +20,7 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
       :loading="isLoading"
       :return-object="returnObject"
       :search-input.sync="search"
-      :prepend-icon="prependIcon"
+      :prepend-inner-icon="prependInnerIcon"
       :error="error || !!fieldViolations"
       :error-messages="
         errorMessage || fieldViolations ? $t(fieldViolations) : ''
@@ -131,9 +131,9 @@ const props = defineProps({
   },
   /**
    * Icône de gauche
-   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-prepend-icon
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-prepend-inner-icon
    */
-  prependIcon: {
+  prependInnerIcon: {
     type: String,
   },
   /**
@@ -232,7 +232,7 @@ const props = defineProps({
       | undefined
     >,
     required: false,
-    default: 'filled',
+    default: 'outlined',
   },
 })
 

+ 2 - 2
components/Ui/Input/Autocomplete/Accesses.vue

@@ -17,7 +17,7 @@ Champs autocomplete dédié à la recherche des access d'une structure
       hide-no-data
       :chips="chips"
       :auto-select-first="false"
-      prependIcon="fas fa-magnifying-glass"
+      prepend-inner-icon="fas fa-magnifying-glass"
       :return-object="false"
       :variant="variant"
       @update:model-value="onUpdateModelValue"
@@ -119,7 +119,7 @@ const props = defineProps({
       | undefined
     >,
     required: false,
-    default: 'filled',
+    default: 'outlined',
   },
 })
 

+ 1 - 1
components/Ui/Input/AutocompleteWithEnum.vue

@@ -53,7 +53,7 @@ const props = defineProps({
       | undefined
     >,
     required: false,
-    default: 'filled',
+    default: 'outlined',
   },
 })
 

+ 1 - 1
components/Ui/Input/Number.vue

@@ -80,7 +80,7 @@ const props = defineProps({
       | undefined
     >,
     required: false,
-    default: 'filled',
+    default: 'outlined',
   },
 })
 

+ 4 - 1
i18n/lang/fr.json

@@ -701,5 +701,8 @@
   "no_admin_access_recorded": "Aucun compte super-admin enregistré",
   "redirecting": "Redirection en cours",
   "Invalid profile hash": "Le profil de l'utilisateur a été modifié ailleurs, veuillez rafraichir la page et réessayer.",
-  "An error occured": "Une erreur s'est produite."
+  "An error occured": "Une erreur s'est produite.",
+  "teachers": "Professeurs",
+  "pupils-members": "Élèves / Adhérents / Membres",
+  "id": "Id"
 }

+ 213 - 197
nuxt.config.ts

@@ -30,200 +30,216 @@ if (process.env.NUXT_ENV === 'dev') {
  * @see https://nuxt.com/docs/api/configuration/nuxt-config
  */
 export default defineNuxtConfig({
-  ssr: true,
-  experimental: {
-    // Fix the 'Cannot stringify non POJO' bug
-    // @see https://github.com/nuxt/nuxt/issues/20787
-    renderJsonPayloads: false,
-  },
-  runtimeConfig: {
-    // Private config that is only available on the server
-    env: '',
-    baseUrl: '',
-    baseUrlLegacy: '',
-    baseUrlAdminLegacy: '',
-    baseUrlTypo3: '',
-    baseUrlMercure: '',
-    fileStorageBaseUrl: '',
-    supportUrl: '',
-    basicomptaUrl: 'https://app.basicompta.fr/',
-    // Config within public will be also exposed to the client
-    public: {
-      env: '',
-      baseUrl: '',
-      baseUrlLegacy: '',
-      baseUrlAdminLegacy: '',
-      baseUrlTypo3: '',
-      baseUrlMercure: '',
-      fileStorageBaseUrl: '',
-      supportUrl: '',
-      basicomptaUrl: 'https://app.basicompta.fr/',
-    },
-  },
-  hooks: {
-    'builder:watch': console.log,
-  },
-  app: {
-    head: {
-      title: 'Opentalent',
-      meta: [
-        { charset: 'utf-8' },
-        { name: 'viewport', content: 'width=device-width, initial-scale=1' },
-        { name: 'msapplication-TileColor', content: '#324250' },
-        {
-          name: 'msapplication-TileImage',
-          content: '/favicon/favicon-144x144.png',
-        },
-      ],
-      link: [
-        { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
-        {
-          rel: 'apple-touch-icon-precomposed',
-          sizes: '57x57',
-          href: '/favicon/apple-touch-icon-57x57.png',
-        },
-        {
-          rel: 'apple-touch-icon-precomposed',
-          sizes: '114x114',
-          href: '/favicon/apple-touch-icon-114x114.png',
-        },
-        {
-          rel: 'apple-touch-icon-precomposed',
-          sizes: '72x72',
-          href: '/favicon/apple-touch-icon-72x72.png',
-        },
-        {
-          rel: 'apple-touch-icon-precomposed',
-          sizes: '144x144',
-          href: '/favicon/apple-touch-icon-144x144.png',
-        },
-        {
-          rel: 'apple-touch-icon-precomposed',
-          sizes: '120x120',
-          href: '/favicon/apple-touch-icon-120x120.png',
-        },
-        {
-          rel: 'apple-touch-icon-precomposed',
-          sizes: '152x152',
-          href: '/favicon/apple-touch-icon-152x152.png',
-        },
-        {
-          rel: 'icon',
-          sizes: '32x32',
-          type: 'image/x-icon',
-          href: '/favicon/favicon-32x32.png',
-        },
-        {
-          rel: 'icon',
-          sizes: '16x16',
-          type: 'image/x-icon',
-          href: '/favicon/favicon-16x16.png',
-        },
-      ],
-    },
-  },
-  css: [
-    '@/assets/css/global.scss',
-    '@/assets/css/theme.scss',
-    '@/assets/css/import.scss',
-    '@vuepic/vue-datepicker/dist/main.css',
-  ],
-  typescript: {
-    strict: true,
-  },
-  modules: [
-    // eslint-disable-next-line require-await
-    async (_, nuxt) => {
-      nuxt.hooks.hook('vite:extendConfig', (config) =>
-        // @ts-expect-error A revoir après que les lignes aient été décommentées
-        (config.plugins ?? []).push(
-          vuetify(),
-          // Remplacer par cela quand l'issue https://github.com/vuetifyjs/vuetify-loader/issues/273 sera règlée..
-          // voir aussi : https://github.com/nuxt/nuxt/issues/15412 et https://github.com/vuetifyjs/vuetify-loader/issues/290
-          // voir aussi : https://github.com/jrutila/nuxt3-vuetify3-bug
-          // vuetify({
-          //     styles: { configFile: './assets/css/settings.scss' }
-          // })
-        ),
-      )
-    },
-    [
-      '@pinia/nuxt',
-      {
-        autoImports: [
-          // automatically imports `usePinia()`
-          'defineStore',
-          // automatically imports `usePinia()` as `usePiniaStore()`
-          ['defineStore', 'definePiniaStore'],
-        ],
-      },
-    ],
-    '@pinia-orm/nuxt',
-    '@nuxtjs/i18n',
-    '@nuxt/devtools',
-    '@nuxt/image',
-    'nuxt-prepare',
-    'nuxt-vitalizer',
-  ],
-  vite: {
-    esbuild: {
-      drop: process.env.DEBUG ? [] : ['console', 'debugger'],
-      tsconfigRaw: {
-        compilerOptions: {
-          experimentalDecorators: true,
-        },
-      },
-    },
-    ssr: {
-      // with ssr enabled, this config is required to load vuetify properly
-      noExternal: ['vuetify'],
-    },
-    server: {
-      https,
-      // @ts-expect-error J'ignore pourquoi cette erreur TS se produit, cette propriété est valide
-      port: 443,
-      hmr: {
-        protocol: 'wss',
-        port: 24678,
-      },
-    },
-  },
-  // Hide the sourcemaps warnings with vuetify
-  // @see https://github.com/vuetifyjs/vuetify-loader/issues/290#issuecomment-1435702713
-  sourcemap: {
-    server: false,
-    client: false,
-  },
-  i18n: {
-    langDir: 'lang',
-    lazy: true,
-    strategy: 'no_prefix',
-    locales: [
-      {
-        code: 'en',
-        iso: 'en-US',
-        file: 'en.json',
-        name: 'English',
-      },
-      {
-        code: 'fr',
-        iso: 'fr-FR',
-        file: 'fr.json',
-        name: 'Français',
-      },
-    ],
-    defaultLocale: 'fr',
-    detectBrowserLanguage: false,
-    vueI18n: './i18n.config.ts',
-  },
-  image: {
-    provider: 'none',
-  },
-  build: {
-    transpile,
-  },
-  ignore: [process.env.NUXT_ENV === 'prod' ? 'pages/dev/*' : ''],
-  prepare: {
-    scripts: ['prepare/buildIndex.ts'],
-  },
-})
+ ssr: true,
+
+ experimental: {
+   // Fix the 'Cannot stringify non POJO' bug
+   // @see https://github.com/nuxt/nuxt/issues/20787
+   renderJsonPayloads: false,
+ },
+
+ runtimeConfig: {
+   // Private config that is only available on the server
+   env: '',
+   baseUrl: '',
+   baseUrlLegacy: '',
+   baseUrlAdminLegacy: '',
+   baseUrlTypo3: '',
+   baseUrlMercure: '',
+   fileStorageBaseUrl: '',
+   supportUrl: '',
+   basicomptaUrl: 'https://app.basicompta.fr/',
+   // Config within public will be also exposed to the client
+   public: {
+     env: '',
+     baseUrl: '',
+     baseUrlLegacy: '',
+     baseUrlAdminLegacy: '',
+     baseUrlTypo3: '',
+     baseUrlMercure: '',
+     fileStorageBaseUrl: '',
+     supportUrl: '',
+     basicomptaUrl: 'https://app.basicompta.fr/',
+   },
+ },
+
+ hooks: {
+   'builder:watch': console.log,
+ },
+
+ app: {
+   head: {
+     title: 'Opentalent',
+     meta: [
+       { charset: 'utf-8' },
+       { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+       { name: 'msapplication-TileColor', content: '#324250' },
+       {
+         name: 'msapplication-TileImage',
+         content: '/favicon/favicon-144x144.png',
+       },
+     ],
+     link: [
+       { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
+       {
+         rel: 'apple-touch-icon-precomposed',
+         sizes: '57x57',
+         href: '/favicon/apple-touch-icon-57x57.png',
+       },
+       {
+         rel: 'apple-touch-icon-precomposed',
+         sizes: '114x114',
+         href: '/favicon/apple-touch-icon-114x114.png',
+       },
+       {
+         rel: 'apple-touch-icon-precomposed',
+         sizes: '72x72',
+         href: '/favicon/apple-touch-icon-72x72.png',
+       },
+       {
+         rel: 'apple-touch-icon-precomposed',
+         sizes: '144x144',
+         href: '/favicon/apple-touch-icon-144x144.png',
+       },
+       {
+         rel: 'apple-touch-icon-precomposed',
+         sizes: '120x120',
+         href: '/favicon/apple-touch-icon-120x120.png',
+       },
+       {
+         rel: 'apple-touch-icon-precomposed',
+         sizes: '152x152',
+         href: '/favicon/apple-touch-icon-152x152.png',
+       },
+       {
+         rel: 'icon',
+         sizes: '32x32',
+         type: 'image/x-icon',
+         href: '/favicon/favicon-32x32.png',
+       },
+       {
+         rel: 'icon',
+         sizes: '16x16',
+         type: 'image/x-icon',
+         href: '/favicon/favicon-16x16.png',
+       },
+     ],
+   },
+ },
+
+ css: [
+   '@/assets/css/global.scss',
+   '@/assets/css/theme.scss',
+   '@/assets/css/import.scss',
+   '@vuepic/vue-datepicker/dist/main.css',
+ ],
+
+ typescript: {
+   strict: true,
+ },
+
+ modules: [
+   // eslint-disable-next-line require-await
+   async (_, nuxt) => {
+     nuxt.hooks.hook('vite:extendConfig', (config) =>
+       // @ts-expect-error A revoir après que les lignes aient été décommentées
+       (config.plugins ?? []).push(
+         vuetify(),
+         // Remplacer par cela quand l'issue https://github.com/vuetifyjs/vuetify-loader/issues/273 sera règlée..
+         // voir aussi : https://github.com/nuxt/nuxt/issues/15412 et https://github.com/vuetifyjs/vuetify-loader/issues/290
+         // voir aussi : https://github.com/jrutila/nuxt3-vuetify3-bug
+         // vuetify({
+         //     styles: { configFile: './assets/css/settings.scss' }
+         // })
+       ),
+     )
+   },
+   [
+     '@pinia/nuxt',
+     {
+       autoImports: [
+         // automatically imports `usePinia()`
+         'defineStore',
+         // automatically imports `usePinia()` as `usePiniaStore()`
+         ['defineStore', 'definePiniaStore'],
+       ],
+     },
+   ],
+   '@pinia-orm/nuxt',
+   '@nuxtjs/i18n',
+   '@nuxt/devtools',
+   '@nuxt/image',
+   'nuxt-prepare',
+   'nuxt-vitalizer',
+ ],
+
+ vite: {
+   esbuild: {
+     drop: process.env.DEBUG ? [] : ['console', 'debugger'],
+     tsconfigRaw: {
+       compilerOptions: {
+         experimentalDecorators: true,
+       },
+     },
+   },
+   ssr: {
+     // with ssr enabled, this config is required to load vuetify properly
+     noExternal: ['vuetify'],
+   },
+   server: {
+     https,
+     // @ts-expect-error J'ignore pourquoi cette erreur TS se produit, cette propriété est valide
+     port: 443,
+     hmr: {
+       protocol: 'wss',
+       port: 24678,
+     },
+   },
+ },
+
+ // Hide the sourcemaps warnings with vuetify
+ // @see https://github.com/vuetifyjs/vuetify-loader/issues/290#issuecomment-1435702713
+ sourcemap: {
+   server: false,
+   client: false,
+ },
+
+ i18n: {
+   langDir: 'lang',
+   lazy: true,
+   strategy: 'no_prefix',
+   locales: [
+     {
+       code: 'en',
+       iso: 'en-US',
+       file: 'en.json',
+       name: 'English',
+     },
+     {
+       code: 'fr',
+       iso: 'fr-FR',
+       file: 'fr.json',
+       name: 'Français',
+     },
+   ],
+   defaultLocale: 'fr',
+   detectBrowserLanguage: false,
+   vueI18n: './i18n.config.ts',
+ },
+
+ image: {
+   provider: 'none',
+ },
+
+ build: {
+   transpile,
+ },
+
+ ignore: [process.env.NUXT_ENV === 'prod' ? 'pages/dev/*' : ''],
+
+ prepare: {
+   scripts: ['prepare/buildIndex.ts'],
+ },
+
+ compatibilityDate: '2025-01-30'
+})

+ 6 - 1
pages/parameters.vue

@@ -18,4 +18,9 @@ definePageMeta({
 })
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+:deep(.v-table thead td) {
+  color: rgb(var(--v-theme-on-primary-alt));
+  font-weight: bold;
+}
+</style>

+ 4 - 64
pages/parameters/attendances.vue

@@ -38,56 +38,10 @@
       <v-divider class="my-10" />
     </div>
 
-    <UiLoadingPanel v-if="attendanceBookingReasonsPending" />
-    <div v-else>
-      <v-table>
-        <thead>
-          <tr>
-            <td>{{ $t('attendanceBookingReasons') }}</td>
-            <td></td>
-          </tr>
-        </thead>
-        <tbody v-if="attendanceBookingReasons!.items.length > 0">
-          <tr
-            v-for="reason in attendanceBookingReasons!.items"
-            :key="reason.id"
-          >
-            <td class="cycle-editable-cell">
-              {{ reason.reason }}
-            </td>
-            <td class="d-flex flex-row">
-              <v-btn
-                :flat="true"
-                icon="fa fa-pen"
-                class="cycle-edit-icon mr-3"
-                @click="goToEditPage(reason.id as number)"
-              />
-              <UiButtonDelete
-                :entity="reason"
-                :flat="true"
-                class="cycle-edit-icon"
-              />
-            </td>
-          </tr>
-        </tbody>
-        <tbody v-else>
-          <tr class="theme-neutral">
-            <td>
-              <i>{{ $t('nothing_to_show') }}</i>
-            </td>
-            <td></td>
-          </tr>
-        </tbody>
-      </v-table>
-      <v-btn
-        :flat="true"
-        prepend-icon="fa fa-plus"
-        class="theme-primary mt-4"
-        @click="goToCreatePage"
-      >
-        {{ $t('add') }}
-      </v-btn>
-    </div>
+    <LayoutParametersTable
+      :model="AttendanceBookingReason"
+      :columns-props="['reason']"
+    />
   </LayoutContainer>
 </template>
 <script setup lang="ts">
@@ -117,12 +71,6 @@ const { data: parameters, pending } = fetch(
   organizationProfile.parametersId,
 ) as AsyncData<Parameters | null, Error | null>
 
-const { fetchCollection } = useEntityFetch()
-
-const {
-  data: attendanceBookingReasons,
-  pending: attendanceBookingReasonsPending,
-} = fetchCollection(AttendanceBookingReason)
 
 const rules = () => [
   (numberConsecutiveAbsences: string | null) =>
@@ -130,12 +78,4 @@ const rules = () => [
       parseInt(numberConsecutiveAbsences) > 0) ||
     i18n.t('please_enter_a_value'),
 ]
-
-const goToEditPage = (id: number) => {
-  navigateTo(UrlUtils.join('/parameters/attendance_booking_reasons', id))
-}
-
-const goToCreatePage = () => {
-  navigateTo('/parameters/attendance_booking_reasons/new')
-}
 </script>

+ 0 - 2
pages/parameters/bulletin.vue

@@ -58,14 +58,12 @@
             v-model="parameters.bulletinReceiver"
             field="bulletinReceiver"
             enum-name="organization_bulletin_send_to"
-            variant="underlined"
           />
 
           <UiInputAutocompleteWithEnum
             v-model="parameters.bulletinCriteriaSort"
             field="bulletinCriteriaSort"
             enum-name="organization_bulletin_criteria_sort"
-            variant="underlined"
           />
         </v-col>
       </v-row>

+ 6 - 9
pages/parameters/education_notation.vue

@@ -21,25 +21,23 @@
             label="evaluation_criterium_edition_is_admin_only"
           />
 
+          <UiInputCheckbox
+            v-model="parameters.requiredValidation"
+            field="requiredValidation"
+            label="mandatory_validation_for_evaluations"
+          />
+
           <UiInputAutocompleteWithEnum
             v-if="organizationProfile.hasModule('AdvancedEducationNotation')"
             v-model="parameters.advancedEducationNotationType"
             enum-name="advanced_education_notation"
             field="advancedEducationNotationType"
-            variant="underlined"
-          />
-
-          <UiInputCheckbox
-            v-model="parameters.requiredValidation"
-            field="requiredValidation"
-            label="mandatory_validation_for_evaluations"
           />
 
           <UiInputAutocompleteWithEnum
             v-model="parameters.educationPeriodicity"
             enum-name="education_periodicity"
             field="educationPeriodicity"
-            variant="underlined"
           />
 
           <UiInputNumber
@@ -50,7 +48,6 @@
             :min="1"
             :max="100"
             class="mt-2"
-            variant="underlined"
           />
         </v-col>
       </v-row>

+ 1 - 1
pages/parameters/education_timings/index.vue

@@ -6,7 +6,7 @@
         <thead>
           <tr>
             <td>{{ $t('educationTimings') }}</td>
-            <td></td>
+            <td>{{ $t('actions')}}</td>
           </tr>
         </thead>
         <tbody v-if="educationTimings!.items.length > 0">

+ 2 - 0
pages/parameters/index.vue

@@ -11,3 +11,5 @@
 const router = useRouter()
 router.push({ path: `/parameters/general_parameters` })
 </script>
+
+<style scoped lang="scss"></style>

+ 2 - 0
pages/parameters/intranet.vue

@@ -9,6 +9,7 @@
     >
       <v-row>
         <v-col cols="12">
+          <h3>{{ $t('teachers')}}</h3>
           <UiInputCheckbox
             v-model="parameters.createCourse"
             field="createCourse"
@@ -33,6 +34,7 @@
             label="allow_teachers_to_generate_attendance_reports"
           />
 
+          <h3>{{ $t('pupils-members')}}</h3>
           <UiInputCheckbox
             v-model="parameters.administrationCc"
             field="administrationCc"

+ 1 - 1
pages/parameters/residence_areas/index.vue

@@ -6,7 +6,7 @@
         <thead>
           <tr>
             <td>{{ $t('residenceAreas') }}</td>
-            <td></td>
+            <td>{{$t('actions')}}</td>
           </tr>
         </thead>
         <tbody v-if="residenceAreas!.items.length > 0">

+ 1 - 0
pages/parameters/teaching.vue

@@ -12,6 +12,7 @@
           <tr>
             <td>{{ $t('originalLabel') }}</td>
             <td>{{ $t('effectiveLabel') }}</td>
+            <td>{{ $t('actions') }}</td>
           </tr>
         </thead>
 

+ 18 - 12
pages/parameters/website.vue

@@ -24,21 +24,10 @@
             field="publicationDirectors"
             multiple
             chips
-            variant="underlined"
+            variant="outlined"
             class="my-4"
           />
 
-          <div>
-            <UiInputText
-              v-model="parameters.otherWebsite"
-              field="otherWebsite"
-              variant="underlined"
-              class="my-4"
-            />
-          </div>
-
-          <v-divider class="my-10" />
-
           <div class="mb-6">
             <div>
               <h4>{{ $t('your_subdomains') }} :</h4>
@@ -131,6 +120,15 @@
               </LazyLayoutDialog>
             </div>
           </div>
+
+          <div>
+            <UiInputText
+              v-model="parameters.otherWebsite"
+              field="otherWebsite"
+              variant="underlined"
+              class="my-4"
+            />
+          </div>
         </v-col>
       </v-row>
     </UiForm>
@@ -214,6 +212,7 @@ const onDialogYesBtnClick = () => {
   cursor: pointer;
   border: solid 1px rgb(var(--v-theme-neutral-strong));
 }
+
 .subdomainItem:hover {
   background: rgb(var(--v-theme-neutral));
 }
@@ -222,10 +221,17 @@ const onDialogYesBtnClick = () => {
 }
 
 .subdomainItem td:first-child {
+  border-top: solid 1px rgb(var(--v-theme-neutral));
+  border-bottom: solid 1px rgb(var(--v-theme-neutral));
   border-left: solid 2px rgb(var(--v-theme-neutral));
 }
 
 .subdomainItem.active td:first-child {
   border-left: solid 2px rgb(var(--v-theme-primary));
 }
+.subdomainItem.active td:last-child {
+  border-top: solid 1px rgb(var(--v-theme-neutral));
+  border-bottom: solid 1px rgb(var(--v-theme-neutral));
+  border-right: solid 1px rgb(var(--v-theme-neutral));
+}
 </style>

+ 18 - 18
plugins/ability.ts

@@ -26,22 +26,22 @@ export default defineNuxtPlugin(() => {
    *
    * @see https://pinia.vuejs.org/core-concepts/actions.html#Subscribing-to-actions
    */
-  // const unsubscribe = organizationProfile.$onAction(
-  //   ({
-  //     name, // name of the action
-  //     after, // hook after the action returns or resolves
-  //   }) => {
-  //     after((_) => {
-  //       if (name === 'initiateProfile') {
-  //         // On construit les habilités et on les enregistre dans le store
-  //         // noinspection UnnecessaryLocalVariableJS
-  //         const abilities = abilityUtils.buildAbilities()
-  //         accessProfile.abilities = abilities
-  //
-  //         // Unsubscribe pour éviter les memory leaks
-  //         unsubscribe()
-  //       }
-  //     })
-  //   },
-  // )
+  const unsubscribe = organizationProfile.$onAction(
+    ({
+      name, // name of the action
+      after, // hook after the action returns or resolves
+    }) => {
+      after((_) => {
+        if (name === 'initiateProfile') {
+          // On construit les habilités et on les enregistre dans le store
+          // noinspection UnnecessaryLocalVariableJS
+          const abilities = abilityUtils.buildAbilities()
+          accessProfile.abilities = abilities
+
+          // Unsubscribe pour éviter les memory leaks
+          unsubscribe()
+        }
+      })
+    },
+  )
 })

+ 6 - 0
types/enum/enums.ts

@@ -105,3 +105,9 @@ export const enum LINK_TARGET {
   TOP = '_top',
   FRAMENAME = 'framename',
 }
+
+export const enum TABLE_ACTION {
+  EDIT = 'edit',
+  DELETE = 'delete',
+  ADD = 'add',
+}