Просмотр исходного кода

Merge branch 'production'

# Conflicts:
#	package.json
#	src/api/APIGateway.js
ali 3 лет назад
Родитель
Сommit
e0b5a2083e
51 измененных файлов с 6105 добавлено и 27 удалено
  1. 2 2
      package.json
  2. 16 12
      src-pwa/custom-service-worker.js
  3. 2 0
      src/api/APIGateway.js
  4. 145 0
      src/api/models/Event.js
  5. 4 4
      src/assets/Enums/Enums.js
  6. 231 0
      src/components/EventItem.vue
  7. 6 0
      src/components/Template/Header/MainHeader.vue
  8. 16 0
      src/components/Template/SideBard/AdminPanelSideBar.vue
  9. 5 0
      src/components/Template/SideBard/Main.vue
  10. 16 0
      src/components/Template/SideBard/UserPanelSideBar.vue
  11. 169 0
      src/components/Widgets/Event/AdminEventCreate/AdminEventCreate.vue
  12. 21 0
      src/components/Widgets/Event/AdminEventCreate/OptionPanel.vue
  13. 215 0
      src/components/Widgets/Event/AdminEventList/AdminEventList.vue
  14. 21 0
      src/components/Widgets/Event/AdminEventList/OptionPanel.vue
  15. 127 0
      src/components/Widgets/Event/AdminEventShow/AdminEventShow.vue
  16. 36 0
      src/components/Widgets/Event/AdminEventShow/OptionPanel.vue
  17. 168 0
      src/components/Widgets/Event/AdminEventShow/components/ActivitySheetList.vue
  18. 285 0
      src/components/Widgets/Event/AdminEventShow/components/EventInfo.vue
  19. 35 0
      src/components/Widgets/Event/AdminEventShow/components/LiveStreaming.vue
  20. 225 0
      src/components/Widgets/Event/AdminEventShow/components/MembersList.vue
  21. 176 0
      src/components/Widgets/Event/AdminEventShow/components/ProjectList.vue
  22. 337 0
      src/components/Widgets/Event/AllEvents/AllEvents.vue
  23. 164 0
      src/components/Widgets/Event/AllEvents/OptionPanel.vue
  24. 173 0
      src/components/Widgets/Event/AllEvents/components/Filters.vue
  25. 253 0
      src/components/Widgets/Event/LastEvents/LastEvents.vue
  26. 164 0
      src/components/Widgets/Event/LastEvents/OptionPanel.vue
  27. 164 0
      src/components/Widgets/Event/ShowEventInfo/OptionPanel.vue
  28. 531 0
      src/components/Widgets/Event/ShowEventInfo/ShowEventInfo.vue
  29. 1 0
      src/layouts/AdminLayout.vue
  30. 1 0
      src/layouts/UserPanelLayout.vue
  31. 153 0
      src/models/Event.js
  32. 50 0
      src/models/EventRegistration.js
  33. 110 0
      src/pages/Admin/Event/ProjectAttendanceSheet/create.vue
  34. 171 0
      src/pages/Admin/Event/ProjectAttendanceSheet/members.vue
  35. 23 0
      src/pages/Admin/Event/ProjectAttendanceSheet/show.vue
  36. 23 0
      src/pages/Admin/Event/SessionActivitySheets/Assignment.vue
  37. 23 0
      src/pages/Admin/Event/SessionActivitySheets/Attendance.vue
  38. 23 0
      src/pages/Admin/Event/SessionActivitySheets/sessionActivitySheets.vue
  39. 23 0
      src/pages/Admin/Event/Ticket.vue
  40. 241 0
      src/pages/Admin/Event/create.vue
  41. 264 0
      src/pages/Admin/Event/index.vue
  42. 111 0
      src/pages/Admin/Event/project/create.vue
  43. 171 0
      src/pages/Admin/Event/project/members.vue
  44. 84 0
      src/pages/Admin/Event/project/show.vue
  45. 23 0
      src/pages/Admin/Event/show.vue
  46. 25 0
      src/pages/Public/Events.vue
  47. 41 0
      src/pages/Public/ShowEventInfo.vue
  48. 475 0
      src/pages/User/Profile/EventInfo.vue
  49. 291 0
      src/pages/User/Profile/Events.vue
  50. 62 0
      src/router/routes.js
  51. 9 9
      yarn.lock

+ 2 - 2
package.json

@@ -51,11 +51,11 @@
     "postcss-rtlcss": "^4.0.4",
     "process": "^0.11.10",
     "quasar": "^2.12.0",
-    "quasar-app-extension-q-page-builder": "^0.1.63",
+    "quasar-app-extension-q-page-builder": "^0.1.71",
     "quasar-crud": "^0.3.55",
     "quasar-form-builder": "^0.1.110",
     "quasar-template-builder": "^1.0.23",
-    "quasar-ui-q-page-builder": "^0.1.63",
+    "quasar-ui-q-page-builder": "^0.1.71",
     "socket.io-client": "^4.6.1",
     "sticky-sidebar-v2": "^1.1.1",
     "video.js": "^8.3.0",

+ 16 - 12
src-pwa/custom-service-worker.js

@@ -7,8 +7,12 @@
  */
 
 import { clientsClaim } from 'workbox-core'
-import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL } from 'workbox-precaching'
-import { registerRoute, NavigationRoute } from 'workbox-routing'
+import {
+  precacheAndRoute,
+  cleanupOutdatedCaches
+  // createHandlerBoundToURL
+} from 'workbox-precaching'
+// import { registerRoute, NavigationRoute } from 'workbox-routing'
 
 self.skipWaiting()
 clientsClaim()
@@ -18,13 +22,13 @@ precacheAndRoute(self.__WB_MANIFEST)
 
 cleanupOutdatedCaches()
 
-// Non-SSR fallback to index.html
-// Production SSR fallback to offline.html (except for dev)
-if (process.env.MODE !== 'ssr' || process.env.PROD) {
-  registerRoute(
-    new NavigationRoute(
-      createHandlerBoundToURL(process.env.PWA_FALLBACK_HTML),
-      { denylist: [/sw\.js$/, /workbox-(.)*\.js$/] }
-    )
-  )
-}
+// // Non-SSR fallback to index.html
+// // Production SSR fallback to offline.html (except for dev)
+// if (process.env.MODE !== 'ssr' || process.env.PROD) {
+//   registerRoute(
+//     new NavigationRoute(
+//       createHandlerBoundToURL(process.env.PWA_FALLBACK_HTML),
+//       { denylist: [/sw\.js$/, /workbox-(.)*\.js$/] }
+//     )
+//   )
+// }

+ 2 - 0
src/api/APIGateway.js

@@ -4,6 +4,7 @@ import UserAPI from 'src/api/models/User.js'
 import PostAPI from 'src/api/models/Post.js'
 import UnitAPI from 'src/api/models/Unit.js'
 import TeamAPI from 'src/api/models/Team.js'
+import EventAPI from 'src/api/models/Event.js'
 import MediaAPI from 'src/api/models/Media.js'
 import TicketAPI from 'src/api/models/Ticket.js'
 import WalletAPI from 'src/api/models/Wallet.js'
@@ -40,6 +41,7 @@ export const APIGateway = {
   unit: new UnitAPI(),
   post: new PostAPI(),
   team: new TeamAPI(),
+  event: new EventAPI(),
   media: new MediaAPI(),
   ticket: new TicketAPI(),
   wallet: new WalletAPI(),

+ 145 - 0
src/api/models/Event.js

@@ -0,0 +1,145 @@
+import { appApi } from 'src/boot/axios.js'
+import { Invoice } from 'src/models/Invoice.js'
+import APIRepository from '../classes/APIRepository.js'
+import { Event, EventList } from 'src/models/Event.js'
+
+export default class EventAPI extends APIRepository {
+  constructor() {
+    super('events', appApi, '/ema/event', Event)
+    this.APIAdresses = {
+      base: '/ema/events',
+      byId: (id) => '/ema/events/' + id,
+      enroll: (id) => '/ema/events/' + id + '/enroll',
+      drop: (id) => '/ema/events/' + id + '/drop',
+      members: '/ema/registrations',
+      activitySheet: '/ema/registrations/activity_sheet',
+      enrollByAdmin: (classroomId, userId) => '/ema/events/' + classroomId + '/enrolll?user_id=' + userId,
+      dropByAdmin: (classroomId) => '/ema/events/' + classroomId + '/drop',
+      createInvoice: (id) => '/ema/events/' + id + '/create_invoice'
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base,
+      byId: (id) => this.name + this.APIAdresses.byId(id)
+    }
+  }
+
+  index (data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        per_page: 10, // Number
+        page: 1 // Number
+      }, data),
+      resolveCallback: (response) => {
+        const paginate = response.data
+        const results = response.data.results
+        delete paginate.results
+        return {
+          list: new EventList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  get (id) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.byId(id),
+      resolveCallback: (response) => {
+        return new Event(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  enrollByUser (id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.enroll(id),
+      resolveCallback: (response) => {
+        return new Event(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  dropByUser (id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.drop(id),
+      resolveCallback: (response) => {
+        return new Event(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  dropByAdmin (classroomId, userId) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.dropByAdmin(classroomId),
+      data: {
+        owner: userId
+      },
+      resolveCallback: (response) => {
+        return new Event(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  createInvoice (id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.createInvoice(id),
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  createInvoiceByAdmin (classroomId, userId) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.createInvoice(classroomId),
+      data: { owner: userId },
+      resolveCallback: (response) => {
+        return new Invoice(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 4 - 4
src/assets/Enums/Enums.js

@@ -2,21 +2,21 @@ import groups from 'assets/Enums/groups'
 import genders from 'assets/Enums/genders'
 import classroomGenders from 'assets/Enums/classroomGenders'
 import classroomStatuses from 'assets/Enums/classroomStatuses'
-import classroomHoldingTypes from 'assets/Enums/classroomHoldingTypes'
 import maritalStatuses from 'assets/Enums/maritalStatuses'
 import academicDegree from 'assets/Enums/academicDegree'
 import educationalStatus from 'assets/Enums/educationalStatus'
 import invoiceStatus from 'assets/Enums/invoiceStatus'
+import classroomHoldingTypes from 'assets/Enums/classroomHoldingTypes'
 
 const Enums = {
   groups,
   genders,
-  classroomGenders,
+  invoiceStatus,
   academicDegree,
   maritalStatuses,
-  educationalStatus,
+  classroomGenders,
   classroomStatuses,
-  invoiceStatus,
+  educationalStatus,
   classroomHoldingTypes
 }
 

+ 231 - 0
src/components/EventItem.vue

@@ -0,0 +1,231 @@
+<template>
+  <q-card flat
+          class="event-item"
+          :class="{'isRegistered' : event.is_current_user_registered }">
+    <q-card-section class="thumbnail">
+      <router-link :to="{name: 'Public.ShowEventInfo', params: {id: event.id}}">
+        <div class="RegisteredSign">
+          <svg class="ribbon"
+               xmlns="http://www.w3.org/2000/svg"
+               width="123.945"
+               height="123.422"
+               viewBox="0 0 123.945 123.422">
+            <g id="Group_11524"
+               data-name="Group 11524"
+               transform="translate(-361 -1111)">
+              <g id="Group_7903"
+                 data-name="Group 7903"
+                 transform="translate(361 1111)">
+                <g id="Group_7901"
+                   data-name="Group 7901"
+                   transform="translate(0 0)">
+                  <rect id="Rectangle_1771"
+                        data-name="Rectangle 1771"
+                        width="13.986"
+                        height="39.668"
+                        transform="translate(0 83.754)"
+                        fill="#cfb184" />
+                  <rect id="Rectangle_1772"
+                        data-name="Rectangle 1772"
+                        width="39.668"
+                        height="13.986"
+                        transform="translate(84.277 0)"
+                        fill="#cfb184" />
+                  <path id="Path_18668"
+                        data-name="Path 18668"
+                        d="M1938.931,282.651l123.945-123.406-.016-.016h-44a9.041,9.041,0,0,0-6.379,2.631l-70.907,70.6a8.962,8.962,0,0,0-2.642,6.352Z"
+                        transform="translate(-1938.931 -159.229)"
+                        fill="#eac38a" />
+                </g>
+              </g>
+              <text id="دوره_های_من"
+                    data-name="دوره‌های من"
+                    transform="translate(445.063 1135.174) rotate(-45)"
+                    fill="#fff"
+                    font-size="18"
+                    font-family="SegoeUI-Bold, Segoe UI"
+                    font-weight="700"><tspan x="0"
+                                             y="0">دوره‌های من</tspan></text>
+            </g>
+          </svg>
+        </div>
+        <q-img :src="event.thumbnail" />
+      </router-link>
+    </q-card-section>
+    <div class="infos">
+      <div class="title ellipsis-2-lines">
+        <router-link :to="{name: 'Public.ShowEventInfo', params: {id: event.id}}">
+          {{event.title}}
+        </router-link>
+      </div>
+      <div class="date">
+        <q-icon name="calendar_month"
+                size="20px" />
+        {{ miladiToShamsi() }}
+      </div>
+    </div>
+    <q-card-actions class="action-section">
+      <q-btn color="primary"
+             class="btn-show-event"
+             :to="{name: 'Public.ShowEventInfo', params: {id: event.id}}">
+        مشاهده و ثبت نام
+        <svg xmlns="http://www.w3.org/2000/svg"
+             width="18.387"
+             height="11.502"
+             viewBox="0 0 18.387 11.502">
+          <path id="Combined_Shape"
+                data-name="Combined Shape"
+                d="M-9.338,11.408a.748.748,0,0,0,.388-.656V6.5h8.2A.751.751,0,0,0,0,5.75.75.75,0,0,0-.75,5h-8.2V.75A.751.751,0,0,0-9.338.093.769.769,0,0,0-9.7,0a.735.735,0,0,0-.4.115l-7.938,5a.747.747,0,0,0-.35.635.742.742,0,0,0,.35.634l7.938,5a.751.751,0,0,0,.4.116A.746.746,0,0,0-9.338,11.408ZM-10.45,9.392l-5.78-3.641,5.78-3.642Z"
+                transform="translate(18.387)"
+                fill="#fff" />
+        </svg>
+      </q-btn>
+    </q-card-actions>
+  </q-card>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { Event } from 'src/models/Event.js'
+import Assist from 'src/assets/js/Assist.js'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  props: {
+    event: {
+      type: Event,
+      default: new Event()
+    }
+  },
+  data() {
+    return {
+      size: 'xs',
+      sizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
+      actionTypes: ['event', 'scroll', 'link'],
+      defaultOptions: {
+        imageSource: null,
+        ratio: null,
+        hasAction: false,
+        useAEEEvent: false,
+        AEEEventBody: {
+          id: '-',
+          name: '-',
+          creative: null,
+          position: null
+        },
+        action: {
+          name: null,
+          route: null,
+          scrollTo: null,
+          eventName: null,
+          eventArgs: null
+        },
+        xs: {
+          height: null,
+          width: null,
+          src: null
+        },
+        sm: {
+          height: null,
+          width: null,
+          src: null
+        },
+        md: {
+          height: null,
+          width: null,
+          src: null
+        },
+        lg: {
+          height: null,
+          width: null,
+          src: null
+        },
+        xl: {
+          height: null,
+          width: null,
+          src: null
+        }
+      }
+    }
+  },
+  methods: {
+    getHoldingType (event) {
+      return new Event(event).holding_type_info.label
+    },
+    miladiToShamsi () {
+      if (!this.event.publish_time) {
+        return '-'
+      }
+      return Assist.miladiToShamsi(this.event.publish_time, 'jD jMMMM jYYYY ساعت HH:mm')
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.event-item {
+  width: 100%;
+  background-color: #F6F6F6;
+  border-radius: 8px;
+  position: relative;
+  z-index: 1;
+
+  &.isRegistered {
+    .thumbnail {
+      position: relative;
+      z-index: 1;
+      .RegisteredSign {
+        position: absolute;
+        right: 5px;
+        top: 7px;
+        z-index: 2;
+        display: block;
+      }
+    }
+  }
+  .thumbnail {
+    padding: 0;
+    .RegisteredSign {
+      display: none;
+    }
+  }
+  .infos {
+    position: absolute;
+    left: 0;
+    bottom: 60px;
+    width: 100%;
+    z-index: 2;
+    .title {
+      padding: 0 15px;
+      text-align: left;
+      margin-bottom: 49px;
+      a {
+        color: white;
+        font-size: 16px;
+        font-weight: bold;
+      }
+    }
+    .date {
+      padding: 0 15px;
+      text-align: left;
+      margin-bottom: 24px;
+      color: white;
+      font-size: 16px;
+      font-weight: normal;
+      .q-icon {
+        color: #EAC38A;
+        margin-right: 12.5px;
+      }
+    }
+  }
+  .action-section {
+    padding: 0;
+    .btn-show-event {
+      width: 100%;
+      height: 60px;
+      border-top-right-radius: 0;
+      border-top-left-radius: 0;
+    }
+  }
+}
+</style>

+ 6 - 0
src/components/Template/Header/MainHeader.vue

@@ -45,6 +45,12 @@
                           clickable>
                     <q-item-section>کتب سطح یک</q-item-section>
                   </q-item>
+                  <q-item v-close-popup
+                          href="https://formafzar.com/form/qk8oq"
+                          target="_blank"
+                          clickable>
+                    <q-item-section>کتب سطح چهار</q-item-section>
+                  </q-item>
                   <q-item v-close-popup
                           href="https://formafzar.com/form/ch1om"
                           target="_blank"

+ 16 - 0
src/components/Template/SideBard/AdminPanelSideBar.vue

@@ -61,6 +61,22 @@ export default {
             }
           ]
         },
+        {
+          title: 'رویدادها',
+          icon: 'import_contacts',
+          routeName: null,
+          show: true,
+          open: true,
+          active: false,
+          children: [
+            {
+              title: 'لیست رویدادها',
+              routeName: 'Admin.Event.Index',
+              show: true,
+              active: false
+            }
+          ]
+        },
         {
           title: 'حلقه های مباحثاتی',
           roles: ['superuser', 'educational_department_head'],

+ 5 - 0
src/components/Template/SideBard/Main.vue

@@ -69,6 +69,11 @@ export default {
               externalLink: 'https://formafzar.com/form/iv9cq',
               show: true
             },
+            {
+              title: 'کتب سطح چهار',
+              externalLink: 'https://formafzar.com/form/qk8oq',
+              show: true
+            },
             {
               title: 'کتب جریان‌شناسی',
               externalLink: 'https://formafzar.com/form/ch1om',

+ 16 - 0
src/components/Template/SideBard/UserPanelSideBar.vue

@@ -69,6 +69,22 @@ export default {
             }
           ]
         },
+        {
+          title: 'رویدادها',
+          routeName: null,
+          icon: 'auto_stories',
+          show: true,
+          open: true,
+          active: false,
+          children: [
+            {
+              title: 'رویدادهای من',
+              routeName: 'UserPanel.Profile.AllEvents',
+              show: true,
+              active: false
+            }
+          ]
+        },
         {
           title: 'درخواست ها',
           routeName: 'UserPanel.Ticket.List',

+ 169 - 0
src/components/Widgets/Event/AdminEventCreate/AdminEventCreate.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="AdminEventCreate"
+       :style="localOptions.style">
+    <div class="flex justify-end">
+      <q-btn flat
+             color="grey"
+             @click="$router.go(-1)">
+        بازگشت
+        >
+      </q-btn>
+    </div>
+    <entity-create v-if="mounted"
+                   ref="entityCreate"
+                   v-model:value="inputs"
+                   title="ایجاد پست"
+                   :api="api"
+                   :entity-id-key="entityIdKey"
+                   :entity-param-key="entityParamKey"
+                   :show-route-name="showRouteName"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false" />
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityCreate } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import BtnControl from 'src/components/Control/btn.vue'
+import PostMixin from 'src/components/Widgets/Post/PostMixin.js'
+import PostCategorySelector from 'src/components/FormBuilderCustumComponents/PostCategorySelector.vue'
+import FormBuilderTiptapEditor from 'src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue'
+// import { FormBuilderAssist } from 'quasar-form-builder'
+
+const BtnControlComp = shallowRef(BtnControl)
+const PostCategorySelectorComp = shallowRef(PostCategorySelector)
+const FormBuilderTiptapEditorComp = shallowRef(FormBuilderTiptapEditor)
+
+export default {
+  name: 'AdminEventCreate',
+  components: { EntityCreate },
+  mixins: [mixinWidget, PostMixin],
+  data () {
+    const authorization = 'Bearer ' + this.$store.getters['Auth/accessToken']
+    const uploadVideo = this.uploadVideo
+    const uploadAudio = this.uploadAudio
+    return {
+      mounted: false,
+      entityLoading: false,
+      api: APIGateway.post.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Post.Show',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-12' },
+        { type: 'input', name: 'order', responseKey: 'order', label: 'ترتیب', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: PostCategorySelectorComp, name: 'category', responseKey: 'category', col: 'col-12' },
+        {
+          // type: 'tiptap-editor',
+          type: FormBuilderTiptapEditorComp,
+          name: 'summary',
+          responseKey: 'summary',
+          label: 'خلاصه',
+          options: {
+            bubbleMenu: false,
+            floatingMenu: false,
+            poem: false,
+            reading: false,
+            loadBareHtml: true,
+            persianKeyboard: true,
+            mathliveOptions: { smartFence: false },
+            uploadServer: {
+              url: '/api' + APIGateway.media.APIAdresses.base,
+              instantUpload: true,
+              responseKey: 'file',
+              headers: { Authorization: authorization }
+            },
+            uploadVideo,
+            uploadAudio
+          },
+          col: 'col-md-12 col-12'
+        },
+        {
+          // type: 'tiptap-editor',
+          type: FormBuilderTiptapEditorComp,
+          name: 'text',
+          responseKey: 'text',
+          label: 'متن',
+          options: {
+            bubbleMenu: false,
+            floatingMenu: false,
+            poem: false,
+            reading: false,
+            loadBareHtml: true,
+            persianKeyboard: true,
+            mathliveOptions: { smartFence: false },
+            uploadServer: {
+              url: '/api' + APIGateway.media.APIAdresses.base,
+              instantUpload: true,
+              responseKey: 'file',
+              headers: { Authorization: authorization }
+            },
+            uploadVideo,
+            uploadAudio
+          },
+          col: 'col-md-12 col-12'
+        },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ایجاد محتوا', placeholder: ' ', atClick: () => {}, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted () {
+    this.setActionBtn()
+    // const options = FormBuilderAssist.getInputsByName(this.inputs, 'text').options
+    // options.uploadVideo = this.uploadVideo
+    // FormBuilderAssist.setAttributeByName(this.inputs, 'text', 'options', options)
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].atClick = this.onSubmit
+        }
+      })
+    },
+    onSubmit () {
+      this.create()
+    },
+    create() {
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity()
+        .then(() => {
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminEventCreate {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+    position: relative;
+    .back-action {
+      position: absolute;
+      right: 0;
+      top: 0;
+    }
+  }
+  :deep(.form) {
+    padding: 24px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Event/AdminEventCreate/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 215 - 0
src/components/Widgets/Event/AdminEventList/AdminEventList.vue

@@ -0,0 +1,215 @@
+<template>
+  <div class="AdminEventList"
+       :style="localOptions.style">
+    <entity-index v-if="mounted"
+                  ref="entityIndex"
+                  v-model:value="inputs"
+                  title="لیست صورتحساب ها"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :create-route-name="createRouteName"
+                  :show-search-button="false"
+                  :show-expand-button="false"
+                  :show-reload-button="false">
+      <template #entity-index-table-cell="{inputData}">
+        <template v-if="inputData.col.name === 'number'">
+          {{ inputData.rowNumber }}
+        </template>
+        <template v-else-if="inputData.col.name === 'action'">
+          <q-btn color="primary"
+                 :to="{name: 'Admin.Invoice.Show', params: {id: inputData.props.row.id}}">
+            جزییات جزییات
+          </q-btn>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import Assist from 'assets/js/Assist.js'
+import { EntityIndex } from 'quasar-crud'
+import { Invoice } from 'src/models/Invoice.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import BtnControl from 'src/components/Control/btn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'AdminEventList',
+  components: { EntityIndex },
+  mixins: [mixinWidget],
+  data () {
+    return {
+      api: APIGateway.invoice.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [
+        {
+          type: 'dateTime',
+          name: 'creation_time__gte',
+          label: 'از تاریخ',
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        {
+          type: 'dateTime',
+          name: 'creation_time__lt',
+          label: 'تا تاریخ',
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: 'select', name: 'status', options: (new Invoice()).statusEnums, label: 'وضعیت', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'type', options: (new Invoice()).typeEnums, label: 'نوع', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'owner__firstname', label: 'نام سفارش دهنده', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'owner__lastname', label: 'نام خانوادگی سفارش دهنده', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'owner__national_code', label: 'کدملی سفارش دهنده', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-3 col-12' }
+      ],
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'سفارش دهنده',
+            align: 'left',
+            field: row => row.owner_info.firstname + ' ' + row.owner_info.lastname
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'کدملی سفارش دهنده',
+            align: 'left',
+            field: row => row.owner_info.national_code
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'عنوان',
+            align: 'left',
+            field: row => this.getInvoiceTitle(row)
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'نوع صورتحساب',
+            align: 'left',
+            field: row => (new Invoice(row)).type_info.label
+          },
+          {
+            name: 'amount',
+            required: true,
+            label: 'مبلغ تراکنش(ریال)',
+            align: 'left',
+            field: row => row.amount.toLocaleString('fa')
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'وضعیت تراکنش',
+            align: 'left',
+            field: row => (new Invoice(row)).status_info.label
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'زمان ثبت',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.creation_time)
+          },
+          {
+            name: 'last_modification_time',
+            required: true,
+            label: 'زمان آخرین بروز رسانی',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.last_modification_time)
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: row => ''
+          }
+        ]
+      },
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    getInvoiceTitle (invoice) {
+      if (invoice.item_info.item_type === 'CLASSROOM') {
+        return invoice.item_info.title
+      } else {
+        return invoice.item_info.title || '-'
+      }
+    },
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].atClick = this.search
+        }
+      })
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminEventList {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+  }
+  :deep(.list) {
+    .quasar-crud-index-table {
+      padding: 0;
+      .q-table__container {
+        background-color: transparent;
+        box-shadow: none;
+        border: none;
+        .q-table__top {
+          display: none;
+        }
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Event/AdminEventList/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 127 - 0
src/components/Widgets/Event/AdminEventShow/AdminEventShow.vue

@@ -0,0 +1,127 @@
+<template>
+  <q-skeleton v-if="event.loading"
+              type="text"
+              width="200px" />
+  <breadcrumbs v-else
+               style="margin-top: 29px; margin-bottom: 19px;" />
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <q-card class="AdminEventShow">
+    <q-tabs v-model="tab"
+            dense
+            class="text-grey"
+            active-color="primary"
+            indicator-color="primary"
+            align="justify"
+            narrow-indicator>
+      <q-tab name="eventInfo"
+             label="مشخصات" />
+      <q-tab name="projects"
+             label="پروژه‌ها" />
+      <q-tab name="movies2"
+             label="آزمون" />
+      <q-tab name="members"
+             label="اندیشه جویان" />
+      <q-tab name="activity_sheet"
+             label="فعالیت کلاسی" />
+      <q-tab name="live_streaming_url"
+             label="بخش آنلاین" />
+    </q-tabs>
+
+    <q-separator />
+
+    <q-tab-panels v-model="tab"
+                  animated>
+      <q-tab-panel name="eventInfo"
+                   class="q-pa-none">
+        <event-info v-model:event="event"
+                    :event-id="$route.params.id" />
+      </q-tab-panel>
+
+      <q-tab-panel name="projects"
+                   class="q-pa-none">
+        <project-list :event-id="$route.params.id" />
+      </q-tab-panel>
+      <q-tab-panel name="movies2">
+        آزمون
+      </q-tab-panel>
+      <q-tab-panel name="members"
+                   class="q-pa-none">
+        <members-list :event-id="$route.params.id" />
+      </q-tab-panel>
+      <q-tab-panel name="activity_sheet"
+                   class="q-pa-none">
+        <activity-sheet-list :event-id="$route.params.id" />
+      </q-tab-panel>
+      <q-tab-panel name="live_streaming_url">
+        <live-streaming :event="event" />
+      </q-tab-panel>
+    </q-tab-panels>
+  </q-card>
+</template>
+
+<script>
+import { Event } from 'src/models/Event.js'
+import ProjectList from './components/ProjectList.vue'
+import MembersList from './components/MembersList.vue'
+import EventInfo from './components/EventInfo.vue'
+import LiveStreaming from './components/LiveStreaming.vue'
+import ActivitySheetList from './components/ActivitySheetList.vue'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+
+export default {
+  name: 'Admin.Event.Show',
+  components: {
+    ProjectList,
+    MembersList,
+    Breadcrumbs,
+    EventInfo,
+    LiveStreaming,
+    ActivitySheetList
+  },
+  data () {
+    return {
+      tab: 'eventInfo',
+      event: new Event()
+    }
+  },
+  computed: {
+    eventId () {
+      return this.event.id
+    }
+  },
+  watch: {
+    eventId (newValue) {
+      if (!newValue) {
+        return
+      }
+
+      this.updateBreadcrumbs()
+    }
+  },
+  methods: {
+    updateBreadcrumbs () {
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'رویدادها',
+            to: { name: 'Admin.Event.Index' }
+          },
+          {
+            label: this.event.title,
+            to: { name: 'Admin.Event.Show', params: { id: this.$route.params.id } }
+          }
+        ]
+      })
+    }
+  }
+}
+</script>

+ 36 - 0
src/components/Widgets/Event/AdminEventShow/OptionPanel.vue

@@ -0,0 +1,36 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <q-option-group v-model="localOptions.classroomType"
+                      :options="classroomTypeOptions" />
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel],
+  data () {
+    return {
+      classroomTypeOptions: [
+        {
+          label: 'رویداد',
+          value: 'Classroom'
+        },
+        {
+          label: 'حلقه مباحثاتی',
+          value: 'DiscussionCircles'
+        }
+      ]
+    }
+  }
+})
+</script>

+ 168 - 0
src/components/Widgets/Event/AdminEventShow/components/ActivitySheetList.vue

@@ -0,0 +1,168 @@
+<template>
+  <entity-index v-if="mounted"
+                ref="activitySheetList"
+                v-model:value="activitySheetListInputs"
+                title="فعالیت کلاسی"
+                :api="activitySheetListApi"
+                :table="activitySheetListTable"
+                :table-keys="activitySheetListTableKeys"
+                :show-search-button="false"
+                :show-reload-button="false"
+                :show-expand-button="false">
+    <template v-slot:entity-index-table-cell="{inputData, showConfirmRemoveDialog}">
+      <template v-if="inputData.col.name === 'actions'">
+        <div class="action-column-entity-index">
+          <q-btn size="md"
+                 color="primary"
+                 outline
+                 label="سابقه آموزشی"
+                 class="q-mr-md" />
+          <!--                :to="{name: 'Admin.Session.Show', params: {id: inputData.props.row.id}}"-->
+          <delete-btn @click="showConfirmRemoveDialog(inputData.props.row, 'id', getRemoveMessage(inputData.props.row))" />
+        </div>
+      </template>
+      <template v-else>
+        {{ inputData.col.value }}
+      </template>
+    </template>
+  </entity-index>
+  <q-skeleton v-else
+              type="rect"
+              height="200px" />
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'ActivitySheetList',
+  components: {
+    DeleteBtn,
+    EntityIndex
+  },
+  props: {
+    eventId: {
+      type: Number,
+      default: null
+    }
+  },
+  data () {
+    const eventId = this.eventId
+    return {
+      mounted: false,
+      createInvoiceLoading: false,
+
+      activitySheetListInputs: [
+        { type: 'hidden', name: 'event', value: eventId },
+        { type: 'select', name: 'status', value: null, label: 'وضعیت', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
+      ],
+      activitySheetListApi: APIGateway.event.APIAdresses.activitySheet,
+      activitySheetListTable: {
+        columns: [
+          {
+            name: 'id',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'owner_info.fullname',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.owner_info.firstname + ' ' + row.owner_info.lastname
+          },
+          {
+            name: 'owner_info.fullname',
+            required: true,
+            label: 'کد ملی',
+            align: 'left',
+            field: row => row.owner_info.national_code
+          },
+          {
+            name: 'is_project_done',
+            required: true,
+            label: 'پروژه',
+            align: 'left',
+            field: row => row.is_project_done
+          },
+          {
+            name: 'done_assignment_count',
+            required: true,
+            label: 'تکالیف انجام شده',
+            align: 'left',
+            field: row => row.done_assignment_count
+          },
+          {
+            name: 'is_allowed_absence_count',
+            required: true,
+            label: 'حضور و غیاب',
+            align: 'left',
+            field: row => row.is_allowed_absence_count // bool
+          },
+          {
+            name: 'attendance_score',
+            required: true,
+            label: 'نمره حضور و غیاب',
+            align: 'left',
+            field: row => row.attendance_score
+          },
+          {
+            name: 'final_test',
+            required: true,
+            label: 'آزمون',
+            align: 'left',
+            field: row => row.final_test // number - null
+          },
+          {
+            name: 'final_status',
+            required: true,
+            label: 'وضعیت نهایی',
+            align: 'left',
+            field: row => row.final_status
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'سابقه آموزشی',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      activitySheetListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted () {
+    this.setActivitySheetListActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActivitySheetListActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.activitySheetListInputs, 'btn', 'atClick', this.searchActivitySheetList)
+    },
+    searchActivitySheetList () {
+      this.$refs.activitySheet.search()
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.title + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>

+ 285 - 0
src/components/Widgets/Event/AdminEventShow/components/EventInfo.vue

@@ -0,0 +1,285 @@
+<template>
+  <entity-edit v-if="mounted"
+               ref="eventEntityEdit"
+               :key="eventEntityEditKey"
+               v-model:value="inputs"
+               title="مشخصات رویداد"
+               :api="api"
+               :show-close-button="false"
+               :show-edit-button="false"
+               :show-expand-button="false"
+               :show-save-button="false"
+               :show-reload-button="false"
+               :after-load-input-data="afterLoadInputData">
+    <template #after-form-builder>
+      <div class="flex justify-end q-mt-md">
+        <q-btn color="primary"
+               label="بروزرسانی اطلاعات"
+               @click="updateClassroom" />
+      </div>
+    </template>
+  </entity-edit>
+  <q-skeleton v-else
+              type="rect"
+              height="200px" />
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityEdit } from 'quasar-crud'
+import { Event } from 'src/models/Event.js'
+import Enums from 'src/assets/Enums/Enums.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { Classroom } from 'src/models/Classroom.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
+
+const FormBuilderInputEditorComp = shallowRef(FormBuilderInputEditor)
+
+export default {
+  name: 'ClassroomInfo',
+  components: {
+    EntityEdit
+  },
+  props: {
+    eventId: {
+      type: Number,
+      default: null
+    },
+    event: {
+      type: Classroom,
+      default: new Classroom()
+    }
+  },
+  data () {
+    const eventId = this.eventId
+    return {
+      inputs: [
+        { type: 'separator', name: 'space', label: 'مشخصات رویدادها', className: 'custom-separator', col: 'col-12' },
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان رویداد', placeholder: ' ', col: 'col-12' },
+        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'آپلود عکس رویدادها', col: 'col-md-3 col-12' },
+        { type: 'file', name: 'codes', responseKey: 'codes', label: 'آیین نامه رویدادها', col: 'col-md-3 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
+        { type: 'select', name: 'category', responseKey: 'unit_info.category_info.id', options: (new Event()).categoryEnums, value: null, placeholder: ' ', label: 'دسته بندی', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'unit', responseKey: 'unit', options: [], value: null, placeholder: ' ', label: 'درس', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'holding_type', responseKey: 'holding_type', options: (new Event()).holding_typeEnums, value: null, placeholder: ' ', label: 'نوع برگزاری', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'price', responseKey: 'price', placeholder: ' ', label: 'هزینه برگزاری', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'live_streaming_url', responseKey: 'live_streaming_url', placeholder: ' ', label: 'لینک مکان مجازی رویدادها', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'event_address', responseKey: 'event_address', placeholder: ' ', label: 'آدرس مکان فیزیکی رویدادها', col: 'col-12' },
+        {
+          type: 'select',
+          name: 'holding_year',
+          responseKey: 'holding_year',
+          options: [1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410],
+          value: null,
+          label: 'انتخاب سال رویدادها',
+          placeholder: ' ',
+          col: 'col-md-3'
+        },
+        {
+          type: 'select',
+          name: 'holding_month',
+          responseKey: 'holding_month',
+          options: [
+            'فروردین',
+            'اردیبهشت',
+            'خرداد',
+            'تیر',
+            'مرداد',
+            'شهریور',
+            'مهر',
+            'آبان',
+            'آذر',
+            'دی',
+            'بهمن',
+            'اسفند'
+          ],
+          value: null,
+          label: 'انتخاب ماه رویدادها',
+          placeholder: ' ',
+          col: 'col-md-3'
+        },
+        { type: 'input', name: 'sessions_frequency', responseKey: 'sessions_frequency', placeholder: ' ', label: 'رویدادها تناوب', col: 'col-12' },
+        { type: 'select', name: 'professor', responseKey: 'professor', options: [], value: null, placeholder: ' ', label: 'استاد رویدادها', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'مشخصات اندیشه جو', className: 'custom-separator', col: 'col-12' },
+        { type: 'select', name: 'audience_status_wwwww', responseKey: 'audience_status_wwwww', options: [], placeholder: ' ', label: 'وضعیت(؟)', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'category', responseKey: 'category', options: [], placeholder: ' ', label: 'مجموعه(؟)', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'audience_gender_type', responseKey: 'audience_gender_type', options: (new Event()).audience_gender_typeEnums, placeholder: ' ', label: 'جنسیت', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'audience_bashgah_wwwww', responseKey: 'audience_bashgah_wwwww', options: [], placeholder: ' ', label: 'باشگاه(؟)', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'تنظیمات رویدادها', className: 'custom-separator', col: 'col-12' },
+        { type: 'input', name: 'capacity', responseKey: 'capacity', placeholder: ' ', label: 'ظرفیت گروه درسی', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'allowed_absence_count', responseKey: 'allowed_absence_count', placeholder: ' ', label: 'تعداد مجاز غیبت', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'effective_absence_coefficient', responseKey: 'effective_absence_coefficient', placeholder: ' ', label: 'ضریب نمره حضور و غیاب', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'mandatory_assignment_count', responseKey: 'mandatory_assignment_count', placeholder: ' ', label: 'تعداد تکالیف اجباری', col: 'col-md-3 col-12' },
+
+        { type: 'input', name: 'special_passing_mark', responseKey: 'special_passing_mark', placeholder: ' ', label: 'نمره قبولی مشروطی ها', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'minimum_conditional_passing_mark', responseKey: 'minimum_conditional_passing_mark', placeholder: ' ', label: 'نمره قبولی مشروط', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'minimum_clean_passing_mark', responseKey: 'minimum_clean_passing_mark', placeholder: ' ', label: 'نمره قبولی', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'ثبت نام و حذف و اضافه', className: 'custom-separator', col: 'col-12' },
+        { type: 'dateTime', name: 'beginning_enrollment_period', responseKey: 'beginning_enrollment_period', placeholder: ' ', label: 'تاریخ شروع پیش ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_enrollment_period', responseKey: 'ending_enrollment_period', placeholder: ' ', label: 'تاریخ پایان پیش ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'beginning_registration_period', responseKey: 'beginning_registration_period', placeholder: ' ', label: 'تاریخ شروع ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_registration_period', responseKey: 'ending_registration_period', placeholder: ' ', label: 'تاریخ پایان ثبت نام', col: 'col-md-6 col-12' },
+
+        { type: 'input', name: 'registration_period_refund_percent', responseKey: 'registration_period_refund_percent', placeholder: ' ', label: 'درصد برگشت هزینه انصراف در بازه ثبت نام', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_enrollment_period', responseKey: 'allow_dropping_on_enrollment_period', label: 'امکان حذف در بازه پیش ثبت نام', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_adding_on_registration_period', responseKey: 'allow_adding_on_registration_period', label: 'امکان اضافه کردن در بازه ثبت نام', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_registration_period', responseKey: 'allow_dropping_on_registration_period', label: 'امکان حذف ثبت نام در بازه ثبت نام', col: 'col-md-3 col-12' },
+
+        { type: 'dateTime', name: 'beginning_drop_add_period', responseKey: 'beginning_drop_add_period', placeholder: ' ', label: 'تاریخ شروع حذف و اضافه', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_drop_add_period', responseKey: 'ending_drop_add_period', placeholder: ' ', label: 'تاریخ پایان حذف و اضافه', col: 'col-md-6 col-12' },
+
+        { type: 'input', name: 'drop_add_period_refund_percent', responseKey: 'drop_add_period_refund_percent', placeholder: ' ', label: 'درصد برگشت هزینه انصراف در بازه حذف و اضافه', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_adding_on_drop_add_period', responseKey: 'allow_adding_on_drop_add_period', label: 'امکان اضافه شدن در بازه حذف و اضافه', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_drop_add_period', responseKey: 'allow_dropping_on_drop_add_period', label: 'امکان حذف در بازه حذف و اضافه', col: 'col-md-3 col-12' },
+
+        { type: 'dateTime', name: 'publish_time', responseKey: 'publish_time', placeholder: ' ', label: 'تاریخ انتشار', col: 'col-md-6 col-12' },
+
+        { type: 'separator', name: 'space', label: 'قوانین', className: 'custom-separator', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'rules', responseKey: 'rules', label: 'قوانین رویداد', col: 'col-12' },
+
+        { type: FormBuilderInputEditorComp, name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'specification', responseKey: 'specification', label: 'مشخصات', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'bulletin', responseKey: 'bulletin', label: 'تابلو اعلانات', col: 'col-12' },
+
+        // -----------------------------------------------------------------------------------------------------------
+
+        { type: 'separator', name: 'space', label: 'موارد زیر در طرح نبودند', className: 'custom-separator', col: 'col-12' },
+        { type: 'select', name: 'audience_role', responseKey: 'audience_role', options: Enums.groups, placeholder: ' ', label: 'نقش', col: 'col-md-4 col-12' },
+
+        { type: 'hidden', name: 'id', responseKey: 'id', label: 'id', col: 'col-md-3 col-12' }
+      ],
+      mounted: false,
+      eventEntityEditKey: Date.now(),
+      api: APIGateway.event.APIAdresses.byId(eventId)
+    }
+  },
+  computed: {
+    // selectedCategoryId () {
+    //   return FormBuilderAssist.getInputsByName(this.inputs, 'category').value
+    // },
+    localClassroom: {
+      get () {
+        return this.event
+      },
+      set (newValue) {
+        this.$emit('update:event', newValue)
+      }
+    }
+  },
+  // watch: {
+  //   selectedCategoryId () {
+  //     if (!this.mounted) {
+  //       return
+  //     }
+  //     FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'value', null)
+  //     this.getUnits(this.selectedCategoryId)
+  //   }
+  // },
+  mounted () {
+    this.localClassroom.loading = true
+    this.preLoadData()
+      .then(() => {
+        this.mounted = true
+      })
+      .catch(() => {
+        this.mounted = true
+      })
+  },
+  methods: {
+    afterLoadInputData (data) {
+      this.localClassroom = new Classroom(data)
+      this.localClassroom.loading = false
+      // const gg = data.beginning_enrollment_period.replace('T', ' ')
+      // this.setInputAttr('beginning_enrollment_period', 'value', gg)
+    },
+    setInputAttr (name, attr, value) {
+      FormBuilderAssist.setAttributeByName(this.inputs, name, attr, value)
+    },
+    preLoadData () {
+      return new Promise((resolve, reject) => {
+        APIGateway.event.get(this.eventId)
+          .then((event) => {
+            this.setInputAttr('category', 'value', event.category)
+            this.setInputAttr('unit', 'value', event.unit)
+            this.beforeLoadInputData(event)
+              .then((event) => {
+                resolve(event)
+              })
+              .catch(() => {
+                reject()
+              })
+          })
+          .catch(() => {
+            reject()
+          })
+      })
+    },
+    beforeLoadInputData (/* responseData */) {
+      return new Promise((resolve, reject) => {
+        const promise1 = this.getProfessors()
+        const promise2 = this.getCategories()
+        const promise3 = this.getUnits()
+        // const promise3 = this.getUnits(responseData.unit_info.category)
+        this.$nextTick(() => {
+          Promise.all([promise1, promise2, promise3])
+            .then(() => {
+              // this.setInputAttr('category', 'value', responseData.unit_info.category)
+              // this.setInputAttr('unit', 'value', responseData.unit)
+              this.eventEntityEditKey = Date.now()
+              resolve()
+            })
+            .catch(() => {
+              reject()
+            })
+        })
+      })
+    },
+    getProfessors () {
+      return APIGateway.user.index({ per_page: 9999, role: 'professor' })
+        .then((users) => {
+          this.setInputAttr('professor', 'options', users.list.list.map(item => {
+            return {
+              value: item.id,
+              label: this.getUserFullname(item)
+            }
+          }))
+        })
+        .catch(() => {})
+    },
+    getCategories () {
+      return APIGateway.unitCategory.index({ per_page: 9999 })
+        .then((categories) => {
+          this.setInputAttr('category', 'options', categories.list.list.map(item => {
+            return {
+              value: item.id,
+              label: item.title
+            }
+          }))
+        })
+        .catch(() => {})
+    },
+    getUnits (selectedcategoryId) {
+      return APIGateway.unit.index({ per_page: 9999, category: selectedcategoryId })
+        .then((units) => {
+          this.setInputAttr('unit', 'options', units.list.list.map(item => {
+            return {
+              value: item.id,
+              label: item.title
+            }
+          }))
+        })
+        .catch(() => {})
+    },
+    getUserFullname (user) {
+      return user.firstname + ' ' + user.lastname
+    },
+    updateClassroom () {
+      this.$refs.eventEntityEdit.editEntity()
+    }
+  }
+}
+</script>

+ 35 - 0
src/components/Widgets/Event/AdminEventShow/components/LiveStreaming.vue

@@ -0,0 +1,35 @@
+<template>
+  <div v-if="event.loading">
+    کمی صبر کنید...
+  </div>
+  <div v-else>
+    <q-btn class="full-width q-mb-lg"
+           color="primary"
+           @click="goToLiveStreamUrl">
+      رفتن به کلاس
+    </q-btn>
+    <iframe :src="event.live_streaming_url"
+            title="W3Schools Free Online Web Tutorials"
+            style="height: 80vh"
+            class="full-width" />
+  </div>
+</template>
+
+<script>
+import { Event } from 'src/models/Event.js'
+
+export default {
+  name: 'LiveStreaming',
+  props: {
+    event: {
+      type: Event,
+      default: new Event()
+    }
+  },
+  methods: {
+    goToLiveStreamUrl () {
+      window.open(this.event.live_streaming_url, '_blank')
+    }
+  }
+}
+</script>

+ 225 - 0
src/components/Widgets/Event/AdminEventShow/components/MembersList.vue

@@ -0,0 +1,225 @@
+<template>
+  <entity-index v-if="mounted"
+                ref="membersList"
+                v-model:value="membersListInputs"
+                title="لیست کاربران"
+                :api="membersListApi"
+                :table="membersListTable"
+                :table-keys="membersListTableKeys"
+                :show-search-button="false"
+                :show-reload-button="false"
+                :show-expand-button="false">
+    <template #toolbar>
+      <q-btn color="primary"
+             :loading="exportReportLoading"
+             @click="getMembersExcel">
+        خروجی اکسل
+      </q-btn>
+    </template>
+    <template v-slot:entity-index-table-cell="{inputData}">
+      <template v-if="inputData.col.name === 'number'">
+        {{ inputData.rowNumber }}
+      </template>
+      <template v-else-if="inputData.col.name === 'action'">
+        <div class="action-column-entity-index">
+          <q-btn v-if="inputData.props.row.status !== 'DROPPED_BY_ADMIN' && inputData.props.row.status !== 'DROPPED_BY_ITSELF'"
+                 size="md"
+                 color="red"
+                 outline
+                 label="انصراف"
+                 :loading="dropEventByAdminLoading"
+                 class="q-mr-md"
+                 @click="dropEventByAdmin(inputData.props.row)" />
+          <q-btn v-if="inputData.props.row.status === 'ENROLLED'"
+                 size="md"
+                 color="primary"
+                 outline
+                 label="ایجاد صورتحساب"
+                 :loading="createInvoiceLoading"
+                 class="q-mr-md"
+                 @click="createInvoiceForEvent(inputData.props.row)" />
+        </div>
+      </template>
+      <template v-else>
+        {{ inputData.col.value }}
+      </template>
+    </template>
+  </entity-index>
+  <q-skeleton v-else
+              type="rect"
+              height="200px" />
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { Event } from 'src/models/Event.js'
+import Assist from 'src/assets/js/Assist.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'MembersList',
+  components: {
+    EntityIndex
+  },
+  props: {
+    eventId: {
+      type: Number,
+      default: null
+    }
+  },
+  data () {
+    const eventId = this.eventId
+    return {
+      mounted: false,
+      createInvoiceLoading: false,
+      event: new Event(),
+
+      dropEventByAdminLoading: false,
+      exportReportLoading: false,
+      membersListInputs: [
+        { type: 'hidden', name: 'event', value: eventId },
+        { type: 'select', name: 'status', options: [{ label: 'ثبت نام شده', value: 'REGISTERED' }, { label: 'پیش ثبت نام شده', value: 'ENROLLED' }, { label: 'حذف توسط اندیشه جو', value: 'DROPPED_BY_ITSELF' }, { label: 'حذف توسط ادمین', value: 'DROPPED_BY_ADMIN' }], label: 'وضعیت', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'invoice__status', options: [{ label: 'پرداخت کامل', value: 'PAID_FULL' }, { label: 'پرداخت قسطی', value: 'PAYING_IN_INSTALMENT' }], label: 'نوع صورتحساب', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
+      ],
+      membersListApi: APIGateway.event.APIAdresses.members,
+      membersListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'owner_info.fullname',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.owner_info.firstname + ' ' + row.owner_info.lastname
+          },
+          {
+            name: 'owner_info.national_code',
+            required: true,
+            label: 'کد ملی',
+            align: 'left',
+            field: row => row.owner_info.national_code
+          },
+          {
+            name: 'owner_info.mobile_number',
+            required: true,
+            label: 'تلفن همراه',
+            align: 'left',
+            field: row => row.owner_info.mobile_number
+          },
+          {
+            name: 'owner_info.email',
+            required: true,
+            label: 'ایمیل',
+            align: 'left',
+            field: row => row.owner_info.email
+          },
+          {
+            name: 'owner_info.email',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => (new Event()).getUserRegisterInfoLabel(row.status)
+          },
+          {
+            name: 'action',
+            required: true,
+            label: ' ',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      membersListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted () {
+    this.setMembersListActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setMembersListActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.membersListInputs, 'btn', 'atClick', this.searchMembersList)
+    },
+    searchMembersList () {
+      this.$refs.membersList.search()
+    },
+    getMembersExcel () {
+      this.exportReportLoading = true
+      const status = FormBuilderAssist.getInputsByName(this.membersListInputs, 'status').value ? FormBuilderAssist.getInputsByName(this.membersListInputs, 'status').value : null
+      APIGateway.registration.exportReport({
+        event: this.$route.params.id,
+        type: 'users',
+        status
+      })
+        .then((xlsxData) => {
+          Assist.saveXlsx(xlsxData, this.event.title)
+          this.exportReportLoading = false
+        })
+        .catch((e) => {
+          this.exportReportLoading = false
+        })
+    },
+    dropEventByAdmin (row) {
+      const eventId = row.event
+      const userId = row.owner
+      const message = 'آیا از انصراف (' + row.owner_info.firstname + ' ' + row.owner_info.lastname + ') اطمینان دارید؟'
+      this.$q.dialog({
+        title: 'توجه',
+        message,
+        html: true,
+        cancel: true,
+        persistent: true
+      }).onOk(() => {
+        this.dropEventByAdminLoading = true
+        APIGateway.event.dropByAdmin(eventId, userId)
+          .then(() => {
+            this.dropEventByAdminLoading = false
+            this.searchMembersList()
+          })
+          .catch(() => {
+            this.dropEventByAdminLoading = false
+          })
+      })
+    },
+    createInvoiceForEvent (row) {
+      const eventId = row.event
+      const userId = row.owner
+      this.createInvoiceLoading = true
+      APIGateway.event.createInvoiceByAdmin(eventId, userId)
+        .then((invoice) => {
+          this.$router.push({ name: 'Admin.Invoice.Show', params: { id: invoice.id } })
+          this.createInvoiceLoading = false
+        })
+        .catch(() => {
+          this.createInvoiceLoading = false
+        })
+    }
+  }
+}
+</script>

+ 176 - 0
src/components/Widgets/Event/AdminEventShow/components/ProjectList.vue

@@ -0,0 +1,176 @@
+<template>
+  <entity-index v-if="mounted"
+                ref="projectList"
+                v-model:value="projectListInputs"
+                title="لیست پروژه ها"
+                :api="projectListApi"
+                :table="projectListTable"
+                :table-keys="projectListTableKeys"
+                :show-search-button="false"
+                :show-reload-button="false"
+                :show-expand-button="false">
+    <template #toolbar>
+      <q-btn color="primary"
+             outline
+             :loading="addNewProjectLoading"
+             @click="addNewProject">
+        افزودن پروژه جدید
+      </q-btn>
+    </template>
+    <template v-slot:entity-index-table-cell="{inputData, showConfirmRemoveDialog}">
+      <template v-if="inputData.col.name === 'number'">
+        {{ inputData.rowNumber }}
+      </template>
+      <template v-else-if="inputData.col.name === 'actions'">
+        <div class="action-column-entity-index">
+          <q-btn size="md"
+                 color="primary"
+                 label="جزییات"
+                 :to="{name: 'Admin.Event.Project.Show', params: {event_id: $route.params.id, project_id: inputData.props.row.id}}"
+                 class="q-mr-md" />
+          <delete-btn @click="showConfirmRemoveDialog(inputData.props.row, 'id', getRemoveMessage(inputData.props.row))" />
+        </div>
+      </template>
+      <template v-else-if="inputData.col.name === 'members'">
+        <div class="action-column-entity-index">
+          <q-btn size="md"
+                 color="primary"
+                 outline
+                 label="بررسی پروژه"
+                 :to="{name: 'Admin.Event.Project.Members', params: {event_id: $route.params.id, project_id: inputData.props.row.id}}"
+                 class="q-mr-md" />
+        </div>
+      </template>
+      <template v-else>
+        {{ inputData.col.value }}
+      </template>
+    </template>
+  </entity-index>
+  <q-skeleton v-else
+              type="rect"
+              height="200px" />
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import { Project } from 'src/models/Project.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+
+export default {
+  name: 'ProjectList',
+  components: {
+    DeleteBtn,
+    EntityIndex
+  },
+  props: {
+    eventId: {
+      type: Number,
+      default: null
+    }
+  },
+  data () {
+    const eventId = this.eventId
+    return {
+      mounted: false,
+      addNewProjectLoading: false,
+
+      projectListInputs: [
+        { type: 'hidden', name: 'event', value: eventId }
+      ],
+      projectListApi: APIGateway.project.APIAdresses.base,
+      projectListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'عنوان پروژه',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'type',
+            required: true,
+            label: 'نوع',
+            align: 'left',
+            field: row => (new Project(row)).type_info.label
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'زمان شروع پروژه',
+            align: 'left',
+            field: row => row.beginning_doing_period ? ShamsiDate.getDateTime(row.beginning_doing_period) : '-'
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'مهلت ارسال پروژه',
+            align: 'left',
+            field: row => row.ending_doing_period ? ShamsiDate.getDateTime(row.ending_doing_period) : '-'
+          },
+          {
+            name: 'members',
+            required: true,
+            label: 'بررسی',
+            align: 'left',
+            field: ''
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      projectListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    addNewProject () {
+      this.$router.push({ name: 'Admin.Event.Project.Create', params: { event_id: this.eventId } })
+      // this.addNewProjectLoading = true
+      // APIGateway.project.create({
+      //   title: '-',
+      //   event: this.eventId
+      // })
+      //   .then(() => {
+      //     this.$refs.projectList.search()
+      //     this.addNewProjectLoading = false
+      //   })
+      //   .catch(() => {
+      //     this.addNewProjectLoading = false
+      //   })
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.title + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>

+ 337 - 0
src/components/Widgets/Event/AllEvents/AllEvents.vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="AllEvents">
+    <div class="AllEvents-title">
+      <div>
+        <q-banner class="banner">
+          رویدادها
+        </q-banner>
+      </div>
+      <div class="AllEvents-filter">
+        <q-btn outline
+               color="primary"
+               class="btn-filter"
+               @click="openFilterDialog">
+          فیلتر
+          <svg xmlns="http://www.w3.org/2000/svg"
+               width="19.5"
+               height="19.501"
+               viewBox="0 0 19.5 19.501"
+               class="q-ml-sm">
+            <g id="Filter_2"
+               data-name="Filter 2"
+               transform="translate(0 0)">
+              <path id="Fill_1"
+                    data-name="Fill 1"
+                    d="M8.146,19.5A1.151,1.151,0,0,1,7,18.346V13.774a4.39,4.39,0,0,0-1.194-3.022.717.717,0,0,1-.066-.062L.893,5.536A3.283,3.283,0,0,1,0,3.277V2.341A2.331,2.331,0,0,1,2.315,0H17.186A2.33,2.33,0,0,1,19.5,2.341v.936a3.294,3.294,0,0,1-.891,2.258l-4.847,5.154a4.39,4.39,0,0,0-1.242,3.073v2.287a2.058,2.058,0,0,1-1.066,1.808l-2.761,1.5A1.147,1.147,0,0,1,8.146,19.5ZM2.315,1.5a.829.829,0,0,0-.815.841v.936a1.785,1.785,0,0,0,.485,1.23L6.773,9.6a.648.648,0,0,1,.051.049A5.874,5.874,0,0,1,8.5,13.774v3.984l2.236-1.218a.557.557,0,0,0,.284-.491V13.762a5.859,5.859,0,0,1,1.659-4.109l4.837-5.145A1.789,1.789,0,0,0,18,3.277V2.341a.828.828,0,0,0-.814-.841Z"
+                    transform="translate(0 0)"
+                    fill="#475f4a" />
+            </g>
+          </svg>
+        </q-btn>
+        <q-input filled
+                 label="جستجوی رویداد "
+                 class="input-filter">
+          <template v-slot:append>
+            <svg xmlns="http://www.w3.org/2000/svg"
+                 width="20.265"
+                 height="20.722"
+                 viewBox="0 0 20.265 20.722">
+              <g id="Search"
+                 transform="translate(0)">
+                <path id="Combined_Shape"
+                      data-name="Combined Shape"
+                      d="M18.985,20.5l-3.169-3.16a9.755,9.755,0,1,1,1.1-1.026l3.132,3.124a.75.75,0,0,1-1.06,1.062ZM1.5,9.738A8.238,8.238,0,1,0,9.739,1.5,8.247,8.247,0,0,0,1.5,9.738Z"
+                      transform="translate(0 0)"
+                      fill="#475f4a" />
+              </g>
+            </svg>
+          </template>
+        </q-input>
+      </div>
+    </div>
+    <div v-if="displayTitleFilters.length > 0"
+         class="AllEvents-selected-filter">
+      فیلترها:
+      <q-chip v-for="(displayTitleFilter, displayTitleFilterIndex) in displayTitleFilters"
+              :key="displayTitleFilterIndex"
+              removable
+              outline
+              color="primary"
+              text-color="white"
+              @remove="clearFilterItem(displayTitleFilter.filterKey)">
+        {{ displayTitleFilter.label }}
+      </q-chip>
+    </div>
+    <div v-if="!events.loading && events.list.length > 0"
+         :key="eventsKey"
+         class="events row q-col-gutter-md">
+      <div v-for="event in events.list"
+           :key="event.id"
+           class="event-col col-md-4">
+        <event-item :event="event" />
+      </div>
+    </div>
+    <div v-else-if="!events.loading && events.list.length === 0">
+      رویدادی یافت نشد.
+    </div>
+    <div v-else-if="events.loading">
+      کمی صبر کنید ...
+    </div>
+    <q-dialog v-model="filterDialog"
+              :position="'top'"
+              full-width>
+      <filters v-model:filters="filter"
+               :units="units"
+               :professors="professors"
+               :categories="categories"
+               @doFilter="doFilter"
+               @cancelFilter="cancelFilter"
+               @onChangeCategory="onChangeCategory" />
+    </q-dialog>
+  </div>
+</template>
+
+<script>
+import { UnitList } from 'src/models/Unit.js'
+import Filters from './components/Filters.vue'
+import { mixinAuth } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { Event, EventList } from 'src/models/Event.js'
+import EventItem from 'src/components/EventItem.vue'
+import { UnitCategoryList } from 'src/models/UnitCategory.js'
+import { EventRegistrationList } from 'src/models/EventRegistration.js'
+
+export default {
+  name: 'AllEvents',
+  components: { EventItem, Filters },
+  mixins: [mixinAuth],
+  data: () => ({
+    loading: false,
+    units: new UnitList(),
+    eventsKey: Date.now(),
+    categories: new UnitCategoryList(),
+    userRegistrations: new EventRegistrationList(),
+    professors: [],
+    professorsLoading: false,
+    filter: {
+      unit__category: null,
+      unit: null,
+      eventStatus: null,
+      holding_type: null,
+      professor: null
+    },
+    filterDialog: false,
+    events: new EventList()
+  }),
+  computed: {
+    eventStatuses () {
+      return (new Event()).statusEnums
+    },
+    eventHoldingTypeOptions () {
+      return (new Event()).holding_typeEnums
+    },
+    displayTitleFilters () {
+      const filters = []
+      if (this.filter.unit__category) {
+        const targetCategory = this.categories.list.find(item => item.id === this.filter.unit__category)
+        if (targetCategory) {
+          filters.push({
+            filterKey: 'unit__category',
+            value: this.filter.unit__category,
+            label: targetCategory.title
+          })
+        }
+      }
+      if (this.filter.unit) {
+        const targetUnit = this.units.list.find(item => item.id === this.filter.unit)
+        if (targetUnit) {
+          filters.push({
+            filterKey: 'unit',
+            value: this.filter.unit,
+            label: targetUnit.title
+          })
+        }
+      }
+      if (this.filter.eventStatus) {
+        filters.push({
+          filterKey: 'eventStatus',
+          value: this.filter.eventStatus,
+          label: this.eventStatuses.find(item => item.value === this.filter.eventStatus).label
+        })
+      }
+      if (this.filter.holding_type) {
+        filters.push({
+          filterKey: 'holding_type',
+          value: this.filter.holding_type,
+          label: this.eventHoldingTypeOptions.find(item => item.value === this.filter.holding_type).label
+        })
+      }
+      if (this.filter.professor) {
+        const targetProfessor = this.professors.find(item => item.value === this.filter.professor)
+        filters.push({
+          filterKey: 'professor',
+          value: this.filter.professor,
+          label: targetProfessor.label
+        })
+      }
+
+      return filters
+    }
+  },
+  mounted () {
+    if (this.user && this.user.id !== null) {
+      this.getUserRegistrations()
+    }
+    this.getEvents()
+    this.getCategories()
+    this.getUnits()
+    this.getProfessors()
+  },
+  methods: {
+    getUserRegistrations () {
+      this.userRegistrations.loading = true
+      APIGateway.classroomRegistration.index()
+        .then((eventRegistrationList) => {
+          this.userRegistrations = new EventRegistrationList(eventRegistrationList.list)
+          this.eventsKey = Date.now()
+          this.userRegistrations.loading = false
+        })
+        .catch(() => {
+          this.userRegistrations.loading = false
+        })
+    },
+    isEventRegistered (eventId) {
+      return !!this.userRegistrations.list.find(item => item.event === eventId && (item.status === 'REGISTERED' || item.status === 'ENROLLED'))
+    },
+    getEvents () {
+      this.events.loading = true
+      this.filter.per_page = 9999
+      APIGateway.event.index(this.filter)
+        .then(eventList => {
+          this.events = new EventList(eventList.list)
+          this.events.loading = false
+        })
+        .catch(() => {
+          this.events.loading = false
+        })
+    },
+    getCategories () {
+      this.categories.loading = true
+      APIGateway.unitCategory.index()
+        .then(unitCategoryList => {
+          this.categories.loading = false
+          this.categories = new UnitCategoryList(unitCategoryList.list)
+        })
+        .catch(() => {
+          this.categories.loading = false
+        })
+    },
+    getProfessors () {
+      APIGateway.user.professors({ per_page: 9999 })
+        .then(({ list }) => {
+          this.professors = list.list.map(item => {
+            return {
+              value: item.id,
+              label: this.getUserFullname(item)
+            }
+          })
+        })
+    },
+    getUserFullname (user) {
+      return user.firstname + ' ' + user.lastname
+    },
+    getUnits (categoryId) {
+      this.units.loading = true
+      APIGateway.unit.index({ category: categoryId })
+        .then(unitList => {
+          this.units.loading = false
+          this.units = new UnitList(unitList.list)
+        })
+        .catch(() => {
+          this.units.loading = false
+        })
+    },
+    onChangeCategory () {
+      this.filter.unit = null
+      this.getUnits(this.filter.unit__category)
+    },
+    openFilterDialog () {
+      this.filterDialog = true
+    },
+    closeFilterDialog () {
+      this.filterDialog = false
+    },
+    cancelFilter () {
+      this.clearAllFilters()
+      this.closeFilterDialog()
+    },
+    doFilter () {
+      this.getEvents()
+      this.closeFilterDialog()
+    },
+    clearFilterItem (key) {
+      this.filter[key] = null
+      this.doFilter()
+    },
+    clearAllFilters () {
+      Object.keys(this.filter).forEach(filterKey => {
+        this.filter[filterKey] = null
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AllEvents {
+  padding: 60px 0;
+  .AllEvents-title {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    padding-bottom: 33px;
+  }
+  .AllEvents-selected-filter {
+    margin-bottom: 30px;
+  }
+  .AllEvents-filter {
+    display: flex;
+    flex-flow: row;
+    .btn-filter {
+      margin-right: 20px;
+    }
+  }
+  .input-filter {
+    &:deep {
+      .q-field__control {
+        border-radius: 8px;
+        .q-field__native {
+          padding-top: 8px;
+        }
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.AllEvents-widget.filter-card {
+  background-color: #F9F9F9;
+  .filter-card-section {
+    width: 1476px;
+    margin: auto;
+  }
+  .filter-card-header {
+    margin-bottom: 53px;
+    padding-bottom: 0;
+  }
+  .filter-card-filter-actions {
+    display: flex;
+    justify-content: flex-end;
+    margin-bottom: 65px;
+    button {
+      width: 158px;
+    }
+  }
+}
+</style>

+ 164 - 0
src/components/Widgets/Event/AllEvents/OptionPanel.vue

@@ -0,0 +1,164 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div class="option-panel-container">
+        <div class="row q-col-gutter-md">
+          <div class="input-container col-12 q-mt-md">
+
+            <div class="outsideLabel">آدرس فایل عکس</div>
+            <q-input v-model="localOptions.imageSource"
+                     label="image" />
+          </div>
+          <!--          <div class="input-container q-py-md">-->
+          <!--            <div class="outsideLabel">height</div>-->
+          <!--            <q-input v-model="localOptions.height"-->
+          <!--                     label="image" />-->
+          <!--          </div>-->
+          <div class="input-container col-4">
+            <div class="outsideLabel">size</div>
+            <q-select v-model="size"
+                      :options="sizeOptions"
+                      label="page size" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">عرض در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].width"
+                     label="width" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">ارتفاع در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].height"
+                     label="height" />
+          </div>
+          <div class="input-container col-12">
+            <div class="outsideLabel">لینک عکس در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].src"
+                     label="image link" />
+          </div>
+          <div class="action col-md-12">
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.useAEEEvent"
+                          label="استفاده از ایونت GTM"
+                          right-label />
+            </div>
+            <div v-if="localOptions.useAEEEvent"
+                 class="action-container q-gutter-lg-md">
+              <div>فیلد های مورد نظر ایونت GTM :</div>
+              <div class="col-9">
+                <div class="outsideLabel">id</div>
+                <q-input v-model="localOptions.AEEEventBody.id"
+                         label="id" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">name</div>
+                <q-input v-model="localOptions.AEEEventBody.name"
+                         label="name" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">creative</div>
+                <q-input v-model="localOptions.AEEEventBody.creative"
+                         label="creative" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">position</div>
+                <q-input v-model="localOptions.AEEEventBody.position"
+                         label="position" />
+              </div>
+            </div>
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.hasAction"
+                          label="hasAction"
+                          right-label />
+            </div>
+            <div v-if="localOptions.hasAction"
+                 class="action-container">
+              <div>
+                <q-select v-model="localOptions.action.name"
+                          :options="actionTypes" />
+              </div>
+              <div v-if="localOptions.action.name === 'link'">
+                <q-input v-model="localOptions.action.route"
+                         label="route" />
+              </div>
+              <div v-if="localOptions.action.name === 'event'">
+                <q-input v-model="localOptions.action.eventName"
+                         label="event name" />
+                <q-input v-model="localOptions.action.eventArgs"
+                         label="event args" />
+              </div>
+              <div v-if="localOptions.action.name === 'scroll'">
+                <q-input v-model="localOptions.action.scrollTo"
+                         label="scrollTo class" />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: { OptionPanelTabs },
+  mixins: [mixinOptionPanel],
+  data() {
+    return {
+      size: 'xs',
+      sizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
+      actionTypes: ['event', 'scroll', 'link'],
+      defaultOptions: {
+        imageSource: null,
+        ratio: null,
+        hasAction: false,
+        useAEEEvent: false,
+        AEEEventBody: {
+          id: '-',
+          name: '-',
+          creative: null,
+          position: null
+        },
+        action: {
+          name: null,
+          route: null,
+          scrollTo: null,
+          eventName: null,
+          eventArgs: null
+        },
+        xs: {
+          height: null,
+          width: null,
+          src: null
+        },
+        sm: {
+          height: null,
+          width: null,
+          src: null
+        },
+        md: {
+          height: null,
+          width: null,
+          src: null
+        },
+        lg: {
+          height: null,
+          width: null,
+          src: null
+        },
+        xl: {
+          height: null,
+          width: null,
+          src: null
+        }
+      }
+    }
+  }
+})
+</script>

+ 173 - 0
src/components/Widgets/Event/AllEvents/components/Filters.vue

@@ -0,0 +1,173 @@
+<template>
+  <q-card class="filter-card all-event-widget">
+    <q-card-section class="filter-card-header filter-card-section">
+      <div class="flex justify-between">
+        <q-banner class="banner">
+          فیلتر
+        </q-banner>
+        <q-btn flat
+               @click="cancelFilter">
+          <svg id="close_black_24dp"
+               xmlns="http://www.w3.org/2000/svg"
+               width="32"
+               height="32"
+               viewBox="0 0 32 32">
+            <path id="Path_29089"
+                  data-name="Path 29089"
+                  d="M0,0H32V32H0Z"
+                  fill="none" />
+            <path id="Path_29090"
+                  data-name="Path 29090"
+                  d="M22.412,5.806a1.314,1.314,0,0,0-1.86,0L14.1,12.243l-6.45-6.45a1.315,1.315,0,1,0-1.86,1.86l6.45,6.45-6.45,6.45a1.315,1.315,0,0,0,1.86,1.86l6.45-6.45,6.45,6.45a1.315,1.315,0,0,0,1.86-1.86l-6.45-6.45,6.45-6.45A1.322,1.322,0,0,0,22.412,5.806Z"
+                  transform="translate(1.897 1.897)"
+                  fill="#ababab" />
+          </svg>
+        </q-btn>
+      </div>
+    </q-card-section>
+    <q-card-section class="filter-card-filter-section filter-card-section">
+      <div class="row q-col-gutter-md">
+        <div class="col-md-3">
+          <div>گروه آموزشی</div>
+          <select-control v-model:value="localFilters.unit__category"
+                          :options="categories.list"
+                          :disable="categories.loading"
+                          :loading="categories.loading"
+                          optionValue="id"
+                          optionLabel="title"
+                          @update:model-value="onChangeCategory" />
+        </div>
+        <div class="col-md-3">
+          <div>درس</div>
+          <select-control v-model:value="localFilters.unit"
+                          :options="units.list"
+                          :disable="units.loading"
+                          :loading="units.loading"
+                          optionValue="id"
+                          optionLabel="title" />
+        </div>
+        <div class="col-md-3">
+          <div>وضعیت رویداد</div>
+          <select-control v-model:value="localFilters.eventStatus"
+                          :options="eventStatuses"
+                          optionValue="value"
+                          optionLabel="label" />
+        </div>
+        <div class="col-md-3">
+          <div>نوع برگزاری</div>
+          <select-control v-model:value="localFilters.holding_type"
+                          :options="eventHoldingTypeOptions"
+                          optionValue="value"
+                          optionLabel="label" />
+        </div>
+        <div class="col-md-3">
+          <div>استاد</div>
+          <select-control v-model:value="localFilters.professor"
+                          :options="professors"
+                          :disable="professorsLoading"
+                          :loading="professorsLoading"
+                          optionValue="value"
+                          optionLabel="label" />
+        </div>
+      </div>
+    </q-card-section>
+    <q-card-actions class="filter-card-filter-actions filter-card-section">
+      <q-btn color="primary"
+             outline
+             label="لغو فیلتر"
+             @click="cancelFilter" />
+      <q-btn color="primary"
+             label="فیلتر کردن"
+             @click="doFilter" />
+    </q-card-actions>
+  </q-card>
+</template>
+
+<script>
+import { Event } from 'src/models/Event.js'
+import { UnitList } from 'src/models/Unit.js'
+import { mixinAuth } from 'src/mixin/Mixins.js'
+import { UnitCategoryList } from 'src/models/UnitCategory.js'
+import SelectControl from 'src/components/Control/Select.vue'
+
+export default {
+  name: 'Filters',
+  components: { SelectControl },
+  mixins: [mixinAuth],
+  props: {
+    filters: {
+      type: Object,
+      default () {
+        return {
+          unit__category: null,
+          unit: null,
+          eventStatus: null,
+          holding_type: null,
+          professor: null
+        }
+      }
+    },
+    professors: {
+      type: Array,
+      default: () => []
+    },
+    categories: {
+      type: UnitCategoryList,
+      default: new UnitCategoryList()
+    },
+    units: {
+      type: UnitList,
+      default: new UnitList()
+    }
+  },
+  emits: ['doFilter', 'cancelFilter', 'onChangeCategory', 'update:filters'],
+  data () {
+    return {
+      professorsLoading: false
+    }
+  },
+  computed: {
+    localFilters: {
+      get () {
+        return this.filters
+      },
+      set (neValue) {
+        this.$emit('update:filters', neValue)
+      }
+    },
+    eventStatuses () {
+      return (new Event()).statusEnums.filter(item => item.showInUserFilters)
+    },
+    eventHoldingTypeOptions () {
+      return (new Event()).holding_typeEnums
+    }
+  },
+  methods: {
+    cancelFilter () {
+      this.$emit('cancelFilter')
+    },
+    doFilter () {
+      this.$emit('doFilter')
+    },
+    onChangeCategory () {
+      this.$emit('onChangeCategory')
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.filter-card {
+  :deep(.q-field) {
+    &.q-select {
+      .q-field__inner {
+        .q-field__control {
+          .q-field__control-container {
+            padding-top: 6px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 253 - 0
src/components/Widgets/Event/LastEvents/LastEvents.vue

@@ -0,0 +1,253 @@
+<template>
+  <div class="eventCarousel">
+    <div class="eventCarousel-title">
+      <div>
+        <q-banner class="banner">
+          رویدادها
+        </q-banner>
+      </div>
+      <div>
+        <q-btn flat
+               :to="{name: 'Public.AllEvents'}">
+          مشاهده همه
+          <svg xmlns="http://www.w3.org/2000/svg"
+               width="18.387"
+               height="11.502"
+               viewBox="0 0 18.387 11.502"
+               class="q-ml-sm">
+            <path id="Combined_Shape"
+                  data-name="Combined Shape"
+                  d="M-9.338,11.408a.748.748,0,0,0,.388-.656V6.5h8.2A.751.751,0,0,0,0,5.75.75.75,0,0,0-.75,5h-8.2V.75A.751.751,0,0,0-9.338.093.769.769,0,0,0-9.7,0a.735.735,0,0,0-.4.115l-7.938,5a.747.747,0,0,0-.35.635.742.742,0,0,0,.35.634l7.938,5a.751.751,0,0,0,.4.116A.746.746,0,0,0-9.338,11.408ZM-10.45,9.392l-5.78-3.641,5.78-3.642Z"
+                  transform="translate(18.387)"
+                  fill="#eac38a" />
+          </svg>
+        </q-btn>
+      </div>
+    </div>
+    <div class="carousel-section">
+      <carousel v-if="!events.loading && events.list.length > 0"
+                :breakpoints="breakpoints"
+                dir="rtl">
+        <slide v-for="event in events.list"
+               :key="event.id + eventsKey">
+          <event-item :event="event" />
+        </slide>
+
+        <template #addons="{ slidesCount }">
+          <navigation v-if="slidesCount > 1" />
+          <pagination v-if="slidesCount > 1 && false" />
+        </template>
+      </carousel>
+      <div v-else-if="!events.loading && events.list.length === 0">
+        رویدادی یافت نشد.
+      </div>
+      <div v-else-if="events.loading">
+        کمی صبر کنید ...
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import 'vue3-carousel/dist/carousel.css'
+import { User } from 'src/models/User.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import EventItem from 'src/components/EventItem.vue'
+import { Event, EventList } from 'src/models/Event.js'
+import { mixinPrefetchServerData } from 'src/mixin/Mixins.js'
+import { EventRegistrationList } from 'src/models/EventRegistration.js'
+import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel/dist/carousel'
+
+export default {
+  name: 'EventCarousel',
+  components: {
+    Carousel,
+    Slide,
+    Pagination,
+    Navigation,
+    EventItem
+  },
+  mixins: [mixinPrefetchServerData],
+  data: () => ({
+    loading: false,
+    user: new User(),
+    eventsKey: Date.now(),
+    userRegistrations: new EventRegistrationList(),
+    slide: 0,
+    events: new EventList(),
+    breakpoints: {
+      // 1024 and up
+      1024: {
+        itemsToShow: 3,
+        snapAlign: 'start'
+      },
+      // 700px and up
+      700: {
+        itemsToShow: 2.2,
+        snapAlign: 'center'
+      },
+      // 300px and up
+      300: {
+        itemsToShow: 1.1,
+        snapAlign: 'center'
+      }
+    },
+    maximizedToggle: true,
+    dialog: false
+  }),
+  mounted () {
+    this.loadAuthData()
+    if (this.user && this.user.id !== null) {
+      this.getUserRegistrations()
+    }
+  },
+  methods: {
+    getHoldingType (event) {
+      return new Event(event).holding_type_info.label
+    },
+    prefetchServerDataPromise () {
+      return this.getEvents()
+    },
+    prefetchServerDataPromiseThen (eventList) {
+      this.events = new EventList(eventList.list)
+      this.events.loading = false
+    },
+    prefetchServerDataPromiseCatch () {
+      this.events.loading = false
+    },
+
+    loadAuthData () {
+      this.user = this.$store.getters['Auth/user']
+    },
+    getTerm (event) {
+      if (!event.beginning_registration_period) {
+        return '-'
+      }
+      return ShamsiDate.getTerm(event.beginning_registration_period)
+    },
+    isEventRegistered (eventId) {
+      return !!this.userRegistrations.list.find(item => item.event === eventId && (item.status === 'REGISTERED' || item.status === 'ENROLLED'))
+    },
+    getUserRegistrations () {
+      this.userRegistrations.loading = true
+      APIGateway.classroomRegistration.index()
+        .then((eventRegistrationList) => {
+          this.userRegistrations = new EventRegistrationList(eventRegistrationList.list)
+          this.eventsKey = Date.now()
+          this.userRegistrations.loading = false
+        })
+        .catch(() => {
+          this.userRegistrations.loading = false
+        })
+    },
+    getEvents () {
+      this.events.loading = true
+      return APIGateway.event.index()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.eventCarousel {
+  padding: 60px 0;
+  .eventCarousel-title {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    padding-bottom: 33px;
+  }
+  .carousel-section {
+    padding: 0 60px;
+    :deep(.carousel__prev),
+    :deep(.carousel__next) {
+      width: 48px;
+      height: 48px;
+      background-color: #475F4A;
+      border-radius: 8px;
+      &.carousel__prev--in-active {
+        background-color: #DEDEDE;
+      }
+      &:after {
+      }
+      svg {
+        //display: none;
+      }
+    }
+    :deep(.carousel__prev) {
+      right: auto;
+      left: 0;
+      transform: translate(-100%, -50%);
+      color: white;
+    }
+    :deep(.carousel__next) {
+      left: auto;
+      right: 0;
+      transform: translate(100%, -50%);
+      color: white;
+    }
+    .carousel__slide {
+      padding: 0 12px;
+      .eventCarousel-item {
+        width: 100%;
+        &.isRegistered {
+          .thumbnail {
+            position: relative;
+            z-index: 1;
+            .RegisteredSign {
+              position: absolute;
+              right: 5px;
+              top: 7px;
+              z-index: 2;
+              display: block;
+            }
+          }
+        }
+        .thumbnail {
+          padding-top: 17px;
+          padding-left: 22px;
+          padding-right: 22px;
+          padding-bottom: 26px;
+          .RegisteredSign {
+            display: none;
+          }
+        }
+        .title {
+          text-align: left;
+          padding-top: 0;
+          padding-left: 46px;
+          padding-right: 46px;
+          padding-bottom: 33px;
+        }
+        .attribute {
+          padding-top: 0;
+          padding-left: 46px;
+          padding-right: 46px;
+          padding-bottom: 44px;
+          .attribute-item {
+            margin-bottom: 21px;
+            display: flex;
+            flex-flow: row;
+            .attribute-logo {
+              margin-right: 15px;
+            }
+            &:last-child {
+              margin-bottom: 0;
+            }
+          }
+        }
+        .action-section {
+          padding: 0;
+          .btn-show-event {
+            width: 100%;
+            height: 60px;
+            border-top-right-radius: 0;
+            border-top-left-radius: 0;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 164 - 0
src/components/Widgets/Event/LastEvents/OptionPanel.vue

@@ -0,0 +1,164 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div class="option-panel-container">
+        <div class="row q-col-gutter-md">
+          <div class="input-container col-12 q-mt-md">
+
+            <div class="outsideLabel">آدرس فایل عکس</div>
+            <q-input v-model="localOptions.imageSource"
+                     label="image" />
+          </div>
+          <!--          <div class="input-container q-py-md">-->
+          <!--            <div class="outsideLabel">height</div>-->
+          <!--            <q-input v-model="localOptions.height"-->
+          <!--                     label="image" />-->
+          <!--          </div>-->
+          <div class="input-container col-4">
+            <div class="outsideLabel">size</div>
+            <q-select v-model="size"
+                      :options="sizeOptions"
+                      label="page size" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">عرض در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].width"
+                     label="width" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">ارتفاع در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].height"
+                     label="height" />
+          </div>
+          <div class="input-container col-12">
+            <div class="outsideLabel">لینک عکس در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].src"
+                     label="image link" />
+          </div>
+          <div class="action col-md-12">
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.useAEEEvent"
+                          label="استفاده از ایونت GTM"
+                          right-label />
+            </div>
+            <div v-if="localOptions.useAEEEvent"
+                 class="action-container q-gutter-lg-md">
+              <div>فیلد های مورد نظر ایونت GTM :</div>
+              <div class="col-9">
+                <div class="outsideLabel">id</div>
+                <q-input v-model="localOptions.AEEEventBody.id"
+                         label="id" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">name</div>
+                <q-input v-model="localOptions.AEEEventBody.name"
+                         label="name" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">creative</div>
+                <q-input v-model="localOptions.AEEEventBody.creative"
+                         label="creative" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">position</div>
+                <q-input v-model="localOptions.AEEEventBody.position"
+                         label="position" />
+              </div>
+            </div>
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.hasAction"
+                          label="hasAction"
+                          right-label />
+            </div>
+            <div v-if="localOptions.hasAction"
+                 class="action-container">
+              <div>
+                <q-select v-model="localOptions.action.name"
+                          :options="actionTypes" />
+              </div>
+              <div v-if="localOptions.action.name === 'link'">
+                <q-input v-model="localOptions.action.route"
+                         label="route" />
+              </div>
+              <div v-if="localOptions.action.name === 'event'">
+                <q-input v-model="localOptions.action.eventName"
+                         label="event name" />
+                <q-input v-model="localOptions.action.eventArgs"
+                         label="event args" />
+              </div>
+              <div v-if="localOptions.action.name === 'scroll'">
+                <q-input v-model="localOptions.action.scrollTo"
+                         label="scrollTo class" />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: { OptionPanelTabs },
+  mixins: [mixinOptionPanel],
+  data() {
+    return {
+      size: 'xs',
+      sizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
+      actionTypes: ['event', 'scroll', 'link'],
+      defaultOptions: {
+        imageSource: null,
+        ratio: null,
+        hasAction: false,
+        useAEEEvent: false,
+        AEEEventBody: {
+          id: '-',
+          name: '-',
+          creative: null,
+          position: null
+        },
+        action: {
+          name: null,
+          route: null,
+          scrollTo: null,
+          eventName: null,
+          eventArgs: null
+        },
+        xs: {
+          height: null,
+          width: null,
+          src: null
+        },
+        sm: {
+          height: null,
+          width: null,
+          src: null
+        },
+        md: {
+          height: null,
+          width: null,
+          src: null
+        },
+        lg: {
+          height: null,
+          width: null,
+          src: null
+        },
+        xl: {
+          height: null,
+          width: null,
+          src: null
+        }
+      }
+    }
+  }
+})
+</script>

+ 164 - 0
src/components/Widgets/Event/ShowEventInfo/OptionPanel.vue

@@ -0,0 +1,164 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div class="option-panel-container">
+        <div class="row q-col-gutter-md">
+          <div class="input-container col-12 q-mt-md">
+
+            <div class="outsideLabel">آدرس فایل عکس</div>
+            <q-input v-model="localOptions.imageSource"
+                     label="image" />
+          </div>
+          <!--          <div class="input-container q-py-md">-->
+          <!--            <div class="outsideLabel">height</div>-->
+          <!--            <q-input v-model="localOptions.height"-->
+          <!--                     label="image" />-->
+          <!--          </div>-->
+          <div class="input-container col-4">
+            <div class="outsideLabel">size</div>
+            <q-select v-model="size"
+                      :options="sizeOptions"
+                      label="page size" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">عرض در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].width"
+                     label="width" />
+          </div>
+          <div class="input-container col-4">
+
+            <div class="outsideLabel">ارتفاع در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].height"
+                     label="height" />
+          </div>
+          <div class="input-container col-12">
+            <div class="outsideLabel">لینک عکس در سایز {{size}}</div>
+            <q-input v-model="localOptions[size].src"
+                     label="image link" />
+          </div>
+          <div class="action col-md-12">
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.useAEEEvent"
+                          label="استفاده از ایونت GTM"
+                          right-label />
+            </div>
+            <div v-if="localOptions.useAEEEvent"
+                 class="action-container q-gutter-lg-md">
+              <div>فیلد های مورد نظر ایونت GTM :</div>
+              <div class="col-9">
+                <div class="outsideLabel">id</div>
+                <q-input v-model="localOptions.AEEEventBody.id"
+                         label="id" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">name</div>
+                <q-input v-model="localOptions.AEEEventBody.name"
+                         label="name" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">creative</div>
+                <q-input v-model="localOptions.AEEEventBody.creative"
+                         label="creative" />
+              </div>
+              <div class="col-6">
+                <div class="outsideLabel">position</div>
+                <q-input v-model="localOptions.AEEEventBody.position"
+                         label="position" />
+              </div>
+            </div>
+            <div class="cehckBox">
+              <q-checkbox v-model="localOptions.hasAction"
+                          label="hasAction"
+                          right-label />
+            </div>
+            <div v-if="localOptions.hasAction"
+                 class="action-container">
+              <div>
+                <q-select v-model="localOptions.action.name"
+                          :options="actionTypes" />
+              </div>
+              <div v-if="localOptions.action.name === 'link'">
+                <q-input v-model="localOptions.action.route"
+                         label="route" />
+              </div>
+              <div v-if="localOptions.action.name === 'event'">
+                <q-input v-model="localOptions.action.eventName"
+                         label="event name" />
+                <q-input v-model="localOptions.action.eventArgs"
+                         label="event args" />
+              </div>
+              <div v-if="localOptions.action.name === 'scroll'">
+                <q-input v-model="localOptions.action.scrollTo"
+                         label="scrollTo class" />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: { OptionPanelTabs },
+  mixins: [mixinOptionPanel],
+  data() {
+    return {
+      size: 'xs',
+      sizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
+      actionTypes: ['event', 'scroll', 'link'],
+      defaultOptions: {
+        imageSource: null,
+        ratio: null,
+        hasAction: false,
+        useAEEEvent: false,
+        AEEEventBody: {
+          id: '-',
+          name: '-',
+          creative: null,
+          position: null
+        },
+        action: {
+          name: null,
+          route: null,
+          scrollTo: null,
+          eventName: null,
+          eventArgs: null
+        },
+        xs: {
+          height: null,
+          width: null,
+          src: null
+        },
+        sm: {
+          height: null,
+          width: null,
+          src: null
+        },
+        md: {
+          height: null,
+          width: null,
+          src: null
+        },
+        lg: {
+          height: null,
+          width: null,
+          src: null
+        },
+        xl: {
+          height: null,
+          width: null,
+          src: null
+        }
+      }
+    }
+  }
+})
+</script>

+ 531 - 0
src/components/Widgets/Event/ShowEventInfo/ShowEventInfo.vue

@@ -0,0 +1,531 @@
+<template>
+  <div class="event">
+    <q-banner v-if="!loading && !defaultOptions.profileMode"
+              class="banner event-banner">
+      رویدادها
+      {{event.title}}
+    </q-banner>
+    <q-card v-if="!loading"
+            flat
+            class="event-card">
+      <q-card-section class="info-box">
+
+        <q-banner v-if="event.getUserRegisterInfoLabel()"
+                  inline-actions
+                  rounded
+                  class="bg-blue-3 text-white text-center q-mb-md">
+          {{ event.getUserRegisterInfoLabel() }}
+        </q-banner>
+
+        <div class="row">
+          <div class="col-md-4 col-12 event-thumbnail">
+            <q-img :src="event.thumbnail" />
+          </div>
+          <div class="col-md-4 col-12 event-top-attributes">
+            <div class="title">
+              رویداد
+              {{event.title}}:
+            </div>
+            <div class="attributes">
+              <div class="attribute-item">{{getAudienceGenderType(event.audience_gender_type)}}</div>
+              <!--              <div class="attribute-item">{{getHoldingType(event.holding_type)}}</div>-->
+            </div>
+          </div>
+          <div class="col-md-4 col-12 event-action-col">
+            <q-btn v-if="event.is_enabled_adding"
+                   color="primary"
+                   class="btn-register q-ml-md"
+                   @click="openRegisterDialog">
+              <svg data-name="Add User"
+                   xmlns="http://www.w3.org/2000/svg"
+                   width="20"
+                   height="19.795"
+                   viewBox="0 0 20 19.795"
+                   class="q-mr-md">
+                <path id="Combined_Shape"
+                      data-name="Combined Shape"
+                      d="M0,16.115c0-3.281,4.5-3.659,7.877-3.659,1.949,0,7.877,0,7.877,3.681,0,3.279-4.5,3.658-7.877,3.658C5.928,19.795,0,19.795,0,16.115Zm1.5,0c0,1.446,2.146,2.18,6.377,2.18s6.377-.726,6.377-2.158c0-1.447-2.146-2.181-6.377-2.181S1.5,14.683,1.5,16.115Zm14.954-5.436V9.424H15.16a.75.75,0,0,1,0-1.5h1.294V6.669a.75.75,0,1,1,1.5,0V7.924h1.3a.75.75,0,0,1,0,1.5h-1.3v1.255a.75.75,0,1,1-1.5,0Zm-8.609-.043a5.334,5.334,0,1,1,.031,0ZM4.06,5.318A3.8,3.8,0,0,0,7.849,9.136l.028.75v-.75A3.818,3.818,0,1,0,4.06,5.318Z"
+                      transform="translate(0)"
+                      fill="#fff" />
+              </svg>
+              ثبت نام
+            </q-btn>
+            <q-btn v-if="event.is_enabled_enrolment"
+                   color="primary"
+                   class="btn-register q-ml-md"
+                   @click="onEnrolment">
+              <svg data-name="Add User"
+                   xmlns="http://www.w3.org/2000/svg"
+                   width="20"
+                   height="19.795"
+                   viewBox="0 0 20 19.795"
+                   class="q-mr-md">
+                <path id="Combined_Shape"
+                      data-name="Combined Shape"
+                      d="M0,16.115c0-3.281,4.5-3.659,7.877-3.659,1.949,0,7.877,0,7.877,3.681,0,3.279-4.5,3.658-7.877,3.658C5.928,19.795,0,19.795,0,16.115Zm1.5,0c0,1.446,2.146,2.18,6.377,2.18s6.377-.726,6.377-2.158c0-1.447-2.146-2.181-6.377-2.181S1.5,14.683,1.5,16.115Zm14.954-5.436V9.424H15.16a.75.75,0,0,1,0-1.5h1.294V6.669a.75.75,0,1,1,1.5,0V7.924h1.3a.75.75,0,0,1,0,1.5h-1.3v1.255a.75.75,0,1,1-1.5,0Zm-8.609-.043a5.334,5.334,0,1,1,.031,0ZM4.06,5.318A3.8,3.8,0,0,0,7.849,9.136l.028.75v-.75A3.818,3.818,0,1,0,4.06,5.318Z"
+                      transform="translate(0)"
+                      fill="#fff" />
+              </svg>
+              پیش ثبت نام
+            </q-btn>
+            <q-btn v-if="!defaultOptions.profileMode && event.is_enabled_viewing"
+                   color="primary"
+                   class="btn-register q-ml-md"
+                   :to="{name: 'UserPanel.Profile.EventInfo', params: {id: event.id}}">
+              مشاهده رویداد
+            </q-btn>
+            <q-btn v-if="event.is_enabled_dropping"
+                   outline
+                   color="red"
+                   class="btn-register q-ml-md"
+                   :loading="dropLoading"
+                   @click="onDropping">
+              انصراف
+            </q-btn>
+            <q-btn v-if="event.current_user_register_info?.status !== 'REGISTERED' && event.current_user_register_info?.status !== 'ENROLLED'"
+                   outline
+                   color="primary"
+                   class="btn-register q-ml-md"
+                   :loading="fetchCodesLoading"
+                   @click="openCodeLink">
+              آیین نامه رویداد
+            </q-btn>
+          </div>
+        </div>
+      </q-card-section>
+      <q-card-section class="info-box">
+        <div class="row second-section">
+          <div class="col-md-4 col-sm-6 col-12 attribute-column">
+            <div class="icon">
+              <svg xmlns="http://www.w3.org/2000/svg"
+                   width="17.058"
+                   height="21.399"
+                   viewBox="0 0 17.058 21.399">
+                <path id="Combined_Shape"
+                      data-name="Combined Shape"
+                      d="M0,17.418c0-3.55,4.868-3.96,8.53-3.96,2.109,0,8.528,0,8.528,3.982,0,3.548-4.867,3.959-8.528,3.959C6.42,21.4,0,21.4,0,17.418Zm1.616,0c0,1.57,2.326,2.366,6.914,2.366s6.913-.789,6.913-2.345-2.326-2.366-6.913-2.366S1.616,15.862,1.616,17.418ZM8.5,11.436A5.718,5.718,0,0,1,8.53,0a5.718,5.718,0,0,1,0,11.436ZM4.349,5.718A4.171,4.171,0,0,0,8.5,9.9l.031.77V9.9a4.18,4.18,0,1,0-4.181-4.18Z"
+                      transform="translate(0 0)"
+                      fill="#aaa095" />
+              </svg>
+            </div>
+            <div class="title">
+              {{ event.professor_info.firstname }}
+              {{ event.professor_info.lastname }}
+            </div>
+          </div>
+          <div class="col-md-4 col-sm-6 col-12 attribute-column">
+            <div class="icon">
+              <q-icon name="cast_for_education"
+                      size="20px"
+                      color="grey" />
+            </div>
+            <div class="title">
+              <!--              {{ getTerm(event) }}-->
+              {{getHoldingType(event.holding_type)}}
+            </div>
+          </div>
+          <div class="col-md-4 col-sm-6 col-12 attribute-column">
+            <div class="icon">
+              <svg data-name="Group 11306"
+                   xmlns="http://www.w3.org/2000/svg"
+                   width="21.539"
+                   height="21.044"
+                   viewBox="0 0 21.539 21.044">
+                <g id="Buy"
+                   transform="translate(0 0)">
+                  <path id="Combined_Shape"
+                        data-name="Combined Shape"
+                        d="M16.3,19.649A1.395,1.395,0,1,1,17.7,21.044,1.4,1.4,0,0,1,16.3,19.649Zm-12.147,0a1.394,1.394,0,1,1,1.394,1.395A1.4,1.4,0,0,1,4.157,19.649ZM17.77,16.139H6.014A2.76,2.76,0,0,1,3.28,13.615L2.294,1.883.671,1.6A.812.812,0,0,1,.012.668.811.811,0,0,1,.815,0a.829.829,0,0,1,.13.011L3.186.4a.808.808,0,0,1,.666.729L4.1,4.143c15.367.007,15.424.014,15.5.022a2.254,2.254,0,0,1,1.912,2.553l-1.023,7.062a2.76,2.76,0,0,1-2.717,2.358ZM4.89,13.483a1.123,1.123,0,0,0,1.127,1.04H17.771a1.139,1.139,0,0,0,1.122-.972l1.024-7.064a.632.632,0,0,0-.121-.472.625.625,0,0,0-.418-.249c-.052,0-.6,0-1.677,0-1.693,0-4.455,0-7.291,0l-6.17,0ZM13.059,9.74a.808.808,0,0,1,0-1.616h2.985a.808.808,0,0,1,0,1.616Z"
+                        transform="translate(0)"
+                        fill="#aaa095" />
+                </g>
+              </svg>
+            </div>
+            <div class="title">
+              {{ event.price ? event.price.toLocaleString('fa') : 0 }}
+              ریال
+            </div>
+          </div>
+        </div>
+      </q-card-section>
+      <div class="row q-col-gutter-lg four-sections">
+        <div class="col-md-6 col-12">
+          <div class="info-box detailes">
+            <div class="info-box-title">
+              <svg xmlns="http://www.w3.org/2000/svg"
+                   width="17.054"
+                   height="19.866"
+                   viewBox="0 0 17.054 19.866">
+                <path id="Combined_Shape"
+                      data-name="Combined Shape"
+                      d="M4.475,19.866A4.585,4.585,0,0,1,0,15.188V4.491A4.606,4.606,0,0,1,4.57.012h6.271a.758.758,0,0,1,.265,0h.131a.755.755,0,0,1,.541.23l5.065,5.277a.753.753,0,0,1,.209.519V15.2A4.615,4.615,0,0,1,12.6,19.865H4.475ZM1.5,4.509V15.2a3.091,3.091,0,0,0,3.009,3.162h8.064a3.109,3.109,0,0,0,2.979-3.155V6.984h-2.01a3.332,3.332,0,0,1-3.318-3.325V1.512H4.572A3.089,3.089,0,0,0,1.5,4.509Zm10.224-.85a1.828,1.828,0,0,0,1.82,1.824H14.73L11.724,2.353ZM5.387,14.108a.75.75,0,0,1,0-1.5h5.4a.75.75,0,0,1,0,1.5Zm0-3.752a.75.75,0,0,1,0-1.5H8.743a.75.75,0,0,1,0,1.5Z"
+                      transform="translate(0.001)"
+                      fill="#aaa095" />
+              </svg>
+              مشخصات
+            </div>
+            <div class="info-box-body"
+                 v-html="event.specification" />
+          </div>
+          <div class="info-box rules">
+            <div class="info-box-title">
+              <svg xmlns="http://www.w3.org/2000/svg"
+                   width="16.968"
+                   height="20.356"
+                   viewBox="0 0 16.968 20.356">
+                <path id="Combined_Shape"
+                      data-name="Combined Shape"
+                      d="M8.266,20.324c-.335-.1-8.205-2.55-8.205-9.445C.06,9,.036,7.621.019,6.616l0-.058c-.046-2.544-.055-3.074.6-3.73C1.4,2.04,7.349,0,8.483,0s7.078,2.04,7.865,2.828c.656.656.646,1.184.6,3.722l0,.062C16.929,7.619,16.9,9,16.9,10.879c0,6.895-7.87,9.344-8.205,9.445a.711.711,0,0,1-.217.032A.719.719,0,0,1,8.266,20.324ZM1.668,3.9c-.182.184-.189.5-.15,2.63l0,.065c.017,1.011.041,2.395.041,4.286,0,5.217,5.765,7.525,6.921,7.934C9.637,18.4,15.4,16.086,15.4,10.879c0-1.959.027-3.4.044-4.353.038-2.127.031-2.445-.162-2.637A30.988,30.988,0,0,0,8.483,1.5,31.027,31.027,0,0,0,1.668,3.9Zm5.577,8.4L5.353,10.406A.751.751,0,0,1,6.415,9.345l1.361,1.363,3.369-3.368A.75.75,0,0,1,12.2,8.4l-3.9,3.9a.749.749,0,0,1-1.061,0Z"
+                      transform="translate(0.001)"
+                      fill="#aaa095" />
+              </svg>
+              قوانین
+            </div>
+            <div class="info-box-body"
+                 v-html="event.rules" />
+          </div>
+        </div>
+        <div class="col-md-6 col-12">
+          <div class="info-box class-time">
+            <div class="info-box-title">
+              <svg id="Time_Circle"
+                   data-name="Time Circle"
+                   xmlns="http://www.w3.org/2000/svg"
+                   width="20"
+                   height="20"
+                   viewBox="0 0 20 20">
+                <path id="Combined_Shape"
+                      data-name="Combined Shape"
+                      d="M0,10A10,10,0,1,1,10,20,10.011,10.011,0,0,1,0,10Zm1.5,0A8.5,8.5,0,1,0,10,1.5,8.51,8.51,0,0,0,1.5,10Zm11.547,3.587-3.77-2.249a.754.754,0,0,1-.366-.644V5.845a.75.75,0,0,1,1.5,0v4.422l3.4,2.03a.751.751,0,0,1-.769,1.29Z"
+                      fill="#aaa095" />
+              </svg>
+              تابلو اعلانات
+            </div>
+            <div class="info-box-body"
+                 v-html="event.bulletin" />
+          </div>
+          <div class="info-box books">
+            <div class="info-box-title">
+              <svg xmlns="http://www.w3.org/2000/svg"
+                   width="21.968"
+                   height="15.702"
+                   viewBox="0 0 21.968 15.702">
+                <path id="import_contacts_FILL0_wght400_GRAD0_opsz48"
+                      d="M7.192,20.482a10.817,10.817,0,0,1,2.609.312,11.579,11.579,0,0,1,2.484.936V11.07A9.5,9.5,0,0,0,9.851,9.91,8.819,8.819,0,0,0,7.192,9.5a7.353,7.353,0,0,0-1.86.237,17.989,17.989,0,0,0-1.835.587h0V21.156h0a7.045,7.045,0,0,1,1.76-.512A11.9,11.9,0,0,1,7.192,20.482Zm6.59,1.248a12.562,12.562,0,0,1,2.446-.936,10.07,10.07,0,0,1,2.546-.312,13.37,13.37,0,0,1,1.96.15,9.769,9.769,0,0,1,1.735.4h0V10.322h0a7.418,7.418,0,0,0-1.8-.624,9.187,9.187,0,0,0-1.9-.2,8.346,8.346,0,0,0-2.609.412,9.727,9.727,0,0,0-2.384,1.161ZM13.034,23.7a1.613,1.613,0,0,1-.362-.037.708.708,0,0,1-.287-.137,10.528,10.528,0,0,0-2.5-1.123,9.28,9.28,0,0,0-2.7-.4,7.188,7.188,0,0,0-1.8.225,17.621,17.621,0,0,0-1.747.549A1.07,1.07,0,0,1,2.537,22.7,1.122,1.122,0,0,1,2,21.7V10.147a1.381,1.381,0,0,1,.175-.686A1.192,1.192,0,0,1,2.7,8.974a9.65,9.65,0,0,1,2.184-.736A11.268,11.268,0,0,1,7.192,8a11.077,11.077,0,0,1,3.058.424,9.758,9.758,0,0,1,2.783,1.3,9.477,9.477,0,0,1,2.733-1.3A10.725,10.725,0,0,1,18.775,8a11.088,11.088,0,0,1,2.3.237,9.691,9.691,0,0,1,2.172.736,1.272,1.272,0,0,1,.537.487,1.309,1.309,0,0,1,.187.686V21.7a1.183,1.183,0,0,1-.562,1.061.956.956,0,0,1-1.111.012,11.887,11.887,0,0,0-1.722-.562,7.589,7.589,0,0,0-1.8-.212,8.949,8.949,0,0,0-2.646.4,10.183,10.183,0,0,0-2.446,1.123.708.708,0,0,1-.287.137A1.613,1.613,0,0,1,13.034,23.7ZM7.891,15.764Z"
+                      transform="translate(-2 -8)"
+                      fill="#aaa095" />
+              </svg>
+              توضیحات
+            </div>
+            <div class="info-box-body"
+                 v-html="event.description" />
+          </div>
+        </div>
+      </div>
+    </q-card>
+
+    <q-skeleton v-if="loading"
+                type="circle"
+                size="100px"
+                class="q-mt-md" />
+    <q-skeleton v-if="loading"
+                width="150px"
+                class="q-mt-md" />
+    <q-skeleton v-if="loading"
+                height="150px"
+                class="q-mt-md" />
+
+    <q-dialog v-model="rulesDialog"
+              class="rulesDialog"
+              full-width>
+      <q-card>
+        <q-card-section>
+          <div class="text-h6">قوانین</div>
+        </q-card-section>
+
+        <q-separator />
+
+        <q-card-section style="max-height: 50vh"
+                        class="scroll">
+          <div v-html="event.rules" />
+          <div v-if="event.unit_info && event.unit_info.codes"
+               v-html="event.unit_info.codes" />
+        </q-card-section>
+
+        <q-separator />
+
+        <q-card-actions class="rulesDialog-actions">
+          <div v-if="(!event.is_enabled_enrolment || !event.is_enabled_adding) && acceptClassType !== 'rules'"
+               class="rulesDialog-accept-chk">
+            <q-checkbox v-model="rulesAccept"
+                        label="قوانین رویداد آموزشی را خواندم و قصد ثبت نام در رویداد را دارم" />
+          </div>
+          <div class="rulesDialog-btns q-gutter-md">
+            <q-btn v-if="acceptClassType === 'rules'"
+                   v-close-popup
+                   label="بستن"
+                   color="primary"
+                   outline />
+            <q-btn v-if="(!event.is_enabled_enrolment || !event.is_enabled_adding) && acceptClassType !== 'rules'"
+                   v-close-popup
+                   label="انصراف"
+                   color="primary"
+                   outline />
+            <q-btn v-if="(!event.is_enabled_enrolment || !event.is_enabled_adding) && acceptClassType !== 'rules'"
+                   label="تایید و ادامه"
+                   color="primary"
+                   :disable="!rulesAccept"
+                   @click="acceptAndContinue" />
+          </div>
+        </q-card-actions>
+      </q-card>
+    </q-dialog>
+  </div>
+</template>
+
+<script>
+import { Event } from 'src/models/Event.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'ShowEventInfo',
+  mixins: [mixinWidget, mixinPrefetchServerData],
+  props: {
+    options: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    }
+  },
+  emits: ['onloadn'],
+  data: () => ({
+    defaultOptions: {
+      profileMode: false
+    },
+    acceptClassType: 'register', // accept - enrollment - rules
+    fetchCodesLoading: false,
+    dropLoading: false,
+    rulesAccept: false,
+    rulesDialog: false,
+    loading: true,
+    event: new Event()
+  }),
+  methods: {
+    prefetchServerDataPromise () {
+      return this.getEvent()
+    },
+    prefetchServerDataPromiseThen (event) {
+      this.event = new Event(event)
+      this.loading = false
+      this.$emit('onloadn', this.event)
+    },
+    prefetchServerDataPromiseCatch () {
+      this.loading = false
+    },
+
+    reloadEvent () {
+      this.getEvent()
+        .then((event) => {
+          this.prefetchServerDataPromiseThen(event)
+        })
+        .catch(() => {
+          this.prefetchServerDataPromiseCatch()
+        })
+    },
+
+    getTitledDateTime (dateTime) {
+      return ShamsiDate.getTitledDateTime(dateTime)
+    },
+    acceptAndContinue () {
+      this.$store.commit('Shop/updateOnRegisterEvent', this.event)
+      this.$router.push({ name: 'UserPanel.ShopCompleteInfo', query: { type: this.acceptClassType } })
+    },
+    onEnrolment () {
+      this.acceptClassType = 'enrollment'
+      this.rulesDialog = true
+    },
+    onDropping () {
+      this.$q.dialog({
+        title: 'توجه',
+        message: 'آیا از حذف نام این رویداد اطمینان دارید؟',
+        cancel: true,
+        persistent: true
+      }).onOk(() => {
+        this.dropLoading = true
+        const eventId = this.$route.params.id
+        APIGateway.event.dropByUser(eventId)
+          .then(() => {
+            this.dropLoading = false
+            this.reloadEvent()
+          })
+          .catch(() => {
+            this.dropLoading = false
+          })
+      })
+    },
+    openRulesDialog () {
+      this.acceptClassType = 'rules'
+      this.rulesDialog = true
+    },
+    openCodeLink () {
+      if (!this.event.codes) {
+        return
+      }
+
+      const fileName = (this.event.codes.match(/(?<=\/)[^/?#]+(?=[^/]*$)/gm) || [this.event.title])[0]
+      this.fetchCodesLoading = true
+      fetch(this.event.codes)
+        .then(response => response.blob())
+        .then(blob => {
+          const link = document.createElement('a')
+          link.href = URL.createObjectURL(blob)
+          link.download = fileName
+          link.click()
+          this.fetchCodesLoading = false
+        })
+        .catch(() => {
+          this.fetchCodesLoading = false
+        })
+    },
+    openRegisterDialog () {
+      this.acceptClassType = 'register'
+      this.rulesDialog = true
+    },
+    getHoldingType (type) {
+      const target = (new Event()).holding_typeEnums.find(item => item.value === type)
+      if (target) {
+        return target.label
+      }
+      return '-'
+    },
+    getAudienceGenderType (type) {
+      const target = (new Event()).audience_gender_typeEnums.find(item => item.value === type)
+      if (target) {
+        return target.label
+      }
+      return '-'
+    },
+    getTime (time) {
+      return ShamsiDate.getDateTime(time)
+    },
+    getTerm (event) {
+      if (!event.beginning_registration_period) {
+        return '-'
+      }
+      return ShamsiDate.getTerm(event.beginning_registration_period)
+    },
+    getEvent () {
+      this.loading = true
+      return APIGateway.event.get(this.$route.params.id)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.event {
+  .event-banner {
+    margin-top: 52px;
+    margin-bottom: 42px;
+  }
+  .event-card {
+    background-color: #F5F5F5;
+    padding: 20px 30px 40px 30px;
+    .four-sections {
+      .info-box {
+        height: 353px;
+        overflow: auto;
+        &.detailes {
+          //margin-right: 24px;
+        }
+        &.rules {
+          //margin-right: 24px;
+        }
+        .info-box-title {
+          margin-bottom: 29px;
+          display: flex;
+          flex-flow: row;
+          svg {
+            margin-right: 8px;
+          }
+        }
+        .info-box-body {
+          .info-item {
+            margin-bottom: 27px;
+          }
+        }
+      }
+    }
+    .info-box {
+      background-color: #FFFFFF;
+      border-radius: 8px;
+      padding: 26px;
+      margin-bottom: 24px;
+      .event-thumbnail {
+        padding-right: 42px;
+      }
+      .event-top-attributes {
+        .attributes {
+          padding-top: 27px;
+          .attribute-item {
+            margin-bottom: 20px;
+            position: relative;
+            padding-left: 15px;
+            &:before {
+              content: ' ';
+              background-color: #EAC38A;
+              width: 8px;
+              height: 8px;
+              border-radius: 100%;
+              position: absolute;
+              left: 0;
+              top: 10px;
+            }
+          }
+        }
+      }
+      .event-action-col {
+        display: flex;
+        justify-content: flex-end;
+        align-items: flex-end;
+        flex-wrap: wrap;
+        .btn-register {
+          width: 220px;
+        }
+        @media screen and (max-width: 700px) {
+          width: 100%;
+          .q-btn {
+            width: 100% !important;
+            margin-left: 0;
+            margin-bottom: 8px;
+          }
+        }
+      }
+      .second-section {
+        .attribute-column {
+          display: flex;
+          flex-flow: row;
+          .icon {
+            margin-right: 20px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.rulesDialog {
+  .rulesDialog-actions {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    .rulesDialog-accept-chk {}
+    .rulesDialog-btns {}
+  }
+}
+</style>

+ 1 - 0
src/layouts/AdminLayout.vue

@@ -46,6 +46,7 @@ export default {
 .AdminLayout {
   background: #F9F9F9;
   padding: 35px 35px 35px 61px;
+  min-height: calc( 100vh - 51px - 60px );
   @media screen and (max-width: 1023px) {
     padding: 15px 10px !important;
   }

+ 1 - 0
src/layouts/UserPanelLayout.vue

@@ -46,6 +46,7 @@ export default {
 .UserPanelLayout {
   background: #F9F9F9;
   padding: 35px 35px 35px 61px;
+  min-height: calc( 100vh - 72px - 60px );
   @media screen and (max-width: 1023px) {
     padding: 15px 5px !important;
   }

+ 153 - 0
src/models/Event.js

@@ -0,0 +1,153 @@
+import { User } from 'src/models/User.js'
+import { Unit } from 'src/models/Unit.js'
+import { Model, Collection } from 'js-abstract-model'
+
+class Event extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'creator' },
+      {
+        key: 'creator_info',
+        relatedModel: User
+      },
+      { key: 'thumbnail' },
+      { key: 'title' },
+      { key: 'holding_type' },
+      { key: 'category' },
+      { key: 'item_type' },
+      { key: 'audience_gender_type' },
+      { key: 'holding_year' },
+      { key: 'holding_month' },
+      { key: 'sessions_frequency' },
+      { key: 'professor' },
+      { key: 'specification' },
+      { key: 'bulletin' },
+      { key: 'current_user_register_info' },
+      {
+        key: 'professor_info',
+        relatedModel: User
+      },
+      { key: 'unit' },
+      {
+        key: 'unit_info',
+        relatedModel: Unit
+      },
+      { key: 'audience_role' },
+      { key: 'price' },
+      { key: 'codes' },
+      { key: 'capacity' },
+      { key: 'used_capacity' },
+      { key: 'allowed_absence_count' },
+      { key: 'effective_absence_coefficient' },
+      { key: 'mandatory_assignment_count' },
+      { key: 'minimum_passing_mark' },
+      { key: 'minimum_conditional_passing_mark' },
+      { key: 'minimum_clean_passing_mark' },
+      { key: 'special_passing_mark' },
+      { key: 'live_streaming_url' },
+      { key: 'classroom_address' },
+      { key: 'beginning_enrollment_period' },
+      { key: 'ending_enrollment_period' },
+      { key: 'beginning_registration_period' },
+      { key: 'ending_registration_period' },
+      { key: 'allow_dropping_on_registration_period' },
+      { key: 'registration_period_refund_percent' },
+      { key: 'beginning_drop_add_period' },
+      { key: 'ending_drop_add_period' },
+      { key: 'allow_adding_on_drop_add_period' },
+      { key: 'allow_dropping_on_drop_add_period' },
+      { key: 'drop_add_period_refund_percent' },
+      { key: 'is_enabled_enrolment' },
+      { key: 'is_enabled_adding' },
+      { key: 'is_enabled_viewing' },
+      { key: 'is_enabled_dropping' },
+      { key: 'rules' },
+      { key: 'description' },
+      { key: 'is_current_user_registered' },
+      { key: 'publish_time' },
+      { key: 'creation_time' },
+      { key: 'last_modification_time' },
+      { key: 'is_active' },
+      { key: 'is_deleted' },
+      { key: 'status' }
+
+    ], {
+      status: {
+        infoKey: 'status_info',
+        enumListKey: 'statusEnums',
+        enums: [
+          { label: 'منتشر نشده', value: 'NOT_PUBLISHED_YET', showInUserFilters: false },
+          { label: 'قبل از مرحله پیش ثبت نام', value: 'BEFORE_ENROLLMENT_PERIOD', showInUserFilters: false },
+          { label: 'مرحله پیش ثبت نام', value: 'IN_ENROLLMENT_PERIOD', showInUserFilters: true },
+          { label: 'قبل از مرحله ثبت نام', value: 'BEFORE_REGISTRATION_PERIOD', showInUserFilters: false },
+          { label: 'مرحله ثبت نام', value: 'IN_REGISTRATION_PERIOD', showInUserFilters: true },
+          { label: 'قبل از مرحله حذف و اضافه', value: 'BEFORE_DROP_ADD_PERIOD', showInUserFilters: false },
+          { label: 'مرحله حذف و اضافه', value: 'IN_DROP_ADD_PERIOD', showInUserFilters: true },
+          { label: 'جاری', value: 'ONGOING', showInUserFilters: true },
+          { label: 'پایان یافته', value: 'FINISHED', showInUserFilters: true }
+        ]
+      },
+      holding_type: {
+        infoKey: 'holding_type_info',
+        enumListKey: 'holding_typeEnums',
+        enums: [
+          { label: 'برخط(آنلاین)', value: 'ONLINE' },
+          { label: 'برون خط(آفلاین)', value: 'OFFLINE' },
+          { label: 'حضوری', value: 'FACE_TO_FACE' }
+        ]
+      },
+      category: {
+        infoKey: 'category_info',
+        enumListKey: 'categoryEnums',
+        enums: [
+          { label: 'عمومی', value: 'OMOUMI' },
+          { label: 'هیئت', value: 'HEIAT' },
+          { label: 'ارزاق', value: 'ARZAGH' },
+          { label: 'اردو', value: 'ORDOO' }
+        ]
+      },
+      item_type: {
+        infoKey: 'item_type_info',
+        enumListKey: 'item_typeEnums',
+        enums: [
+          { label: 'دوره آموزشی', value: 'CLASSROOM' },
+          { label: 'حلقه مباحثاتی', value: 'DISCUSSION_CIRCLE' }
+        ]
+      },
+      audience_gender_type: {
+        infoKey: 'audience_gender_type_info',
+        enumListKey: 'audience_gender_typeEnums',
+        enums: [
+          { label: 'آقایان', value: 'MALE' },
+          { label: 'خانم ها', value: 'FEMALE' },
+          { label: 'خانم ها و آقایان', value: 'FEMALE_MALE' }
+        ]
+      }
+    })
+
+    if (!this.thumbnail) {
+      this.thumbnail = '/assets/images/web/sample-class-thumbnail.jpg'
+    }
+  }
+
+  getUserRegisterInfoLabel (status) {
+    const enums = {
+      REGISTERED: 'ثبت نام شده',
+      ENROLLED: 'پیش ثبت نام شده',
+      DROPPED_BY_ITSELF: 'حذف شده توسط اندیشه جو',
+      DROPPED_BY_ADMIN: 'حذف شده توسط ادمین'
+    }
+
+    const target = status || this.current_user_register_info?.status
+
+    return enums[target] || null
+  }
+}
+
+class EventList extends Collection {
+  model () {
+    return Event
+  }
+}
+export { Event, EventList }

+ 50 - 0
src/models/EventRegistration.js

@@ -0,0 +1,50 @@
+import { User } from 'src/models/User.js'
+import { Classroom } from 'src/models/Classroom.js'
+import { Model, Collection } from 'js-abstract-model'
+
+class EventRegistration extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'owner' },
+      {
+        key: 'owner_info',
+        relatedModel: User
+      },
+      { key: 'event' },
+      {
+        key: 'event_info',
+        relatedModel: Classroom
+      },
+      { key: 'status' }
+    ])
+
+    this.statusEnums = [
+      {
+        label: 'ثبت نام شده',
+        value: 'REGISTERED'
+      }
+    ]
+
+    this.loadStatusInfo()
+  }
+
+  loadStatusInfo () {
+    const target = this.statusEnums.find(type => type.value === this.status)
+    if (!target) {
+      this.status_info = {
+        label: null,
+        value: null
+      }
+    } else {
+      this.status_info = target
+    }
+  }
+}
+
+class EventRegistrationList extends Collection {
+  model () {
+    return EventRegistration
+  }
+}
+export { EventRegistration, EventRegistrationList }

+ 110 - 0
src/pages/Admin/Event/ProjectAttendanceSheet/create.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <div class="AdminClassroomCreate">
+    <q-linear-progress v-if="unitsLoading"
+                       indeterminate />
+    <entity-create v-if="mounted"
+                   ref="entityCreate"
+                   :key="classroomEntityCreateKey"
+                   v-model:value="inputs"
+                   title="مشخصات پروژه"
+                   :api="api"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false">
+      <template #after-form-builder>
+        <div class="flex justify-end q-mt-md">
+          <q-btn color="primary"
+                 label="ایجاد پروژه جدید"
+                 @click="create" />
+        </div>
+      </template>
+    </entity-create>
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityCreate } from 'quasar-crud'
+import { Project } from 'src/models/Project.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import ContentsSelector from 'src/components/FormBuilderCustumComponents/ContentsSelector/ContentsSelector.vue'
+
+const ContentsSelectorComp = shallowRef(ContentsSelector)
+
+export default {
+  name: 'Admin.Classroom.Create',
+  components: {
+    EntityCreate
+  },
+  data () {
+    const classroomId = this.$route.params.classroom_id
+    return {
+      mounted: false,
+      unitsLoading: false,
+      classroomEntityCreateKey: Date.now(),
+      newUnitLoading: false,
+      newUnitName: null,
+      newUnitSessionCount: null,
+      api: APIGateway.classroom.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان پروژه', placeholder: ' ', col: 'col-md-6 col-12' },
+        {
+          type: 'select',
+          name: 'type',
+          responseKey: 'type',
+          label: 'نوع پروژه',
+          options: (new Project()).typeEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: 'dateTime', name: 'beginning_doing_period', responseKey: 'beginning_doing_period', label: 'زمان شروع', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_doing_period', responseKey: 'ending_doing_period', label: 'زمان پایان', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
+        { type: 'inputEditor', name: 'correct_answer', responseKey: 'description', label: 'پاسخ صحیح', placeholder: ' ', col: 'col-12' },
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات پروژه', placeholder: ' ', col: 'col-12' },
+        { type: ContentsSelectorComp, name: 'contents', responseKey: 'contents_info', col: 'col-12' },
+        { type: 'hidden', name: 'classroom', responseKey: 'classroom', value: parseInt(classroomId) }
+      ]
+    }
+  },
+  computed: {
+    selectedCategoryId () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'category')?.value
+    }
+  },
+  watch: {
+    selectedCategoryId () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'value', null)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'options', [])
+      this.getUnits(this.selectedCategoryId)
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    create () {
+      this.$refs.entityCreate.createEntity(false)
+        .then((response) => {
+          const classroomId = this.$route.params.classroom_id
+          const projectId = response.data.id
+          this.$router.push({ name: 'Admin.Classroom.Project.Show', params: { classroom_id: classroomId, project_id: projectId } })
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>

+ 171 - 0
src/pages/Admin/Event/ProjectAttendanceSheet/members.vue

@@ -0,0 +1,171 @@
+<template>
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <div class="AdminClassroomProjectMembers">
+    <entity-index v-if="mounted"
+                  ref="entityIndex"
+                  v-model:value="inputs"
+                  title="لیست کاربران"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :show-expand-button="false"
+                  :show-reload-button="false"
+                  :show-search-button="false">
+      <template v-slot:entity-index-table-cell="{inputData}">
+        <template v-if="inputData.col.name === 'number'">
+          {{ inputData.rowNumber }}
+        </template>
+        <template v-else-if="inputData.col.name === 'action'">
+          <div class="action-column-entity-index">
+            <q-btn size="md"
+                   color="primary"
+                   :label="inputData.props.row.is_answer_verified === null ? 'بررسی پروژه' : 'ویرایش تصحیح'"
+                   :to="{name: 'Admin.User.Show', params: {id: inputData.props.row.id}}"
+                   class="q-mr-md" />
+          </div>
+        </template>
+        <template v-else-if="inputData.col.name === 'status'">
+          <div class="action-column-entity-index">
+            <q-toggle v-model="inputData.props.row.is_active"
+                      label="فعال/غیر فعال"
+                      @update:model-value="changeUserActiveStatus(inputData.props.row)" />
+          </div>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+// import { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+// import BtnControl from 'src/components/Control/btn.vue'
+import { ProjectAttendanceSheets } from 'src/models/ProjectAttendanceSheets'
+
+// const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'Admin.User.Index',
+  components: {
+    EntityIndex
+  },
+  data () {
+    const projectId = this.$route.params.project_id
+    return {
+      mounted: false,
+      exportReportLoading: false,
+      inputs: [
+        { type: 'hidden', name: 'project', value: projectId }
+      ],
+      api: APIGateway.projectAttendanceSheets.APIAdresses.project_sheets,
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'fullname',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.owner_info?.firstname + ' ' + row.owner_info?.lastname
+          },
+          {
+            name: 'national_code',
+            required: true,
+            label: 'کد ملی',
+            align: 'left',
+            field: row => row.owner_info?.national_code
+          },
+          {
+            name: 'mobile_number',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => new ProjectAttendanceSheets(row).answer_status_info.label
+          },
+          {
+            name: 'mobile_number',
+            required: true,
+            label: 'نتیجه',
+            align: 'left',
+            field: row => (row.is_answer_verified === null) ? '-' : ((row.is_answer_verified ? 'انجام شده' : 'انجام نشده'))
+          },
+          {
+            name: 'action',
+            required: true,
+            label: '',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.search)
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    },
+    changeUserActiveStatus (user) {
+      APIGateway.user.updateByAdmin(user)
+        .then(() => {
+          this.search()
+        })
+        .catch(() => {
+          this.search()
+        })
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.firstname + row.lastname + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.AdminClassroomProjectMembers {
+  .action-column-entity-index {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+  }
+}
+</style>

+ 23 - 0
src/pages/Admin/Event/ProjectAttendanceSheet/show.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Classroom.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminProjectAttendanceSheetShow' }], options: { style: { paddingTop: '' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/Event/SessionActivitySheets/Assignment.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Content.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminSessionAttendanceSheetShowAssignment' }], options: { style: { paddingTop: '' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/Event/SessionActivitySheets/Attendance.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Content.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminSessionAttendanceSheetShowAttendance' }], options: { style: { paddingTop: '' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/Event/SessionActivitySheets/sessionActivitySheets.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Content.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminSessionAttendanceSheetList' }], options: { style: { paddingTop: '' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/Event/Ticket.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminTicketList', options: { 0: { label: 'مالی', value: 'INVOICE' }, 1: { label: 'آموزش', value: 'TRAINING_CLASSROOM' }, 2: { label: 'حلقه های مباحثاتی', value: 'DISCUSSION_CIRCLE_CLASSROOM' }, 3: { label: 'SESSION', value: 'SESSION' }, style: {}, defaultSourceType: 'TRAINING_CLASSROOM' } }], options: { style: { paddingTop: '' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '30px', style: { maxWidth: '1200px', width: '1200px' } } }] }, options: { fullHeight: false, backgrounds: { xs: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, sm: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, md: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, lg: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, xl: { size: null, color: null, image: null, repeat: null, position: null, attachment: null } }, verticalAlign: null, style: { minHeight: 'auto' } } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 241 - 0
src/pages/Admin/Event/create.vue

@@ -0,0 +1,241 @@
+<template>
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <div class="AdminEventCreate">
+    <q-linear-progress v-if="unitsLoading"
+                       indeterminate />
+    <entity-create v-if="mounted"
+                   ref="eventEntityCreate"
+                   :key="eventEntityCreateKey"
+                   v-model:value="inputs"
+                   title="مشخصات دوره آموزشی"
+                   :api="api"
+                   :entity-id-key="entityIdKey"
+                   :entity-param-key="entityParamKey"
+                   :show-route-name="showRouteName"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false">
+      <template #after-form-builder>
+        <div class="flex justify-end q-mt-md">
+          <q-btn color="primary"
+                 label="ایجاد رویداد جدید"
+                 @click="createEvent" />
+        </div>
+      </template>
+    </entity-create>
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityCreate } from 'quasar-crud'
+import { Event } from 'src/models/Event.js'
+import Enums from 'src/assets/Enums/Enums.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
+
+const FormBuilderInputEditorComp = shallowRef(FormBuilderInputEditor)
+
+export default {
+  name: 'Admin.Event.Create',
+  components: {
+    EntityCreate
+  },
+  data () {
+    return {
+      mounted: false,
+      unitsLoading: false,
+      eventEntityCreateKey: Date.now(),
+      newUnitLoading: false,
+      newUnitName: null,
+      newUnitSessionCount: null,
+      api: APIGateway.event.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Event.Show',
+      inputs: [
+        { type: 'separator', name: 'space', label: 'مشخصات دوره', className: 'custom-separator', col: 'col-12' },
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان رویداد', placeholder: ' ', col: 'col-12' },
+        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'آپلود عکس دوره', col: 'col-md-3 col-12' },
+        { type: 'file', name: 'codes', responseKey: 'codes', label: 'آیین نامه دوره', col: 'col-md-3 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
+        { type: 'select', name: 'category', responseKey: 'unit_info.category', options: (new Event()).categoryEnums, value: null, placeholder: ' ', label: 'دسته بندی', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'unit', responseKey: 'unit', options: [], value: null, placeholder: ' ', label: 'درس', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'holding_type', responseKey: 'holding_type', options: (new Event()).holding_typeEnums, value: null, placeholder: ' ', label: 'نوع برگزاری', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'price', responseKey: 'price', placeholder: ' ', label: 'هزینه برگزاری', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'live_streaming_url', responseKey: 'live_streaming_url', placeholder: ' ', label: 'لینک مکان مجازی دوره', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'event_address', responseKey: 'event_address', placeholder: ' ', label: 'آدرس مکان فیزیکی دوره', col: 'col-12' },
+        {
+          type: 'select',
+          name: 'holding_year',
+          responseKey: 'holding_year',
+          options: [1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410],
+          value: null,
+          label: 'انتخاب سال دوره',
+          placeholder: ' ',
+          col: 'col-md-3'
+        },
+        {
+          type: 'select',
+          name: 'holding_month',
+          responseKey: 'holding_month',
+          options: [
+            'فروردین',
+            'اردیبهشت',
+            'خرداد',
+            'تیر',
+            'مرداد',
+            'شهریور',
+            'مهر',
+            'آبان',
+            'آذر',
+            'دی',
+            'بهمن',
+            'اسفند'
+          ],
+          value: null,
+          label: 'انتخاب ماه دوره',
+          placeholder: ' ',
+          col: 'col-md-3'
+        },
+        { type: 'input', name: 'sessions_frequency', responseKey: 'sessions_frequency', placeholder: ' ', label: 'دوره تناوب', col: 'col-12' },
+        { type: 'select', name: 'professor', responseKey: 'professor', options: [], value: null, placeholder: ' ', label: 'استاد دوره', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'مشخصات اندیشه جو', className: 'custom-separator', col: 'col-12' },
+        { type: 'select', name: 'audience_status_wwwww', responseKey: 'audience_status_wwwww', options: [], placeholder: ' ', label: 'وضعیت(؟)', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'category1', responseKey: 'category1', options: [], placeholder: ' ', label: 'مجموعه(؟)', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'audience_gender_type', responseKey: 'audience_gender_type', options: (new Event()).audience_gender_typeEnums, placeholder: ' ', label: 'جنسیت', col: 'col-md-3 col-12' },
+        { type: 'select', name: 'audience_bashgah_wwwww', responseKey: 'audience_bashgah_wwwww', options: [], placeholder: ' ', label: 'باشگاه(؟)', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'تنظیمات دوره', className: 'custom-separator', col: 'col-12' },
+        { type: 'input', name: 'capacity', responseKey: 'capacity', placeholder: ' ', label: 'ظرفیت گروه درسی', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'allowed_absence_count', responseKey: 'allowed_absence_count', placeholder: ' ', label: 'تعداد مجاز غیبت', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'effective_absence_coefficient', responseKey: 'effective_absence_coefficient', placeholder: ' ', label: 'ضریب نمره حضور و غیاب', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'mandatory_assignment_count', responseKey: 'mandatory_assignment_count', placeholder: ' ', label: 'تعداد تکالیف اجباری', col: 'col-md-3 col-12' },
+
+        { type: 'input', name: 'special_passing_mark', responseKey: 'special_passing_mark', placeholder: ' ', label: 'نمره قبولی مشروطی ها', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'minimum_conditional_passing_mark', responseKey: 'minimum_conditional_passing_mark', placeholder: ' ', label: 'نمره قبولی مشروط', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'minimum_clean_passing_mark', responseKey: 'minimum_clean_passing_mark', placeholder: ' ', label: 'نمره قبولی', col: 'col-md-3 col-12' },
+
+        { type: 'separator', name: 'space', label: 'ثبت نام و حذف و اضافه', className: 'custom-separator', col: 'col-12' },
+        { type: 'dateTime', name: 'beginning_enrollment_period', responseKey: 'beginning_enrollment_period', placeholder: ' ', label: 'تاریخ شروع پیش ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_enrollment_period', responseKey: 'ending_enrollment_period', placeholder: ' ', label: 'تاریخ پایان پیش ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'beginning_registration_period', responseKey: 'beginning_registration_period', placeholder: ' ', label: 'تاریخ شروع ثبت نام', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_registration_period', responseKey: 'ending_registration_period', placeholder: ' ', label: 'تاریخ پایان ثبت نام', col: 'col-md-6 col-12' },
+
+        { type: 'input', name: 'registration_period_refund_percent', responseKey: 'registration_period_refund_percent', placeholder: ' ', label: 'درصد برگشت هزینه انصراف در بازه ثبت نام', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_enrollment_period', responseKey: 'allow_dropping_on_enrollment_period', label: 'امکان حذف در بازه پیش ثبت نام', value: false, col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_adding_on_registration_period', responseKey: 'allow_adding_on_registration_period', label: 'امکان اضافه کردن در بازه ثبت نام', value: false, col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_registration_period', responseKey: 'allow_dropping_on_registration_period', label: 'امکان حذف ثبت نام در بازه ثبت نام', value: false, col: 'col-md-3 col-12' },
+
+        { type: 'dateTime', name: 'beginning_drop_add_period', responseKey: 'beginning_drop_add_period', placeholder: ' ', label: 'تاریخ شروع حذف و اضافه', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_drop_add_period', responseKey: 'ending_drop_add_period', placeholder: ' ', label: 'تاریخ پایان حذف و اضافه', col: 'col-md-6 col-12' },
+
+        { type: 'input', name: 'drop_add_period_refund_percent', responseKey: 'drop_add_period_refund_percent', placeholder: ' ', label: 'درصد برگشت هزینه انصراف در بازه حذف و اضافه', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_adding_on_drop_add_period', responseKey: 'allow_adding_on_drop_add_period', label: 'امکان اضافه شدن در بازه حذف و اضافه', value: false, col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'allow_dropping_on_drop_add_period', responseKey: 'allow_dropping_on_drop_add_period', label: 'امکان حذف در بازه حذف و اضافه', value: false, col: 'col-md-3 col-12' },
+
+        { type: 'dateTime', name: 'publish_time', responseKey: 'publish_time', placeholder: ' ', label: 'تاریخ انتشار', col: 'col-md-6 col-12' },
+
+        { type: 'separator', name: 'space', label: 'قوانین', className: 'custom-separator', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'rules', responseKey: 'rules', label: 'قوانین دوره آموزشی', col: 'col-12' },
+
+        { type: FormBuilderInputEditorComp, name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'specification', responseKey: 'specification', label: 'مشخصات', col: 'col-12' },
+        { type: FormBuilderInputEditorComp, name: 'bulletin', responseKey: 'bulletin', label: 'تابلو اعلانات', col: 'col-12' },
+
+        // -----------------------------------------------------------------------------------------------------------
+
+        // { type: 'separator', name: 'space', label: 'موارد زیر در طرح نبودند', className: 'custom-separator', col: 'col-12' },
+        // { type: 'select', name: 'audience_role', responseKey: 'audience_role', options: Enums.groups, placeholder: ' ', label: 'نقش', col: 'col-md-4 col-12' },
+
+        { type: 'hidden', name: 'id', responseKey: 'id', label: 'id', col: 'col-md-3 col-12' }
+      ]
+    }
+  },
+  // computed: {
+  //   selectedCategoryId () {
+  //     return FormBuilderAssist.getInputsByName(this.inputs, 'category')?.value
+  //   }
+  // },
+  // watch: {
+  //   selectedCategoryId () {
+  //     FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'value', null)
+  //     FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'options', [])
+  //     this.getUnits(this.selectedCategoryId)
+  //   }
+  // },
+  mounted () {
+    this.preLoadData()
+      .then(() => {
+        this.mounted = true
+      })
+      .catch(() => {
+      })
+  },
+  methods: {
+    setInputAttr (name, attr, value) {
+      this.$refs.eventEntityCreate.setInputAttributeByName(name, attr, value)
+    },
+    preLoadData () {
+      return new Promise((resolve, reject) => {
+        const promise1 = this.getProfessors()
+        const promise2 = this.getUnits()
+        this.$nextTick(() => {
+          Promise.all([promise1, promise2])
+            .then(() => {
+              resolve()
+            })
+            .catch(() => {
+              reject()
+            })
+        })
+      })
+    },
+    getProfessors () {
+      return APIGateway.user.index({ per_page: 9999, role: 'professor' })
+        .then((users) => {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'professor', 'options', users.list.list.map(item => {
+            return {
+              value: item.id,
+              label: this.getUserFullname(item)
+            }
+          }))
+        })
+        .catch(() => {})
+    },
+    getUnits (selectedcategoryId = null) {
+      this.unitsLoading = true
+      APIGateway.unit.index({ per_page: 9999, category: selectedcategoryId })
+        .then((units) => {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'options', units.list.list.map(item => {
+            return {
+              value: item.id,
+              label: item.title
+            }
+          }))
+          this.unitsLoading = false
+        })
+        .catch(() => {
+          this.unitsLoading = false
+        })
+    },
+    getUserFullname (user) {
+      return user.firstname + ' ' + user.lastname
+    },
+    createEvent () {
+      this.$refs.eventEntityCreate.createEntity()
+    }
+  }
+}
+</script>

+ 264 - 0
src/pages/Admin/Event/index.vue

@@ -0,0 +1,264 @@
+<template>
+  <div class="AdminEventIndex">
+    <div class="add-new-action-row">
+      <q-btn color="primary"
+             :to="{name: 'Admin.Event.Create'}">
+        <svg data-name="Close Square"
+             xmlns="http://www.w3.org/2000/svg"
+             width="20"
+             height="20"
+             viewBox="0 0 20 20"
+             class="q-mr-md">
+          <path id="Combined_Shape"
+                data-name="Combined Shape"
+                d="M5.665,20C2.276,20,0,17.623,0,14.084V5.916C0,2.377,2.276,0,5.665,0h8.67C17.723,0,20,2.377,20,5.916v8.168C20,17.623,17.723,20,14.333,20ZM1.5,5.916v8.168c0,2.683,1.634,4.416,4.164,4.416h8.669c2.532,0,4.167-1.733,4.167-4.416V5.916c0-2.682-1.635-4.415-4.166-4.415H5.665C3.135,1.5,1.5,3.234,1.5,5.916Zm7.968,8a.747.747,0,0,1-.22-.53V10.741H6.611a.75.75,0,0,1-.75-.75.749.749,0,0,1,.75-.75H9.249V6.6a.75.75,0,1,1,1.5,0V9.241h2.639a.75.75,0,0,1,0,1.5H10.748v2.642a.75.75,0,0,1-1.28.53Z"
+                fill="#fff" />
+        </svg>
+        ایجاد رویداد جدید
+      </q-btn>
+    </div>
+    <entity-index v-if="mounted"
+                  ref="entityIndex"
+                  v-model:value="inputs"
+                  title="لیست رویداد ها"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :show-expand-button="false"
+                  :show-reload-button="false"
+                  :show-search-button="false">
+      <template v-slot:entity-index-table-cell="{inputData, showConfirmRemoveDialog}">
+        <template v-if="inputData.col.name === 'number'">
+          {{ inputData.rowNumber }}
+        </template>
+        <template v-else-if="inputData.col.name === 'action'">
+          <div class="action-column-entity-index">
+            <q-btn size="md"
+                   color="primary"
+                   label="جزییات"
+                   :to="{name: 'Admin.Event.Show', params: {id: inputData.props.row.id}}"
+                   class="q-mr-md" />
+            <delete-btn @click="showConfirmRemoveDialog(inputData.props.row, 'id', getRemoveMessage(inputData.props.row))" />
+          </div>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { Event } from 'src/models/Event.js'
+import Enums from 'src/assets/Enums/Enums.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import BtnControl from 'src/components/Control/btn.vue'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'Admin.Event.Index',
+  components: {
+    EntityIndex,
+    DeleteBtn
+  },
+  data () {
+    return {
+      mounted: false,
+      inputs: [
+        {
+          type: 'select',
+          name: 'status',
+          options: (new Event()).statusEnums,
+          value: null,
+          label: 'وضعیت رویداد',
+          placeholder: ' ',
+          col: 'col-md-2 col-12'
+        },
+        {
+          type: 'select',
+          name: 'unit',
+          options: [],
+          value: null,
+          label: 'درس',
+          placeholder: ' ',
+          col: 'col-md-2 col-12'
+        },
+        { type: 'hidden', name: 'unit__category__type', value: 'TRAINING' },
+        { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
+      ],
+      api: APIGateway.event.APIAdresses.base,
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'نام رویداد',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'audience_gender_type',
+            required: true,
+            label: 'جنسیت',
+            align: 'left',
+            field: row => (row.audience_gender_type === 'FEMALE') ? 'خواهران' : (row.audience_gender_type === 'MALE') ? 'برادران' : 'خواهران و برادران'
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            // field: row => this.getEventStatusesTitle(row.status)
+            field: row => (new Event(row)).status_info.label
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'نوع برگزاری',
+            align: 'left',
+            // field: row => this.getEventHoldingTypeTitle(row.holding_type)
+            field: row => (new Event(row)).holding_type_info.label
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'هزینه(ریال)',
+            align: 'left',
+            field: row => row.price
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'استاد مربوطه',
+            align: 'left',
+            field: row => row.professor_info?.firstname + ' ' + row.professor_info?.lastname
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'تاریخ و زمان ثبت نام',
+            align: 'left',
+            field: row => row.beginning_registration_period ? ShamsiDate.getDateTime(row.beginning_registration_period) : '-'
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'ترم برگزاری',
+            align: 'left',
+            field: row => (row.beginning_registration_period) ? ShamsiDate.getTerm(row.beginning_registration_period) : '-'
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted () {
+    this.setActionBtn()
+    this.mounted = true
+    this.getUnits()
+  },
+  methods: {
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].atClick = this.search
+        }
+      })
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    },
+    getUnits () {
+      APIGateway.unit.index({ per_page: 9999 })
+        .then((units) => {
+          this.loadSelectOptions('unit', this.getSelectOptions(units.list.list, 'id', 'title'))
+        })
+        .catch(() => {
+
+        })
+    },
+    loadSelectOptions (name, value) {
+      const inputIndex = this.inputs.findIndex(input => input.name === name)
+      this.inputs[inputIndex].options = value
+    },
+    getSelectOptions (result, value, label) {
+      return result.map(item => {
+        return {
+          value: item[value],
+          label: item[label]
+        }
+      })
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.title + ' اطمینان دارید؟'
+    },
+    getEventHoldingTypeTitle (type) {
+      const target = (new Event()).holding_typeEnums.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    getEventStatusesTitle (type) {
+      const target = Enums.classroomStatuses.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.AdminEventIndex {
+  .add-new-action-row {
+    display: flex;
+    flex-flow: row;
+    align-items: center;
+    justify-content: flex-end;
+    margin-bottom: 22px;
+  }
+  .action-column-entity-index {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+  }
+}
+</style>

+ 111 - 0
src/pages/Admin/Event/project/create.vue

@@ -0,0 +1,111 @@
+<template>
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <div class="AdminClassroomCreate">
+    <q-linear-progress v-if="unitsLoading"
+                       indeterminate />
+    <entity-create v-if="mounted"
+                   ref="entityCreate"
+                   :key="classroomEntityCreateKey"
+                   v-model:value="inputs"
+                   title="مشخصات پروژه"
+                   :api="api"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false">
+      <template #after-form-builder>
+        <div class="flex justify-end q-mt-md">
+          <q-btn color="primary"
+                 label="ایجاد پروژه جدید"
+                 @click="create" />
+        </div>
+      </template>
+    </entity-create>
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityCreate } from 'quasar-crud'
+import { Project } from 'src/models/Project.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import ContentsSelector from 'src/components/FormBuilderCustumComponents/ContentsSelector/ContentsSelector.vue'
+
+const ContentsSelectorComp = shallowRef(ContentsSelector)
+
+export default {
+  name: 'Admin.Classroom.Create',
+  components: {
+    EntityCreate
+  },
+  data () {
+    const classroomId = this.$route.params.classroom_id
+    return {
+      mounted: false,
+      unitsLoading: false,
+      classroomEntityCreateKey: Date.now(),
+      newUnitLoading: false,
+      newUnitName: null,
+      newUnitSessionCount: null,
+      api: APIGateway.project.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان پروژه', placeholder: ' ', col: 'col-md-6 col-12' },
+        {
+          type: 'select',
+          name: 'type',
+          responseKey: 'type',
+          label: 'نوع پروژه',
+          options: (new Project()).typeEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: 'checkbox', name: 'is_mandatory', responseKey: 'is_mandatory', label: 'اجباری', placeholder: ' ', value: false, col: 'col-md-3 col-12' },
+        { type: 'dateTime', name: 'beginning_doing_period', responseKey: 'beginning_doing_period', label: 'زمان شروع', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_doing_period', responseKey: 'ending_doing_period', label: 'زمان پایان', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات پروژه', placeholder: ' ', col: 'col-12' },
+        { type: 'inputEditor', name: 'correct_answer', responseKey: 'correct_answer', label: 'پاسخ صحیح', placeholder: ' ', col: 'col-12' },
+        { type: ContentsSelectorComp, name: 'contents', responseKey: 'contents_info', col: 'col-12' },
+        { type: 'hidden', name: 'classroom', responseKey: 'classroom', value: parseInt(classroomId) }
+      ]
+    }
+  },
+  computed: {
+    selectedCategoryId () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'category')?.value
+    }
+  },
+  watch: {
+    selectedCategoryId () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'value', null)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'options', [])
+      this.getUnits(this.selectedCategoryId)
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    create () {
+      this.$refs.entityCreate.createEntity(false)
+        .then((response) => {
+          const classroomId = this.$route.params.classroom_id
+          const projectId = response.data.id
+          this.$router.push({ name: 'Admin.Classroom.Project.Show', params: { classroom_id: classroomId, project_id: projectId } })
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>

+ 171 - 0
src/pages/Admin/Event/project/members.vue

@@ -0,0 +1,171 @@
+<template>
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <div class="AdminClassroomProjectMembers">
+    <entity-index v-if="mounted"
+                  ref="entityIndex"
+                  v-model:value="inputs"
+                  title="لیست کاربران"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :show-expand-button="false"
+                  :show-reload-button="false"
+                  :show-search-button="false">
+      <template v-slot:entity-index-table-cell="{inputData}">
+        <template v-if="inputData.col.name === 'number'">
+          {{ inputData.rowNumber }}
+        </template>
+        <template v-else-if="inputData.col.name === 'action'">
+          <div class="action-column-entity-index">
+            <q-btn size="md"
+                   color="primary"
+                   :label="inputData.props.row.is_answer_verified === null ? 'بررسی پروژه' : 'ویرایش تصحیح'"
+                   :to="{name: 'Admin.Classroom.projectAttendanceSheets.Show', params: { classroom_id: $route.params.classroom_id, project_id: $route.params.project_id, project_attendance_sheets_id: inputData.props.row.id}}"
+                   class="q-mr-md" />
+          </div>
+        </template>
+        <template v-else-if="inputData.col.name === 'status'">
+          <div class="action-column-entity-index">
+            <q-toggle v-model="inputData.props.row.is_active"
+                      label="فعال/غیر فعال"
+                      @update:model-value="changeUserActiveStatus(inputData.props.row)" />
+          </div>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+// import { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+// import BtnControl from 'src/components/Control/btn.vue'
+import { ProjectAttendanceSheets } from 'src/models/ProjectAttendanceSheets'
+
+// const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'Admin.User.Index',
+  components: {
+    EntityIndex
+  },
+  data () {
+    const projectId = this.$route.params.project_id
+    return {
+      mounted: false,
+      exportReportLoading: false,
+      inputs: [
+        { type: 'hidden', name: 'project', value: projectId }
+      ],
+      api: APIGateway.projectAttendanceSheets.APIAdresses.project_sheets,
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'fullname',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.owner_info?.firstname + ' ' + row.owner_info?.lastname
+          },
+          {
+            name: 'national_code',
+            required: true,
+            label: 'کد ملی',
+            align: 'left',
+            field: row => row.owner_info?.national_code
+          },
+          {
+            name: 'mobile_number',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => new ProjectAttendanceSheets(row).answer_status_info.label
+          },
+          {
+            name: 'mobile_number',
+            required: true,
+            label: 'نتیجه',
+            align: 'left',
+            field: row => (row.is_answer_verified === null) ? '-' : ((row.is_answer_verified ? 'انجام شده' : 'انجام نشده'))
+          },
+          {
+            name: 'action',
+            required: true,
+            label: '',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.search)
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    },
+    changeUserActiveStatus (user) {
+      APIGateway.user.updateByAdmin(user)
+        .then(() => {
+          this.search()
+        })
+        .catch(() => {
+          this.search()
+        })
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.firstname + row.lastname + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.AdminClassroomProjectMembers {
+  .action-column-entity-index {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+  }
+}
+</style>

+ 84 - 0
src/pages/Admin/Event/project/show.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <entity-edit v-if="mounted"
+               ref="projectEntityEdit"
+               v-model:value="inputs"
+               title="جزییات پروژه"
+               :api="api"
+               :show-close-button="false"
+               :show-edit-button="false"
+               :show-expand-button="false"
+               :show-save-button="false"
+               :show-reload-button="false">
+    <template #after-form-builder>
+      <div class="flex justify-end q-mt-md">
+        <q-btn color="primary"
+               label="ذخیره جزییات"
+               @click="updateSessionTemplates" />
+      </div>
+    </template>
+  </entity-edit>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityEdit } from 'quasar-crud'
+import { Project } from 'src/models/Project.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import ContentsSelector from 'src/components/FormBuilderCustumComponents/ContentsSelector/ContentsSelector.vue'
+
+const ContentsSelectorComp = shallowRef(ContentsSelector)
+
+export default {
+  name: 'Admin.Classroom.Project.Show',
+  components: {
+    EntityEdit
+  },
+  data () {
+    const projectId = parseInt(this.$route.params.project_id)
+    return {
+      mounted: false,
+      newUnitLoading: false,
+      newUnitName: null,
+      newUnitSessionCount: null,
+      api: APIGateway.project.APIAdresses.byId(projectId),
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان پروژه', placeholder: ' ', col: 'col-md-6 col-12' },
+        {
+          type: 'select',
+          name: 'type',
+          responseKey: 'type',
+          label: 'نوع پروژه',
+          options: (new Project()).typeEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: 'checkbox', name: 'is_mandatory', responseKey: 'is_mandatory', label: 'اجباری', placeholder: ' ', value: false, col: 'col-md-3 col-12' },
+        { type: 'dateTime', name: 'beginning_doing_period', responseKey: 'beginning_doing_period', label: 'زمان شروع', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'dateTime', name: 'ending_doing_period', responseKey: 'ending_doing_period', label: 'زمان پایان', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-md-12' },
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات پروژه', placeholder: ' ', col: 'col-12' },
+        { type: 'inputEditor', name: 'correct_answer', responseKey: 'correct_answer', label: 'پاسخ صحیح', placeholder: ' ', col: 'col-12' },
+        { type: ContentsSelectorComp, name: 'contents', responseKey: 'contents_info', col: 'col-12' },
+        { type: 'hidden', name: 'id', responseKey: 'id' },
+        { type: 'hidden', name: 'classroom', responseKey: 'classroom' }
+      ]
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    updateSessionTemplates () {
+      this.$refs.projectEntityEdit.editEntity(false)
+    }
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/Event/show.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Admin.Classroom.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminEventShow', options: { style: {}, classroomType: 'Classroom' } }], options: { style: { paddingTop: '' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 25 - 0
src/pages/Public/Events.vue

@@ -0,0 +1,25 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+// import { mixinSEO, mixinPageOptions, mixinPrefetchServerData } from 'src/mixin/Mixins.js'
+export default {
+  name: 'Public.AllEvents',
+  // mixins: [mixinPrefetchServerData, mixinPageOptions, mixinSEO],
+  data () {
+    return {
+      pageConfig: {},
+      pageBuilderEditable: false,
+      pageBuilderLoading: false,
+      currenSections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AllEvents', options: { imageSource: null, ratio: null, hasAction: false, useAEEEvent: false, AEEEventBody: { id: '-', name: '-', creative: null, position: null }, action: { name: null, route: null, scrollTo: null, eventName: null, eventArgs: null }, xs: { height: null, width: null, src: null }, sm: { height: null, width: null, src: null }, md: { height: null, width: null, src: null }, lg: { height: null, width: null, src: null }, xl: { height: null, width: null, src: null }, style: { marginBottom: '32px' } } }, { name: 'RegistrationList', options: { style: { marginBottom: '32px' } } }] }], options: { boxed: true } }] } }]
+    }
+  }
+  // mounted() {
+  //   this.currenSections = this.sections
+  // }
+}
+</script>

+ 41 - 0
src/pages/Public/ShowEventInfo.vue

@@ -0,0 +1,41 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions, mixinPrefetchServerData, mixinSEO } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Public.ShowEventInfo',
+  mixins: [mixinPrefetchServerData, mixinPageOptions, mixinSEO],
+  data () {
+    return {
+      sections: [
+        {
+          data: {
+            rows: [
+              {
+                cols: [
+                  {
+                    widgets: [
+                      {
+                        name: 'ShowEventInfo'
+                      }
+                    ]
+                  }
+                ],
+                options: {
+                  boxed: true
+                }
+              }
+            ]
+          }
+        }
+      ]
+    }
+  }
+}
+</script>

+ 475 - 0
src/pages/User/Profile/EventInfo.vue

@@ -0,0 +1,475 @@
+<template>
+  <breadcrumbs style="margin-top: 29px; margin-bottom: 19px;" />
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <q-card>
+    <q-tabs v-model="tab"
+            dense
+            class="text-grey"
+            active-color="primary"
+            indicator-color="primary"
+            align="justify"
+            narrow-indicator>
+      <q-tab name="eventInfo"
+             label="اطلاعات دوره آموزشی" />
+      <q-tab name="educations"
+             label="جدول آموزشی" />
+      <q-tab name="movies1"
+             label="آزمون" />
+      <q-tab name="projects"
+             label="پروژه" />
+      <q-tab v-if="event.live_streaming_url"
+             name="live_streaming_url"
+             label="بخش آنلاین" />
+    </q-tabs>
+
+    <q-separator />
+
+    <q-tab-panels v-model="tab"
+                  animated>
+      <q-tab-panel name="eventInfo">
+        <show-event-info v-if="mounted"
+                         :options="{ profileMode: true }"
+                         @onloadn="onloadnEvent" />
+      </q-tab-panel>
+
+      <q-tab-panel name="educations"
+                   class="q-pa-none">
+        <entity-index v-if="mounted"
+                      ref="entityIndex"
+                      v-model:value="sessionsInputs"
+                      title="لیست جلسات"
+                      :api="sessionsApi"
+                      :table="sessionsTable"
+                      :table-keys="sessionsTableKeys"
+                      :table-grid-size="$q.screen.lt.sm"
+                      :show-expand-button="false"
+                      :show-reload-button="false"
+                      :show-search-button="false">
+          <template #entity-index-table-cell="{inputData}">
+            <template v-if="inputData.col.name === 'number'">
+              {{ inputData.rowNumber }}
+            </template>
+            <template v-else-if="inputData.col.name === 'action'">
+              <q-btn size="md"
+                     color="primary"
+                     label="جزییات"
+                     :to="{name: 'UserPanel.Profile.SessionInfo', params: {id: inputData.props.row.id}}" />
+            </template>
+            <template v-else>
+              {{ inputData.col.value }}
+            </template>
+          </template>
+          <template #entity-index-table-item-cell="{inputData}">
+            <entity-index-grid-item :input-data="inputData">
+              <template #col="{col, row}">
+                <template v-if="col.name === 'number'">
+                  {{ inputData.rowNumber }}
+                </template>
+                <template v-else-if="col.name === 'action'">
+                  <q-btn size="md"
+                         color="primary"
+                         label="جزییات"
+                         :to="{name: 'UserPanel.Profile.SessionInfo', params: {id: row.id}}" />
+                </template>
+              </template>
+            </entity-index-grid-item>
+          </template>
+        </entity-index>
+      </q-tab-panel>
+
+      <q-tab-panel name="movies1">
+        آزمون
+      </q-tab-panel>
+      <q-tab-panel name="projects"
+                   class="q-pa-none">
+        <entity-index v-if="mounted"
+                      ref="projectList"
+                      v-model:value="projectListInputs"
+                      title="لیست پروژه ها"
+                      :api="projectListApi"
+                      :table="projectListTable"
+                      :table-keys="projectListTableKeys"
+                      :show-search-button="false"
+                      :show-reload-button="false"
+                      :show-expand-button="false">
+          <template v-slot:entity-index-table-cell="{inputData}">
+            <template v-if="inputData.col.name === 'number'">
+              {{ inputData.rowNumber }}
+            </template>
+            <template v-else-if="inputData.col.name === 'actions'">
+              <div class="action-column-entity-index">
+                <q-btn size="md"
+                       color="primary"
+                       label="جزییات"
+                       :to="{name: 'UserPanel.Profile.Event.Project.Show', params: {event_id: $route.params.id, project_id: inputData.props.row.id}}"
+                       class="q-mr-md" />
+              </div>
+            </template>
+            <template v-else>
+              {{ inputData.col.value }}
+            </template>
+          </template>
+        </entity-index>
+        <q-skeleton v-else
+                    type="rect"
+                    height="200px" />
+      </q-tab-panel>
+      <q-tab-panel v-if="event.live_streaming_url"
+                   name="live_streaming_url">
+        <q-btn class="full-width q-mb-lg"
+               color="primary"
+               @click="goToLiveStreamUrl">
+          رفتن به کلاس
+        </q-btn>
+        <q-banner>
+          <div class="q-mb-md">
+            نام کاربری اسکای روم:
+            {{ user.national_code }}
+          </div>
+          <div>
+            کلمه عبور اسکای روم:
+            1234
+          </div>
+        </q-banner>
+        <iframe :src="event.live_streaming_url"
+                title="W3Schools Free Online Web Tutorials"
+                style="height: 80vh"
+                class="full-width" />
+      </q-tab-panel>
+    </q-tab-panels>
+  </q-card>
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import { Event } from 'src/models/Event.js'
+import Enums from 'src/assets/Enums/Enums.js'
+import {Invoice} from "src/models/Invoice.js";
+import { mixinAuth } from 'src/mixin/Mixins.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import EntityIndexGridItem from 'src/components/EntityIndexGridItem.vue'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+import { ProjectAttendanceSheets } from 'src/models/ProjectAttendanceSheets.js'
+import { SessionAttendanceSheets } from 'src/models/SessionAttendanceSheets.js'
+import ShowEventInfo from 'src/components/Widgets/Event/ShowEventInfo/ShowEventInfo.vue'
+
+export default {
+  name: 'UserPanel.Profile.EventInfo',
+  components: {
+    EntityIndex,
+    Breadcrumbs,
+    ShowEventInfo,
+    EntityIndexGridItem
+  },
+  mixins: [mixinAuth],
+  data () {
+    const eventId = this.$route.params.id
+    // const userId = this.$store.getters['Auth/user'].id
+    return {
+      mounted: false,
+      tab: 'eventInfo',
+      inputs: [],
+      sessionsInputs: [
+        { type: 'hidden', name: 'event', value: eventId }
+      ],
+      api: APIGateway.event.get(eventId),
+      sessionsApi: APIGateway.session.APIAdresses.attendanceSheets,
+      event: new Event(),
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'عنوان',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'زمان شروع',
+            align: 'left',
+            field: row => row.beginning_time ? ShamsiDate.getDateTime(row.beginning_time) : '-'
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'زمان پایان',
+            align: 'left',
+            field: row => row.ending_time ? ShamsiDate.getDateTime(row.ending_time) : '-'
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      sessionsTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'عنوان',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'زمان شروع',
+            align: 'left',
+            field: row => row.beginning_time ? ShamsiDate.getDateTime(row.beginning_time) : '-'
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'زمان پایان',
+            align: 'left',
+            field: row => row.ending_time ? ShamsiDate.getDateTime(row.ending_time) : '-'
+          },
+          {
+            name: 'assignment_status',
+            required: true,
+            label: 'وضعیت تکلیف',
+            align: 'left',
+            field: row => (new SessionAttendanceSheets(this.getCurrentUserAttendanceSheet(row))).assignment_status_info.label
+          },
+          {
+            name: 'attendance_status',
+            required: true,
+            label: 'وضعیت حضور و غیاب',
+            align: 'left',
+            field: row => (new SessionAttendanceSheets(this.getCurrentUserAttendanceSheet(row))).attendance_status_info.label
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      sessionsTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+
+      projectListInputs: [
+        { type: 'hidden', name: 'event', value: eventId }
+        // { type: 'hidden', name: 'owner', value: userId }
+      ],
+      // projectListApi: APIGateway.projectAttendanceSheets.APIAdresses.base,
+      projectListApi: APIGateway.project.APIAdresses.attendanceSheetsList,
+      projectListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'عنوان پروژه',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'زمان شروع',
+            align: 'left',
+            field: row => row?.beginning_doing_period ? ShamsiDate.getDateTime(row.beginning_doing_period) : '-'
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'زمان پایان',
+            align: 'left',
+            field: row => row?.ending_doing_period ? ShamsiDate.getDateTime(row.ending_doing_period) : '-'
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => (new ProjectAttendanceSheets(this.getProjectAttendanceSheets(row))).answer_status_info.label
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      projectListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  computed: {
+    selectedCategoryId () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'category')?.value
+    }
+  },
+  watch: {
+    selectedCategoryId () {
+      this.setInputValue('unit', null)
+      this.getUnits(this.selectedCategoryId)
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    getCurrentUserAttendanceSheet (session) {
+      if (!session?.session_attendance_sheets || session.session_attendance_sheets.length === 0) {
+        return null
+      }
+
+      return session.session_attendance_sheets[0]
+    },
+    getProjectAttendanceSheets (project) {
+      if (!project?.project_attendance_sheets || project.project_attendance_sheets.length === 0) {
+        return null
+      }
+
+      return project.project_attendance_sheets[0]
+    },
+    goToLiveStreamUrl () {
+      window.open(this.event.live_streaming_url, '_blank')
+    },
+    onloadnEvent (event) {
+      this.event = new Event(event)
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'رویدادهای من',
+            to: { name: 'UserPanel.Profile.AllEvents' }
+          },
+          {
+            label: this.event.title,
+            to: { name: 'UserPanel.Profile.EventInfo', params: { id: this.event.id } }
+          }
+        ]
+      })
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    },
+    getEventHoldingTypeTitle (type) {
+      const target = Enums.eventHoldingTypes.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    getEventStatusTitle (type) {
+      const target = (new Event()).statusEnums.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    getInvoiceStatusTitle (type) {
+      const target = (new Invoice()).statusEnums.find(item => item.value === type)
+      const target = Enums.invoiceStatus.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    loadInputDataOptions () {
+      this.getCategories()
+      this.getUnits()
+    },
+    setInputValue (name, value) {
+      const inputIndex = this.inputs.findIndex(input => input.name === name)
+      this.inputs[inputIndex].value = value
+    },
+    getSelectOptions (result, value, label) {
+      return result.map(item => {
+        return {
+          value: item[value],
+          label: item[label]
+        }
+      })
+    },
+    loadSelectOptions (name, value) {
+      const inputIndex = this.inputs.findIndex(input => input.name === name)
+      this.inputs[inputIndex].options = value
+    }
+  }
+}
+</script>

+ 291 - 0
src/pages/User/Profile/Events.vue

@@ -0,0 +1,291 @@
+<template>
+  <breadcrumbs style="margin-top: 29px; margin-bottom: 19px;" />
+  <entity-index v-if="mounted"
+                ref="entityIndex"
+                v-model:value="inputs"
+                title="لیست رویداد ها"
+                :api="api"
+                :table="table"
+                :table-keys="tableKeys"
+                :table-grid-size="$q.screen.lt.sm"
+                :show-expand-button="false"
+                :show-reload-button="false"
+                :show-search-button="false">
+    <template #entity-index-table-cell="{inputData}">
+      <template v-if="inputData.col.name === 'number'">
+        {{ inputData.rowNumber }}
+      </template>
+      <template v-else-if="inputData.col.name === 'action'">
+        <q-btn size="md"
+               color="primary"
+               label="جزییات"
+               :to="{name: 'UserPanel.Profile.EventInfo', params: {id: inputData.props.row.event_info.id}}" />
+      </template>
+      <template v-else>
+        {{ inputData.col.value }}
+      </template>
+    </template>
+    <template #entity-index-table-item-cell="{inputData}">
+      <entity-index-grid-item :input-data="inputData">
+        <template #col="{col, row}">
+          <template v-if="col.name === 'number'">
+            {{ inputData.rowNumber }}
+          </template>
+          <template v-else-if="col.name === 'action'">
+            <q-btn size="md"
+                   color="primary"
+                   label="جزییات"
+                   :to="{name: 'UserPanel.Profile.EventInfo', params: {id: row.event_info.id}}" />
+          </template>
+        </template>
+      </entity-index-grid-item>
+    </template>
+  </entity-index>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { Event } from 'src/models/Event.js'
+import Enums from 'src/assets/Enums/Enums.js'
+import { Invoice } from 'src/models/Invoice.js'
+import { mixinAuth } from 'src/mixin/Mixins.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+import { Registration } from 'src/models/Registration.js'
+import EntityIndexGridItem from 'src/components/EntityIndexGridItem.vue'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'UserPanel.Profile.AllEvents',
+  components: {
+    EntityIndex,
+    Breadcrumbs,
+    EntityIndexGridItem
+  },
+  mixins: [mixinAuth],
+  data () {
+    return {
+      mounted: false,
+      inputs: [
+        {
+          type: 'select',
+          name: 'status',
+          options: (new Event()).statusEnums,
+          value: null,
+          label: 'وضعیت رویداد',
+          placeholder: ' ',
+          col: 'col-md-2 col-12'
+        },
+        {
+          type: 'select',
+          name: 'category',
+          options: (new Event()).categoryEnums,
+          value: null,
+          label: 'گروه آموزشی',
+          placeholder: ' ',
+          col: 'col-md-2 col-12'
+        },
+        {
+          type: 'select',
+          name: 'unit',
+          responseKey: 'unit',
+          options: [],
+          value: null,
+          label: 'درس',
+          placeholder: ' ',
+          col: 'col-md-2 col-12'
+        },
+        { type: 'hidden', name: 'owner', value: null },
+        { type: 'input', name: 'search', label: 'جستجو', placeholder: ' ', col: 'col-md-2 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
+      ],
+      api: APIGateway.registration.APIAdresses.base,
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.event_info.id
+          },
+          {
+            name: 'event_info.title',
+            required: true,
+            label: 'عنوان',
+            align: 'left',
+            field: row => row.event_info?.title
+          },
+          // {
+          //   name: 'title',
+          //   required: true,
+          //   label: 'نام درس',
+          //   align: 'left',
+          //   field: row => row.event_info.unit_info.title
+          // },
+          {
+            name: 'audience_gender_type',
+            required: true,
+            label: 'جنسیت',
+            align: 'left',
+            field: row => (new Event(row.event_info)).audience_gender_type_info.label
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'نوع برگزاری',
+            align: 'left',
+            field: row => this.getEventHoldingTypeTitle(row.event_info.holding_type)
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'استاد',
+            align: 'left',
+            field: row => row.event_info?.professor_info?.firstname + ' ' + row.event_info?.professor_info?.lastname
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'تعداد جلسات',
+            align: 'left',
+            field: row => row.event_info?.sessions_info?.length
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => (new Registration(row)).status_info.label
+            // field: row => this.getEventStatusTitle(row.event_info.status)
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'زمان ثبت نام',
+            align: 'left',
+            field: row => ShamsiDate.getDateTime(row.creation_time)
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'وضعیت مالی',
+            align: 'left',
+            field: row => (new Invoice(row.invoice_info)).status_info.label ? (new Invoice(row.invoice_info)).status_info.label : '-'
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  // computed: {
+  //   selectedCategoryId () {
+  //     return this.getInputValue('category')
+  //   }
+  // },
+  // watch: {
+  //   selectedCategoryId () {
+  //     this.setInputValue('unit', null)
+  //     this.getUnits(this.selectedCategoryId)
+  //   }
+  // },
+  mounted () {
+    this.setOwner()
+    this.setActionBtn()
+    this.loadInputDataOptions()
+    this.mounted = true
+  },
+  methods: {
+    setOwner () {
+      const user = this.$store.getters['Auth/user']
+      FormBuilderAssist.setAttributeByName(this.inputs, 'owner', 'value', user.id)
+    },
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.search)
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    },
+    getEventHoldingTypeTitle (type) {
+      const target = (new Event()).holding_typeEnums.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    getEventStatusTitle (type) {
+      const target = (new Event()).statusEnums.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    getInvoiceStatusTitle (type) {
+      const target = Enums.invoiceStatus.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    loadInputDataOptions () {
+      this.getUnits()
+    },
+    getUnits (selectedcategoryId) {
+      APIGateway.unit.index({ category: selectedcategoryId })
+        .then(unitList => {
+          this.loadSelectOptions('unit', this.getSelectOptions(unitList.list.list, 'id', 'title'))
+        })
+        .catch(() => {
+        })
+    },
+    getInputValue (name) {
+      const inputIndex = this.inputs.findIndex(input => input.name === name)
+      return this.inputs[inputIndex].value
+    },
+    setInputValue (name, value) {
+      const inputIndex = this.inputs.findIndex(input => input.name === name)
+      this.inputs[inputIndex].value = value
+    },
+    getSelectOptions (result, value, label) {
+      return result.map(item => {
+        return {
+          value: item[value],
+          label: item[label]
+        }
+      })
+    },
+    loadSelectOptions (name, value) {
+      const inputIndex = this.inputs.findIndex(input => input.name === name)
+      this.inputs[inputIndex].options = value
+    }
+  }
+}
+</script>

+ 62 - 0
src/router/routes.js

@@ -112,6 +112,22 @@ const routes = [
             },
             component: () => import('src/pages/Public/ShowClassroomInfo.vue')
           },
+          {
+            name: 'Public.AllEvents',
+            path: 'all-events',
+            meta: {
+              hasDynamicSetting: true
+            },
+            component: () => import('src/pages/Public/Events.vue')
+          },
+          {
+            name: 'Public.ShowEventInfo',
+            path: 'event/:id',
+            meta: {
+              hasDynamicSetting: true
+            },
+            component: () => import('src/pages/Public/ShowEventInfo.vue')
+          },
           {
             name: 'Public.FAQ',
             path: 'faq',
@@ -226,6 +242,24 @@ const routes = [
                   middlewares: [HasOverdueInstalment]
                 }
               },
+              {
+                name: 'UserPanel.Profile.AllEvents',
+                path: 'all-events',
+                component: () => import('src/pages/User/Profile/Events.vue'),
+                meta: {
+                  breadcrumbs: {
+                    visible: true,
+                    loading: false,
+                    path: [
+                      {
+                        label: 'رویدادهای من',
+                        to: { name: 'UserPanel.Profile.AllEvents' }
+                      }
+                    ]
+                  },
+                  middlewares: [HasOverdueInstalment]
+                }
+              },
               {
                 name: 'UserPanel.Profile.AllDiscussionCirclesClassrooms',
                 path: 'all-discussion-circles-classrooms',
@@ -252,6 +286,14 @@ const routes = [
                   middlewares: [HasOverdueInstalment]
                 }
               },
+              {
+                name: 'UserPanel.Profile.EventInfo',
+                path: 'event/:id',
+                component: () => import('src/pages/User/Profile/EventInfo.vue'),
+                meta: {
+                  middlewares: [HasOverdueInstalment]
+                }
+              },
               { name: 'UserPanel.Profile.Classroom.Project.Show', path: 'classroom/:classroom_id/project/:project_id', component: () => import('src/pages/User/Profile/projectInfo.vue') },
               {
                 name: 'UserPanel.Profile.SessionInfo',
@@ -577,6 +619,26 @@ const routes = [
               { name: 'Admin.Classroom.Ticket', path: 'ticket', component: () => import('src/pages/Admin/Classroom/Ticket.vue'), breadcrumbs: { title: 'درخواست ها دوره های آموزشی' } }
             ]
           },
+          {
+            name: 'Admin.Event',
+            path: 'events',
+            component: () => import('layouts/bareLayout.vue'),
+            breadcrumbs: { title: 'دوره های آموزشی', route: { name: 'Admin.Event.Index' } },
+            children: [
+              { name: 'Admin.Event.Index', path: '', component: () => import('src/pages/Admin/Event/index.vue') },
+              { name: 'Admin.Event.Create', path: 'create', component: () => import('src/pages/Admin/Event/create.vue'), breadcrumbs: { title: 'ساخت دوره آموزشی' } },
+              { name: 'Admin.Event.Show', path: ':id', component: () => import('src/pages/Admin/Event/show.vue') },
+              { name: 'Admin.Event.Session.AttendanceSheetList', path: ':classroom_id/session/:session_id/session_attendance_sheets', component: () => import('pages/Admin/Event/SessionActivitySheets/sessionActivitySheets.vue') },
+              { name: 'Admin.Event.Session.AttendanceSheet.Assignment', path: ':classroom_id/session/:session_id/session_attendance_sheet/:session_attendance_sheet_id/assignment', component: () => import('pages/Admin/Event/SessionActivitySheets/Assignment.vue') },
+              { name: 'Admin.Event.Session.AttendanceSheet.Attendance', path: ':classroom_id/session/:session_id/session_attendance_sheet/:session_attendance_sheet_id/attendance', component: () => import('pages/Admin/Event/SessionActivitySheets/Attendance.vue') },
+              { name: 'Admin.Event.Project.Create', path: ':classroom_id/project/create', component: () => import('src/pages/Admin/Event/project/create.vue'), breadcrumbs: { title: 'ساخت دوره آموزشی' } },
+              { name: 'Admin.Event.Project.Show', path: ':classroom_id/project/:project_id', component: () => import('src/pages/Admin/Event/project/show.vue') },
+              { name: 'Admin.Event.Project.Members', path: ':classroom_id/project/:project_id/members', component: () => import('src/pages/Admin/Event/project/members.vue') },
+              { name: 'Admin.Event.projectAttendanceSheets.Show', path: ':classroom_id/project/:project_id/projectAttendanceSheet/:project_attendance_sheets_id', component: () => import('src/pages/Admin/Event/ProjectAttendanceSheet/show.vue') },
+              { name: 'Admin.Event.Project', path: ':id', component: () => import('src/pages/Admin/Event/show.vue') },
+              { name: 'Admin.Event.Ticket', path: 'ticket', component: () => import('src/pages/Admin/Event/Ticket.vue'), breadcrumbs: { title: 'درخواست ها دوره های آموزشی' } }
+            ]
+          },
           {
             name: 'Admin.Classroom.DiscussionCircles',
             path: 'discussion-circles-classrooms',

+ 9 - 9
yarn.lock

@@ -6118,12 +6118,12 @@ qs@6.11.0:
   dependencies:
     side-channel "^1.0.4"
 
-quasar-app-extension-q-page-builder@^0.1.63:
-  version "0.1.63"
-  resolved "https://registry.yarnpkg.com/quasar-app-extension-q-page-builder/-/quasar-app-extension-q-page-builder-0.1.63.tgz#15ef415c966a80a534db2d272ff9c4ea1c28cce5"
-  integrity sha512-45Aid8eYMn65ElA2gsmuVA1yFs0SjhDJSo0G6tukTQ+gp3ZxYapv2K9pzw0UH80zziHrTsfjCu/fpspK7USagw==
+quasar-app-extension-q-page-builder@^0.1.71:
+  version "0.1.71"
+  resolved "https://registry.yarnpkg.com/quasar-app-extension-q-page-builder/-/quasar-app-extension-q-page-builder-0.1.71.tgz#97ee2eca196b383b6ab480aa2cc40d054bb93023"
+  integrity sha512-4GsPgAQEsPfvzEORC/40Tkcd8XLhtlzhp+3sJaXqT624EeGLG0Ek1F0nG/QNOdzZqZZorc+iI0YukrUPA2YNzQ==
   dependencies:
-    quasar-ui-q-page-builder "^0.1.63"
+    quasar-ui-q-page-builder "^0.1.71"
 
 quasar-crud@^0.3.55:
   version "0.3.55"
@@ -6160,10 +6160,10 @@ quasar-template-builder@^1.0.23:
     vue "^3.2.47"
     vuex "^4.1.0"
 
-quasar-ui-q-page-builder@^0.1.63:
-  version "0.1.63"
-  resolved "https://registry.yarnpkg.com/quasar-ui-q-page-builder/-/quasar-ui-q-page-builder-0.1.63.tgz#ad8024a7b54d1a2ee954bb9daea514f5da826f40"
-  integrity sha512-WI6Goz7XT3eJZZbJcKQEFkPKfy8qztSJgT1/uPfdltaDQZWXOExtmjsZM0FqBZtmn4QEC+qhS32v+Y6fv4RLQw==
+quasar-ui-q-page-builder@^0.1.71:
+  version "0.1.71"
+  resolved "https://registry.yarnpkg.com/quasar-ui-q-page-builder/-/quasar-ui-q-page-builder-0.1.71.tgz#55d17e848b62a9693f78433f07b93225f5f6f439"
+  integrity sha512-KloqgzLxK4tBrFYuS/2N6QnSMiVx6MjO2HGAO+q96zFvVw5aP2kQM3cVkvBJu+6oZ9FrYU/ZXGLYw3iVj2Sgfg==
   dependencies:
     "@quasar/extras" "^1.16.1"
     "@vue/compiler-sfc" "^3.2.47"