Sfoglia il codice sorgente

add CreateButton, notifs, em cleanup after persist and minor style fixes

Olivier Massot 3 anni fa
parent
commit
191edac465

+ 25 - 26
components/Layout/Dialog.vue

@@ -1,35 +1,35 @@
 <!-- Fenêtre de dialogue -->
 <template>
   <v-dialog
-    :value="show"
+    :model-value="show"
     persistent
     max-width="800"
     :content-class="contentClass"
+    @update:modelValue="console.log($event); $emit($event)"
   >
-    <v-card class="d-flex">
-        <div class="dialog-type flex-column justify-center d-none d-sm-flex">
-          <h3 class="d-flex">
-            <slot name="dialogType" />
-          </h3>
-        </div>
+    <v-card class="d-flex flex-row">
+      <div class="dialog-type flex-column justify-center d-none d-sm-flex">
+        <h3 class="d-flex">
+          <slot name="dialogType" />
+        </h3>
+      </div>
 
-        <div class="dialog-container flex-column flex-grow-1">
-          <div class="d-flex flex-column">
-            <v-card-title class="dialog-title">
-              <slot name="dialogTitle" />
-            </v-card-title>
+      <div class="dialog-container d-flex flex-column flex-grow-1">
+        <v-card-title class="dialog-title">
+          <slot name="dialogTitle" />
+        </v-card-title>
 
-            <div class="dialog-text-container">
-              <slot name="dialogText" />
-            </div>
+        <div class="dialog-text-container">
+          <slot name="dialogText" />
+        </div>
 
-            <v-divider />
+        <v-spacer />
+        <v-divider />
 
-            <v-card-actions class="justify-center">
-              <slot name="dialogBtn" />
-            </v-card-actions>
-          </div>
-        </div>
+        <v-card-actions class="justify-center">
+          <slot name="dialogBtn" />
+        </v-card-actions>
+      </div>
     </v-card>
   </v-dialog>
 </template>
@@ -57,16 +57,15 @@ const props = defineProps({
   .dialog-type {
     background: rgb(var(--v-theme-ot-green, #00AD8E));
     color: #fff;
-    width: 160px;
+    width: 50px;
+    min-height: 400px;
+    padding: 25px 10px;
 
    h3 {
      font-size: 25px;
      font-weight: normal;
-     writing-mode: tb-lr;
      writing-mode: vertical-lr;
-     transform: rotate(
-         -180deg);
-     padding: 10px;
+     transform: rotate(-180deg);
     }
   }
 

+ 2 - 6
components/Layout/Header.vue

@@ -23,7 +23,7 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 
 <!--    <v-spacer />-->
 
-<!--    <LayoutHeaderUniversalCreationCreateButton v-if="showUniversalButton" />-->
+    <LayoutHeaderUniversalCreationCreateButton v-if="showUniversalButton" />
 
     <LayoutHeaderHomeBtn />
 
@@ -33,7 +33,7 @@ Contient entre autres le nom de l'organisation, l'accès à l'aide et aux préf
 
     <LayoutHeaderMenu name="MyFamily" />
 
-<!--    <LayoutHeaderNotification />-->
+    <LayoutHeaderNotification />
 
     <LayoutHeaderMenu name="Configuration" />
 
@@ -91,8 +91,4 @@ const showUniversalButton =
     font-size: 14px;
     text-decoration: none;
   }
-
-  :deep(.v-btn) {
-    background: none !important;
-  }
 </style>

+ 6 - 2
components/Layout/Header/Menu.vue

@@ -6,7 +6,12 @@ header principal (configuration, paramètres du compte...)
 <template>
   <div v-if="displayMenu">
 
-    <v-btn ref="btn" icon width="48px" size="small">
+    <v-btn
+        ref="btn"
+        icon
+        width="48px"
+        size="small"
+    >
       <v-avatar v-if="menu.icon.avatarId || menu.icon.avatarByDefault" size="30">
         <UiImage :id="menu.icon.avatarId" :defaultImage="menu.icon.avatarByDefault" :width="30"></UiImage>
       </v-avatar>
@@ -20,7 +25,6 @@ header principal (configuration, paramètres du compte...)
     <v-menu
         :activator="btn"
         :model-value="isOpened()"
-        location="start"
         @update:modelValue="onStateUpdated"
     >
       <v-card>

+ 109 - 77
components/Layout/Header/Notification.vue

@@ -2,50 +2,68 @@
   <v-btn
       ref="btn"
       icon
-      color=""
-      @click="click"
+      width="48px"
+      size="small"
   >
     <v-badge
         color="orange"
-        offset-y="10"
-        :value="unreadNotification.length > 0"
-        :content="unreadNotification.length"
-    >
-      <v-icon class="text-ot-white" small>
-        fa-bell
+        offset-x="-4"
+        offset-y="17"
+        :model-value="unreadNotification.length > 0"
+        :content="unreadNotification.length">
+      <v-icon class="text-ot-white">
+        fa fa-bell
       </v-icon>
     </v-badge>
   </v-btn>
 
-  <v-tooltip bottom :activator="btn">
+  <v-tooltip :activator="btn" location="bottom">
     <span>{{ $t('notification') }}</span>
   </v-tooltip>
 
-  <v-menu :activator="btn" offset-y v-model="isOpen">
-    <v-card scrollable max-width="400">
+  <v-menu
+      :activator="btn"
+      v-model="isOpen"
+  >
+    <v-card max-width="400">
       <v-card-title class="ot-header-menu text-body-2 font-weight-bold">
         {{ $t('notification') }}
       </v-card-title>
 
       <v-card-text class="ma-0 pa-0 header-menu">
-        <v-list dense :subheader="true">
-          <template v-for="(notification, index) in notifications">
-            <v-list-item :key="index" :class="`${notification.notificationUsers.length === 0 ? 'unread' : ''}`">
-              <v-list-item-content>
-                <v-list-item-title class="list_item mt-2 mb-2" v-text="getMessage(notification)"/>
-              </v-list-item-content>
-              <v-list-item-icon v-if="notification.link" class="pt-4">
-                <v-icon @click="download(notification.link)" icon="mdi-download" />
-              </v-list-item-icon>
-            </v-list-item>
-            <v-divider></v-divider>
-          </template>
+        <v-list density="compact" :subheader="true">
+          <v-list-item
+              v-for="(notification, index) in notifications"
+              :key="index"
+              :class="`${notification.notificationUsers.length === 0 ? 'unread' : ''}`"
+          >
+
+            <v-list-item-content>
+              <v-list-item-title
+                  class="list_item mt-2 mb-2"
+                  v-text="getMessage(notification)"
+                  style="font-size: 13px; line-height: 16px;"
+              />
+            </v-list-item-content>
+
+            <template #append>
+              <v-icon
+                  v-if="notification.link"
+                  icon="mdi:mdi-download"
+                  @click="download(notification.link)"
+                  class="pt-4"
+              />
+            </template>
+
+<!--            <v-divider v-if="index < (notifications.length - 1)"/>-->
+          </v-list-item>
         </v-list>
 
+        <!--suppress VueUnrecognizedDirective -->
         <v-card v-intersect="update"></v-card>
 
         <v-row
-          v-if="loading"
+          v-if="pending"
           class="fill-height mt-3 mb-3"
           align="center"
           justify="center"
@@ -53,21 +71,23 @@
           <v-progress-circular
             indeterminate
             color="grey lighten-1"
-          ></v-progress-circular>
+          />
         </v-row>
       </v-card-text>
 
       <v-card-actions class="ma-0 pa-0">
-        <template>
-          <v-list-item
-            id="all_notifications"
-            :key="$t('all_notification')"
-            :href="notificationUrl"
-            router
-          >
-            <v-list-item-title class="text-body-2 text-ot-white" v-text="$t('all_notification')"/>
-          </v-list-item>
-        </template>
+        <v-list-item
+          id="all_notifications"
+          :key="$t('all_notification')"
+          :href="notificationUrl"
+          router
+          style="width: 100%; height: 52px;"
+        >
+          <v-list-item-title
+              class="text-body-2 text-ot-white"
+              v-text="$t('all_notification')"
+          />
+        </v-list-item>
       </v-card-actions>
     </v-card>
   </v-menu>
@@ -78,12 +98,14 @@ import {NOTIFICATION_TYPE, QUERY_TYPE} from "~/types/enum/enums";
 import {Notification} from "~/models/Core/Notification";
 import {NotificationUsers} from "~/models/Core/NotificationUsers";
 import {useAccessProfileStore} from "~/stores/accessProfile";
-import {ComputedRef, Ref} from "@vue/reactivity";
+import {ComputedRef, Ref, ref} from "@vue/reactivity";
 import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {Pagination} from "~/types/data";
+import {useEntityManager} from "~/composables/data/useEntityManager";
+import Url from "~/services/utils/url";
 
 const accessProfileStore = useAccessProfileStore()
-const currentAccessId = accessProfileStore.id
+
 const loading: Ref<Boolean> = ref(true)
 const isOpen: Ref<Boolean> = ref(false)
 const page: Ref<number> = ref(1)
@@ -93,15 +115,19 @@ const runtimeConfig = useRuntimeConfig()
 
 const btn = ref(null)
 
+const { em } = useEntityManager()
 const { fetchCollection } = useEntityFetch()
 
-const { data: collection, pending } = await fetchCollection(Notification)
+let { data: collection, pending, refresh } = await fetchCollection(Notification)
+
+
 
 /**
  * On récupère les Notifications via le store
  */
 const notifications: ComputedRef = computed(() => {
   // TODO: revoir pour reprendre le order by et tout
+  console.log(collection.value?.items[0].id)
   return collection.value !== null ? collection.value.items : []
 })
 
@@ -126,11 +152,18 @@ const unreadNotification: ComputedRef<Array<Notification>> = computed(() => {
  * (si on a fini de télécharger les éléments précédents)
  */
 const update = async () => {
-  if (!fetchState.pending && metadata.value?.nextPage && metadata.value.nextPage > 0) {
+  if (
+      !pending &&
+      pagination.value &&
+      pagination.value.next &&
+      pagination.value.next > 0
+  ) {
     loading.value = true
-    page.value = metadata.value.nextPage
-    await fetch()
-    //Si des notifications n'avaient pas été marquées comme lues, on le fait immédiatement.
+    page.value = pagination.value.next
+
+    await refresh()
+
+    // Si des notifications n'avaient pas été marquées comme lues, on le fait immédiatement.
     markNotificationsAsRead()
   }
 }
@@ -139,19 +172,19 @@ const update = async () => {
  * On construit le message qui va devoir s'afficher pour une notification
  * @param notification
  */
-const getMessage = (notification:Notification) => {
+const getMessage = (notification: Notification) => {
   switch (notification.type){
     case NOTIFICATION_TYPE.FILE :
-     return `${i18n.t('your_file')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be_downloaded')}`
+      return `${i18n.t('your_file')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be_downloaded')}`
 
-     case NOTIFICATION_TYPE.MESSAGE:
-       if(notification.message?.action)
-         return `${i18n.t('your_message')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be')} ${notification.message.action}`
+    case NOTIFICATION_TYPE.MESSAGE:
+      if (notification.message?.action)
+        return `${i18n.t('your_message')} ${notification.message?.fileName} ${i18n.t('is_ready_to_be')} ${notification.message.action}`
 
-       return `${i18n.t('your_message')} ${notification.message?.about ?? ''} ${i18n.t('has_been_sent')} `
+      return `${i18n.t('your_message')} ${notification.message?.about ?? ''} ${i18n.t('has_been_sent')} `
 
     case NOTIFICATION_TYPE.SYSTEM :
-      if(notification.message?.about)
+      if (notification.message?.about)
         return `${i18n.t(notification.message.about)}`
       break;
 
@@ -164,62 +197,61 @@ const getMessage = (notification:Notification) => {
  * Dès l'ouverture du menu, on indique que les notifications non lues, le sont.
  */
 const unwatch = watch(isOpen, (newValue, oldValue) => {
-  if(newValue){
+  if (newValue){
     markNotificationsAsRead()
   }
 })
-
 onUnmounted(() => {
   unwatch()
 })
 
 /**
- * Marque les notifications non lues comme lues
+ * Créer une nouvelle notification users coté back.
+ * @param notification
+ * @param accessId
  */
-const markNotificationsAsRead = () => {
-  unreadNotification.value.map((notification:Notification)=>{
-    notification.notificationUsers = ['read']
-    repositoryHelper.persist(Notification, notification)
-    createNewNotificationUsers(notification)
+const createNewNotificationUsers = (notification: Notification, accessId: number) => {
+  const notificationUsers = em.newInstance(NotificationUsers, {
+    access:`/api/accesses/${accessId}`,
+    notification:`/api/notifications/${notification.id}`,
+    isRead: true
   })
+
+  em.persist(NotificationUsers, notificationUsers)
+  notification.notificationUsers = ['read']
 }
 
 /**
- * Créer une nouvelle notification users coté back.
- * @param notification
+ * Marque les notifications non lues comme lues
  */
-const createNewNotificationUsers = (notification: Notification) =>{
-  const newNotificationUsers = repositoryHelper.persist(NotificationUsers, new NotificationUsers(
-    {
-      access:`/api/accesses/${currentAccessId}`,
-      notification:`/api/notifications/${notification.id}`,
-      isRead: true
+const markNotificationsAsRead = () => {
+  unreadNotification.value.map((notification: Notification) => {
+    if (accessProfileStore.id === null) {
+      throw new Error('Current access id is null')
     }
-  )) as NotificationUsers
-
-  $dataPersister.invoke({
-    type: QUERY_TYPE.MODEL,
-    model: NotificationUsers,
-    idTemp: newNotificationUsers.id,
-    showProgress: false
+    createNewNotificationUsers(notification, accessProfileStore.id)
   })
 }
 
 /**
- * Download le lien
+ * Download la cible du lien
  * @param link
  */
 const download = (link: string) => {
+  if (accessProfileStore.id === null) {
+    throw new Error('Current access id is null')
+  }
   const url_parts: Array<string> = link.split('/api');
+
   if(accessProfileStore.originalAccess)
-    url_parts[0] = `api/${accessProfileStore.originalAccess.id}/${currentAccessId}`
+    url_parts[0] = Url.join('api', String(accessProfileStore.originalAccess.id), String(accessProfileStore.id))
   else
-    url_parts[0] = `api/${currentAccessId}`
+    url_parts[0] = Url.join('api', String(accessProfileStore.id))
 
-  window.open(`${runtimeConfig.baseUrlLegacy}/${url_parts.join('')}`);
+  window.open(Url.join(runtimeConfig.baseUrlLegacy, url_parts.join('')));
 }
 
-const notificationUrl = `${runtimeConfig.baseURL_adminLegacy}/notifications/list/`
+const notificationUrl = Url.join(runtimeConfig.baseUrlAdminLegacy, 'notifications/list/')
 
 </script>
 

+ 6 - 4
components/Layout/Header/UniversalCreation/CreateButton.vue

@@ -6,7 +6,7 @@ bouton Créer
   <main>
     <v-btn
       elevation="2"
-      color="ot-warning text-ot-white"
+      class="bg-ot-warning text-ot-white"
       @click="showDialog=true"
     >
       {{ $t('create') }}
@@ -23,13 +23,15 @@ bouton Créer
       </template>
 
       <template #dialogText>
-        <LayoutHeaderUniversalCreationGenerateCardsSteps :step="step" @updateStep="updateStep" />
+        <!-- TODO: réactiver ce component quand les v-steper seront implémentés dans vuetify 3 -->
+        <!-- <LayoutHeaderUniversalCreationGenerateCardsSteps :step="step" @updateStep="updateStep" /> -->
+        <span>TEMP</span>
       </template>
 
       <template #dialogBtn>
         <div class="text-center">
           <v-btn
-            color="ot-super-light-grey"
+            class="bg-ot-super-light-grey text-ot-black"
             @click="showDialog=false; step=1; type='home'"
           >
             {{ $t('cancel') }}
@@ -37,7 +39,7 @@ bouton Créer
 
           <v-btn
             v-if="step > 1"
-            color="ot-super_light_grey"
+            class="bg-ot-super-light-grey text-ot-black"
             @click="step=1; type='home'"
           >
             {{ $t('previous') }}

+ 0 - 2
components/Layout/SubHeader/PersonnalizedList.vue

@@ -96,8 +96,6 @@ const filteredItems = computed(() => {
   )
 })
 
-console.log(filteredItems)
-
 const runtimeConfig = useRuntimeConfig()
 const homeUrl: string = runtimeConfig.baseUrlAdminLegacy
 

+ 31 - 1
services/data/entityManager.ts

@@ -203,7 +203,13 @@ class EntityManager {
             response = await this.apiRequestService.post(url, data)
         }
 
-        return this.saveResponseAsEntity(model, response)
+        const createdEntity = this.saveResponseAsEntity(model, response)
+
+        if (entity.isNew()) {
+            this.removeTempAfterPersist(model, entity.id)
+        }
+
+        return createdEntity
     }
 
     /**
@@ -346,6 +352,30 @@ class EntityManager {
         entity.id = id
         return entity
     }
+
+    /**
+     * Delete the temporary entity from the repo after it was persisted via the api, replaced by the entity
+     * that has been returned by the api with is definitive id.
+     *
+     * @param model
+     * @param tempEntityId
+     * @private
+     */
+    private removeTempAfterPersist(model: typeof ApiResource, tempEntityId: number) {
+        const repository = this.getRepository(model)
+
+        const entity = repository.find(tempEntityId)
+        if (!entity || typeof entity === 'undefined') {
+            console.error(model.entity + '/' + tempEntityId, ' does not exist!')
+            return
+        }
+        if (!entity.isNew()) {
+            throw new Error('Error: Can not remove a non-temporary entity')
+        }
+
+        repository.destroy(tempEntityId)
+        repository.destroy(this.CLONE_PREFIX + tempEntityId)
+    }
 }
 
 export default EntityManager