瀏覽代碼

Merge branch 'master' of http://git.hghweb.ir/ali.s/palizvani-front-vite into production

HgH 3 年之前
父節點
當前提交
4816f7f294
共有 100 個文件被更改,包括 8361 次插入 和 1037 次删除
  1. 3 3
      package.json
  2. 19 13
      src-pwa/custom-service-worker.js
  3. 12 0
      src/api/APIGateway.js
  4. 47 0
      src/api/models/Classroom.js
  5. 16 0
      src/api/models/ClassroomGraders.js
  6. 16 0
      src/api/models/ClassroomLeaders.js
  7. 173 0
      src/api/models/Event.js
  8. 73 0
      src/api/models/EventRegistration.js
  9. 1 1
      src/api/models/Invoice.js
  10. 2 0
      src/api/models/Registration.js
  11. 22 0
      src/api/models/SessionAttendanceSheets.js
  12. 51 0
      src/api/models/Team.js
  13. 14 0
      src/api/models/TeamRegistrations.js
  14. 1 0
      src/api/models/Unit.js
  15. 4 4
      src/assets/Enums/Enums.js
  16. 21 10
      src/boot/axios.js
  17. 231 0
      src/components/EventItem.vue
  18. 16 4
      src/components/Menu/SideMenu/MenuItem.vue
  19. 6 3
      src/components/Template/Header/MainHeader.vue
  20. 40 1
      src/components/Template/SideBard/AdminPanelSideBar.vue
  21. 26 1
      src/components/Template/SideBard/UserPanelSideBar.vue
  22. 248 122
      src/components/Widgets/Classroom/AdminClassroomCreate/AdminClassroomCreate.vue
  23. 9 2
      src/components/Widgets/Classroom/AdminClassroomCreate/OptionPanel.vue
  24. 183 94
      src/components/Widgets/Classroom/AdminClassroomList/AdminClassroomList.vue
  25. 9 2
      src/components/Widgets/Classroom/AdminClassroomList/OptionPanel.vue
  26. 115 14
      src/components/Widgets/Classroom/AdminClassroomShow/AdminClassroomShow.vue
  27. 2 10
      src/components/Widgets/Classroom/AdminClassroomShow/OptionPanel.vue
  28. 44 9
      src/components/Widgets/Classroom/AdminClassroomShow/components/ClassroomInfo.vue
  29. 270 0
      src/components/Widgets/Classroom/AdminClassroomShow/components/GraderList.vue
  30. 270 0
      src/components/Widgets/Classroom/AdminClassroomShow/components/LeaderList.vue
  31. 44 5
      src/components/Widgets/Classroom/AdminClassroomShow/components/SessionList.vue
  32. 149 0
      src/components/Widgets/Classroom/AdminClassroomShow/components/TeamList.vue
  33. 33 10
      src/components/Widgets/Classroom/AllClassrooms/AllClassrooms.vue
  34. 8 144
      src/components/Widgets/Classroom/AllClassrooms/OptionPanel.vue
  35. 16 2
      src/components/Widgets/Classroom/AllClassrooms/components/Filters.vue
  36. 361 0
      src/components/Widgets/Classroom/ClassroomList/ClassroomList.vue
  37. 28 0
      src/components/Widgets/Classroom/ClassroomList/OptionPanel.vue
  38. 627 0
      src/components/Widgets/Classroom/ClassroomShow/ClassroomShow.vue
  39. 28 0
      src/components/Widgets/Classroom/ClassroomShow/OptionPanel.vue
  40. 1 1
      src/components/Widgets/Content/AdminContentShow/AdminContentShow.vue
  41. 169 0
      src/components/Widgets/Event/AdminEventCreate/AdminEventCreate.vue
  42. 21 0
      src/components/Widgets/Event/AdminEventCreate/OptionPanel.vue
  43. 215 0
      src/components/Widgets/Event/AdminEventList/AdminEventList.vue
  44. 21 0
      src/components/Widgets/Event/AdminEventList/OptionPanel.vue
  45. 105 0
      src/components/Widgets/Event/AdminEventShow/AdminEventShow.vue
  46. 36 0
      src/components/Widgets/Event/AdminEventShow/OptionPanel.vue
  47. 205 0
      src/components/Widgets/Event/AdminEventShow/components/EventInfo.vue
  48. 35 0
      src/components/Widgets/Event/AdminEventShow/components/LiveStreaming.vue
  49. 225 0
      src/components/Widgets/Event/AdminEventShow/components/MembersList.vue
  50. 337 0
      src/components/Widgets/Event/AllEvents/AllEvents.vue
  51. 164 0
      src/components/Widgets/Event/AllEvents/OptionPanel.vue
  52. 173 0
      src/components/Widgets/Event/AllEvents/components/Filters.vue
  53. 257 0
      src/components/Widgets/Event/LastEvents/LastEvents.vue
  54. 28 0
      src/components/Widgets/Event/LastEvents/OptionPanel.vue
  55. 164 0
      src/components/Widgets/Event/ShowEventInfo/OptionPanel.vue
  56. 531 0
      src/components/Widgets/Event/ShowEventInfo/ShowEventInfo.vue
  57. 39 16
      src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue
  58. 38 5
      src/components/Widgets/Invoice/InvoiceThankyou/InvoiceThankyou.vue
  59. 3 1
      src/components/Widgets/Other/ClassroomCarousel/ClassroomCarousel.vue
  60. 63 16
      src/components/Widgets/Other/Shop/ShopCompleteInfo/ShopCompleteInfo.vue
  61. 22 4
      src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue
  62. 18 5
      src/components/Widgets/Other/ShowClassroomInfo/ShowClassroomInfo.vue
  63. 4 2
      src/components/Widgets/ProjectAttendanceSheet/AdminProjectAttendanceSheetShow/AdminProjectAttendanceSheetShow.vue
  64. 9 2
      src/components/Widgets/Registration/RegistrationList/OptionPanel.vue
  65. 30 3
      src/components/Widgets/Registration/RegistrationList/RegistrationList.vue
  66. 112 30
      src/components/Widgets/SessionAttendanceSheet/AdminSessionAttendanceSheetList/AdminSessionAttendanceSheetList.vue
  67. 9 2
      src/components/Widgets/SessionAttendanceSheet/AdminSessionAttendanceSheetList/OptionPanel.vue
  68. 215 0
      src/components/Widgets/Team/AdminTeamCreate/AdminTeamCreate.vue
  69. 21 0
      src/components/Widgets/Team/AdminTeamCreate/OptionPanel.vue
  70. 446 0
      src/components/Widgets/Team/AdminTeamShow/AdminTeamShow.vue
  71. 21 0
      src/components/Widgets/Team/AdminTeamShow/OptionPanel.vue
  72. 219 0
      src/components/Widgets/Team/LeaderTeamShow/LeaderTeamShow.vue
  73. 21 0
      src/components/Widgets/Team/LeaderTeamShow/OptionPanel.vue
  74. 138 0
      src/components/Widgets/Team/LeaderTeamsIndex/LeaderTeamsIndex.vue
  75. 21 0
      src/components/Widgets/Team/LeaderTeamsIndex/OptionPanel.vue
  76. 1 0
      src/layouts/AdminLayout.vue
  77. 1 0
      src/layouts/UserPanelLayout.vue
  78. 153 0
      src/models/Event.js
  79. 56 0
      src/models/EventRegistration.js
  80. 8 0
      src/models/ProjectAttendanceSheets.js
  81. 6 0
      src/models/Registration.js
  82. 56 0
      src/models/Team.js
  83. 21 1
      src/models/UnitCategory.js
  84. 16 2
      src/models/User.js
  85. 1 1
      src/pages/Admin/Classroom/SessionActivitySheets/sessionActivitySheets.vue
  86. 23 0
      src/pages/Admin/Classroom/Team/create.vue
  87. 23 0
      src/pages/Admin/Classroom/Team/show.vue
  88. 10 238
      src/pages/Admin/Classroom/create.vue
  89. 11 249
      src/pages/Admin/Classroom/index.vue
  90. 9 4
      src/pages/Admin/Classroom/project/members.vue
  91. 1 1
      src/pages/Admin/Classroom/show.vue
  92. 110 0
      src/pages/Admin/Event/ProjectAttendanceSheet/create.vue
  93. 171 0
      src/pages/Admin/Event/ProjectAttendanceSheet/members.vue
  94. 23 0
      src/pages/Admin/Event/ProjectAttendanceSheet/show.vue
  95. 23 0
      src/pages/Admin/Event/SessionActivitySheets/Assignment.vue
  96. 23 0
      src/pages/Admin/Event/SessionActivitySheets/Attendance.vue
  97. 23 0
      src/pages/Admin/Event/SessionActivitySheets/sessionActivitySheets.vue
  98. 23 0
      src/pages/Admin/Event/Ticket.vue
  99. 23 0
      src/pages/Admin/Event/create.vue
  100. 172 0
      src/pages/Admin/Event/ema-create.vue

+ 3 - 3
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-crud": "^0.3.53",
+    "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",

+ 19 - 13
src-pwa/custom-service-worker.js

@@ -9,10 +9,10 @@
 import { clientsClaim } from 'workbox-core'
 import {
   precacheAndRoute,
-  cleanupOutdatedCaches
-  // createHandlerBoundToURL
+  cleanupOutdatedCaches,
+  createHandlerBoundToURL
 } from 'workbox-precaching'
-// import { registerRoute, NavigationRoute } from 'workbox-routing'
+import { registerRoute, NavigationRoute } from 'workbox-routing'
 
 self.skipWaiting()
 clientsClaim()
@@ -22,13 +22,19 @@ 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: [
+          /api$/,
+          /sw\.js$/,
+          /workbox-(.)*\.js$/
+        ]
+      }
+    )
+  )
+}

+ 12 - 0
src/api/APIGateway.js

@@ -3,6 +3,8 @@ import AuthAPI from 'src/api/models/Auth.js'
 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'
@@ -24,6 +26,10 @@ import TicketCategoryAPI from 'src/api/models/TicketCategory.js'
 import SessionTemplateAPI from 'src/api/models/SessionTemplate.js'
 import ContentCategoryAPI from 'src/api/models/ContentCategory.js'
 import InstalmentOfferAPI from 'src/api/models/InstalmentOffer.js'
+import ClassroomGraderAPI from 'src/api/models/ClassroomGraders.js'
+import ClassroomLeadersAPI from 'src/api/models/ClassroomLeaders.js'
+import TeamRegistrationsAPI from 'src/api/models/TeamRegistrations.js'
+import EventRegistrationApi from 'src/api/models/EventRegistration.js'
 import ClassroomRegistrationAPI from 'src/api/models/ClassroomRegistration.js'
 import ProjectAttendanceSheetsAPI from 'src/api/models/ProjectAttendanceSheets.js'
 import SessionAttendanceSheetsAPI from 'src/api/models/SessionAttendanceSheets.js'
@@ -35,6 +41,8 @@ export const APIGateway = {
   user: new UserAPI(),
   unit: new UnitAPI(),
   post: new PostAPI(),
+  team: new TeamAPI(),
+  event: new EventAPI(),
   media: new MediaAPI(),
   ticket: new TicketAPI(),
   wallet: new WalletAPI(),
@@ -56,6 +64,10 @@ export const APIGateway = {
   sessionTemplate: new SessionTemplateAPI(),
   contentCategory: new ContentCategoryAPI(),
   instalmentOffer: new InstalmentOfferAPI(),
+  classroomGrader: new ClassroomGraderAPI(),
+  classroomLeaders: new ClassroomLeadersAPI(),
+  teamRegistrations: new TeamRegistrationsAPI(),
+  eventRegistration: new EventRegistrationApi(),
   classroomRegistration: new ClassroomRegistrationAPI(),
   projectAttendanceSheets: new ProjectAttendanceSheetsAPI(),
   sessionAttendanceSheets: new SessionAttendanceSheetsAPI()

+ 47 - 0
src/api/models/Classroom.js

@@ -1,6 +1,7 @@
 import { appApi } from 'src/boot/axios.js'
 import { Invoice } from 'src/models/Invoice.js'
 import APIRepository from '../classes/APIRepository.js'
+import { Registration } from 'src/models/Registration.js'
 import { Classroom, ClassroomList } from 'src/models/Classroom.js'
 
 export default class ClassroomAPI extends APIRepository {
@@ -11,6 +12,8 @@ export default class ClassroomAPI extends APIRepository {
       byId: (id) => '/lma/classrooms/' + id,
       enroll: (id) => '/lma/classrooms/' + id + '/enroll',
       drop: (id) => '/lma/classrooms/' + id + '/drop',
+      register: (id) => '/lma/classrooms/' + id + '/register',
+      leaders: (id) => '/lma/classrooms/' + id + '/leaders',
       members: '/lma/registrations',
       activitySheet: '/lma/registrations/activity_sheet',
       enrollByAdmin: (classroomId, userId) => '/lma/classrooms/' + classroomId + '/enrolll?user_id=' + userId,
@@ -29,6 +32,7 @@ export default class ClassroomAPI extends APIRepository {
       api: this.api,
       request: this.APIAdresses.base,
       data: this.getNormalizedSendData({
+        unit__category__type: null, // String
         per_page: 10, // Number
         page: 1 // Number
       }, data),
@@ -69,6 +73,32 @@ export default class ClassroomAPI extends APIRepository {
     })
   }
 
+  register (id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.register(id),
+      resolveCallback: (response) => {
+        const data = {
+          type: null,
+          model: response.data
+        }
+        if (response.data.status === 'PAYING') {
+          data.type = 'Invoice'
+          data.model = new Invoice(response.data)
+        } else if (response.data.status === 'REGISTERED') {
+          data.type = 'Registration'
+          data.model = new Registration(response.data)
+        }
+
+        return data
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
   enrollByUser (id) {
     return this.sendRequest({
       apiMethod: 'put',
@@ -97,6 +127,23 @@ export default class ClassroomAPI extends APIRepository {
     })
   }
 
+  appendLeaders (data) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.leaders(data.classroomId),
+      data: {
+        leaders: data.leaders // array of numbers
+      },
+      resolveCallback: (response) => {
+        return new Classroom(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
   dropByAdmin (classroomId, userId) {
     return this.sendRequest({
       apiMethod: 'put',

+ 16 - 0
src/api/models/ClassroomGraders.js

@@ -0,0 +1,16 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+
+export default class ClassroomGraderAPI extends APIRepository {
+  constructor() {
+    super('classroom_graders', appApi, '/lma/classroom_graders')
+    this.APIAdresses = {
+      base: '/lma/classroom_graders',
+      byId: (id) => '/lma/classroom_graders/' + id
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base,
+      byId: (id) => this.name + this.APIAdresses.byId(id)
+    }
+  }
+}

+ 16 - 0
src/api/models/ClassroomLeaders.js

@@ -0,0 +1,16 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+
+export default class ClassroomLeaderAPI extends APIRepository {
+  constructor() {
+    super('classroom_leaders', appApi, '/lma/classroom_leaders')
+    this.APIAdresses = {
+      base: '/lma/classroom_leaders',
+      byId: (id) => '/lma/classroom_leaders/' + id
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base,
+      byId: (id) => this.name + this.APIAdresses.byId(id)
+    }
+  }
+}

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

@@ -0,0 +1,173 @@
+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'
+import { EventRegistration } from 'src/models/EventRegistration'
+
+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',
+      register: (id) => '/ema/events/' + id + '/register',
+      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
+      }
+    })
+  }
+
+  register (id) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.register(id),
+      resolveCallback: (response) => {
+        const data = {
+          type: null,
+          model: response.data
+        }
+        if (response.data.status === 'PAYING') {
+          data.type = 'Invoice'
+          data.model = new Invoice(response.data)
+        } else if (response.data.status === 'REGISTERED') {
+          data.type = 'EventRegistration'
+          data.model = new EventRegistration(response.data)
+        }
+
+        return 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
+      }
+    })
+  }
+}

+ 73 - 0
src/api/models/EventRegistration.js

@@ -0,0 +1,73 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { Registration, RegistrationList } from 'src/models/Registration.js'
+
+export default class EventRegistrationAPI extends APIRepository {
+  constructor() {
+    super('registrations', appApi, '/ema/registrations', Registration)
+    this.APIAdresses = {
+      base: '/ema/registrations',
+      exportReport: '/ema/registrations/export_report',
+      classroomDetail: (registrationId) => '/ema/registrations/' + registrationId + '/classroom_detail',
+      byId: (id) => '/ema/registrations/' + id
+    }
+    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({
+        classroom: null, // Number
+        owner: null, // Number
+        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 RegistrationList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  exportReport(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.exportReport,
+      data: this.getNormalizedSendData({
+        classroom: null, // Number
+        type: null, // String
+        status: null // String
+      }, data),
+      responseType: 'blob',
+      resolveCallback: (response) => {
+        return response.data // xlsxData
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 1 - 1
src/api/models/Invoice.js

@@ -4,7 +4,7 @@ import { Invoice, InvoiceList } from 'src/models/Invoice.js'
 
 export default class InvoiceAPI extends APIRepository {
   constructor() {
-    super('invoice', appApi)
+    super('invoice', appApi, '/accounting/invoices', Invoice)
     this.APIAdresses = {
       base: '/accounting/invoices',
       byId: (id) => '/accounting/invoices/' + id,

+ 2 - 0
src/api/models/Registration.js

@@ -23,6 +23,8 @@ export default class RegistrationAPI extends APIRepository {
       api: this.api,
       request: this.APIAdresses.base,
       data: this.getNormalizedSendData({
+        classroom: null, // Number
+        classroom__unit__category__type: null, // String
         owner: null, // Number
         per_page: 10, // Number
         page: 1 // Number

+ 22 - 0
src/api/models/SessionAttendanceSheets.js

@@ -7,6 +7,8 @@ export default class SessionAttendanceSheetsAPI extends APIRepository {
     super('session', appApi, '/lma/session_attendance_sheets', SessionAttendanceSheets)
     this.APIAdresses = {
       base: '/lma/session_attendance_sheets',
+      exportReport: '/lma/session_attendance_sheets/export_report',
+      importList: '/lma/session_attendance_sheets/import_list',
       sessionSheets: '/lma/session_attendance_sheets/session_sheets',
       submitAssignment: '/lma/session_attendance_sheets/submit_assignment',
       submitAttendanceStatus: '/lma/session_attendance_sheets/submit_attendance_status',
@@ -127,4 +129,24 @@ export default class SessionAttendanceSheetsAPI extends APIRepository {
       }
     })
   }
+
+  exportReport (data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.exportReport,
+      data: this.getNormalizedSendData({
+        session: null, // Number
+        type: 'list', // String
+        registration__status: null // String -> ex: REGISTERED
+      }, data),
+      responseType: 'blob',
+      resolveCallback: (response) => {
+        return response.data // xlsxData
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
 }

+ 51 - 0
src/api/models/Team.js

@@ -0,0 +1,51 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+
+export default class TeamAPI extends APIRepository {
+  constructor() {
+    super('teams', appApi, '/lma/teams')
+    this.APIAdresses = {
+      base: '/lma/teams',
+      classroomTeamsWithJoinStatus: '/lma/teams/classroom_teams_with_join_status',
+      myLeadingTeams: '/lma/teams/my_leading_teams',
+      byId: (id) => '/lma/teams/' + id,
+      register: (id) => '/lma/teams/' + id + '/register',
+      registrations: (id) => '/lma/teams/' + id + '/registrations'
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base,
+      byId: (id) => this.name + this.APIAdresses.byId(id)
+    }
+  }
+
+  appendRegistrations (data) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.registrations(data.teamId),
+      data: {
+        registrations: data.registrations // array of numbers
+      },
+      resolveCallback: (response) => {
+        return response.data
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  register (teamId) {
+    return this.sendRequest({
+      apiMethod: 'put',
+      api: this.api,
+      request: this.APIAdresses.register(teamId),
+      resolveCallback: (response) => {
+        return response.data
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 14 - 0
src/api/models/TeamRegistrations.js

@@ -0,0 +1,14 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+
+export default class TeamAPI extends APIRepository {
+  constructor() {
+    super('teams', appApi, '/lma/teams')
+    this.APIAdresses = {
+      base: '/lma/team_registrations'
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base
+    }
+  }
+}

+ 1 - 0
src/api/models/Unit.js

@@ -22,6 +22,7 @@ export default class UnitAPI extends APIRepository {
       request: this.APIAdresses.base,
       data: this.getNormalizedSendData({
         category: null, // Number
+        category__type: null, // String
         per_page: 10, // Number
         page: 1 // Number
       }, data),

+ 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
 }
 

+ 21 - 10
src/boot/axios.js

@@ -32,22 +32,33 @@ const AxiosHooks = (function () {
     if (statusCode >= 500 && statusCode <= 599) {
       messages.push('مشکلی رخ داده است. مجدد تلاش کنید.')
     } else if (statusCode === 404) {
-      messages.push('موردی یافت نشد.')
+      if (error.response.data.detail) {
+        messages.push(error.response.data.detail)
+      } else {
+        messages.push('موردی یافت نشد.')
+      }
     } else if (statusCode === 401) {
-      const message = error.response.data.message
-      messages.push(message)
+      if (error.response.data.detail) {
+        messages.push(error.response.data.detail)
+      }
+      // const message = error.response.data.message
+      // messages.push(message)
       deAuthorizeUser(router, store)
     } else if (statusCode === 403) {
       const message = error.response.data.detail
       messages.push(message)
     } else if (error.response.data) {
-      for (const key of Object.keys(error.response.data)) {
-        if (typeof error.response.data[key] === 'string') {
-          messages.push(key + ': ' + error.response.data[key])
-        } else if (Array.isArray(error.response.data[key])) {
-          error.response.data[key].forEach(message => {
-            messages.push(key + ': ' + message)
-          })
+      if (error.response.data.detail) {
+        messages.push(error.response.data.detail)
+      } else {
+        for (const key of Object.keys(error.response.data)) {
+          if (typeof error.response.data[key] === 'string') {
+            messages.push(key + ': ' + error.response.data[key])
+          } else if (Array.isArray(error.response.data[key])) {
+            error.response.data[key].forEach(message => {
+              messages.push(key + ': ' + message)
+            })
+          }
         }
       }
     }

+ 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: -13px;
+        top: -13px;
+        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>

+ 16 - 4
src/components/Menu/SideMenu/MenuItem.vue

@@ -2,7 +2,7 @@
   <div v-for="(item , index) in computedMenu"
        :key="index"
        class="menu-item">
-    <q-expansion-item v-if="!loading && item.children && item.children.length > 0 && item.show"
+    <q-expansion-item v-if="!loading && item.children && item.children.length > 0 && item.show && hasOneOfThisRoles(item.roles)"
                       :header-style="{height:'40px', borderRadius: '14px'}"
                       :label="item.title"
                       :icon="item.icon"
@@ -18,8 +18,9 @@
                :key="i">
             <menu-item v-if="subItem.children && subItem.children.length > 0"
                        :items="[subItem]"
+                       :user="user"
                        @item-selected="itemSelected(item)" />
-            <q-item v-else
+            <q-item v-else-if="hasOneOfThisRoles(subItem.roles)"
                     v-ripple
                     clickable
                     :to="redirectRoute(subItem)"
@@ -32,7 +33,9 @@
                 {{ subItem.title }}
               </q-tooltip>
               <q-item-section class="list-child-section">
-                <q-item-label lines="1">{{ subItem.title }}</q-item-label>
+                <q-item-label lines="1">
+                  {{ subItem.title }}
+                </q-item-label>
               </q-item-section>
               <q-badge v-if="subItem.badge"
                        class="badge q-py-xs"
@@ -46,7 +49,7 @@
       </div>
     </q-expansion-item>
     <!--    (item.title === clickedItem.title) || -->
-    <q-item v-else-if="!loading && !item.children"
+    <q-item v-else-if="!loading && !item.children && hasOneOfThisRoles(item.roles)"
             v-ripple
             clickable
             :to="redirectRoute(item)"
@@ -70,6 +73,8 @@
 </template>
 
 <script>
+import { User } from 'src/models/User.js'
+
 export default {
   name: 'MenuItem',
   props: {
@@ -77,6 +82,10 @@ export default {
       type: String,
       default: ''
     },
+    user: {
+      type: User,
+      default: new User()
+    },
     menu: {
       // ToDO: will be deprecate
       type: Array,
@@ -149,6 +158,9 @@ export default {
     },
     inactiveAllItems () {
 
+    },
+    hasOneOfThisRoles (roles) {
+      return !roles || roles.find(role => this.user.hasRole(role))
     }
   }
 }

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

@@ -115,7 +115,7 @@
           <span class="btn-label">
             حساب کاربری
           </span>
-          <q-menu v-if="user.isAdmin() || user.isSuperUser()"
+          <q-menu v-if="canSeeAdminPanel"
                   fit>
             <q-list>
               <q-item v-close-popup
@@ -127,7 +127,7 @@
               </q-item>
               <q-item v-close-popup
                       clickable
-                      :to="{ name: 'Admin.User.Index' }">
+                      :to="{ name: 'Admin' }">
                 <q-item-section>
                   پنل ادمین
                 </q-item-section>
@@ -179,6 +179,9 @@ export default {
     }
   },
   computed: {
+    canSeeAdminPanel () {
+      return this.user.isAdmin() || this.user.isSuperUser() || this.user.isEducationalDepartmentHead() || this.user.isFinancialDepartmentHead()
+    },
     isAdminPage () {
       return this.$route.name && this.$route.name.includes('Admin.')
     },
@@ -204,7 +207,7 @@ export default {
       this.user = this.$store.getters['Auth/user']
     },
     goToProperPanel () {
-      if (this.user.isSuperUser()) {
+      if (this.canSeeAdminPanel) {
         // this.$router.push({ name: 'Admin.User.Index' })
       } else {
         this.$router.push({ name: 'UserPanel.Profile.UserInfo' })

+ 40 - 1
src/components/Template/SideBard/AdminPanelSideBar.vue

@@ -11,22 +11,26 @@
     </div>
     <q-list class="side-menu-list"
             padding>
-      <menu-item :menu="adminMenuItems" />
+      <menu-item :menu="adminMenuItems"
+                 :user="user" />
     </q-list>
   </div>
 </template>
 
 <script>
+import { mixinAuthData } from 'src/mixin/Mixins.js'
 import menuItem from 'src/components/Menu/SideMenu/MenuItem.vue'
 
 export default {
   name: 'AdminPanelSideBar',
   components: { menuItem },
+  mixins: [mixinAuthData],
   data () {
     return {
       adminMenuItems: [
         {
           title: 'کاربران',
+          roles: ['superuser'],
           routeName: 'Admin.User.Index',
           icon: 'groups',
           show: true,
@@ -34,6 +38,7 @@ export default {
         },
         {
           title: 'دوره های آموزشی',
+          roles: ['superuser', 'educational_department_head'],
           icon: 'import_contacts',
           routeName: null,
           show: true,
@@ -42,20 +47,39 @@ export default {
           children: [
             {
               title: 'درس ها و چارت آموزشی',
+              roles: ['superuser', 'educational_department_head'],
               routeName: 'Admin.Category.Index',
               show: true,
               active: false
             },
             {
               title: 'دوره های آموزشی',
+              roles: ['superuser', 'educational_department_head'],
               routeName: 'Admin.Classroom.Index',
               show: true,
               active: false
             }
           ]
         },
+        {
+          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'],
           icon: 'diversity_2',
           routeName: null,
           show: true,
@@ -64,12 +88,14 @@ export default {
           children: [
             {
               title: 'درس ها و چارت آموزشی',
+              roles: ['superuser', 'educational_department_head'],
               routeName: 'Admin.Category.DiscussionCircles.Index',
               show: true,
               active: false
             },
             {
               title: 'حلقه های مباحثاتی',
+              roles: ['superuser', 'educational_department_head'],
               routeName: 'Admin.Classroom.DiscussionCircles.Index',
               show: true,
               active: false
@@ -78,6 +104,7 @@ export default {
         },
         {
           title: 'درخواست های آموزش',
+          roles: ['superuser', 'educational_department_head'],
           routeName: 'Admin.Ticket.List.Educational',
           icon: 'local_activity',
           show: true,
@@ -85,6 +112,7 @@ export default {
         },
         {
           title: 'محتوا',
+          roles: ['superuser'],
           icon: 'live_tv',
           routeName: null,
           show: true,
@@ -93,18 +121,21 @@ export default {
           children: [
             {
               title: 'دسته بندی ها',
+              roles: ['superuser'],
               routeName: 'Admin.ContentCategory.List',
               show: true,
               active: false
             },
             {
               title: 'لیست محتوا ها',
+              roles: ['superuser'],
               routeName: 'Admin.Content.List',
               show: true,
               active: false
             },
             {
               title: 'درخواست ها',
+              roles: ['superuser'],
               routeName: 'Admin.Content.Ticket',
               show: true,
               active: false
@@ -113,6 +144,7 @@ export default {
         },
         {
           title: 'بلاگ',
+          roles: ['superuser'],
           icon: 'rss_feed',
           routeName: null,
           show: true,
@@ -121,12 +153,14 @@ export default {
           children: [
             {
               title: 'دسته بندی ها',
+              roles: ['superuser'],
               routeName: 'Admin.PostCategory.List',
               show: true,
               active: false
             },
             {
               title: 'لیست پست ها',
+              roles: ['superuser'],
               routeName: 'Admin.Post.List',
               show: true,
               active: false
@@ -135,6 +169,7 @@ export default {
         },
         {
           title: 'مدیریت مالی',
+          roles: ['superuser', 'financial_department_head'],
           icon: 'payments',
           routeName: null,
           show: true,
@@ -143,18 +178,21 @@ export default {
           children: [
             {
               title: 'صورتحساب ها',
+              roles: ['superuser', 'financial_department_head'],
               routeName: 'Admin.Invoice.List',
               show: true,
               active: false
             },
             {
               title: 'پرداخت ها',
+              roles: ['superuser', 'financial_department_head'],
               routeName: 'Admin.Payment.List',
               show: true,
               active: false
             },
             {
               title: 'درخواست ها',
+              roles: ['superuser', 'financial_department_head'],
               routeName: 'Admin.Invoice.Ticket.List',
               show: true,
               active: false
@@ -163,6 +201,7 @@ export default {
         },
         {
           title: 'تمام درخواست ها',
+          roles: ['superuser'],
           routeName: 'Admin.Ticket.List',
           icon: 'confirmation_number',
           show: true,

+ 26 - 1
src/components/Template/SideBard/UserPanelSideBar.vue

@@ -11,7 +11,8 @@
     </div>
     <q-list class="side-menu-list"
             padding>
-      <menu-item :menu="userProfileMenuItems" />
+      <menu-item :menu="userProfileMenuItems"
+                 :user="user" />
     </q-list>
   </div>
 </template>
@@ -57,6 +58,30 @@ export default {
               routeName: 'UserPanel.Profile.AllClassrooms',
               show: true,
               active: false
+            },
+            {
+              title: 'گروه های من',
+              roles: ['team_leader'],
+              routeName: 'UserPanel.Leader.Teams.Index',
+              icon: 'diversity_3',
+              show: true,
+              active: false
+            }
+          ]
+        },
+        {
+          title: 'رویدادها',
+          routeName: null,
+          icon: 'auto_stories',
+          show: true,
+          open: true,
+          active: false,
+          children: [
+            {
+              title: 'رویدادهای من',
+              routeName: 'UserPanel.Profile.AllEvents',
+              show: true,
+              active: false
             }
           ]
         },

+ 248 - 122
src/components/Widgets/Classroom/AdminClassroomCreate/AdminClassroomCreate.vue

@@ -1,169 +1,295 @@
 <template>
-  <div class="AdminPostCreate"
-       :style="localOptions.style">
-    <div class="flex justify-end">
-      <q-btn flat
-             color="grey"
-             @click="$router.go(-1)">
-        بازگشت
-        >
-      </q-btn>
-    </div>
+  <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"
+                   ref="classroomEntityCreate"
+                   :key="classroomEntityCreateKey"
                    v-model:value="inputs"
-                   title="ایجاد پست"
+                   :title="'مشخصات ' + classroomTypeTitle"
                    :api="api"
                    :entity-id-key="entityIdKey"
                    :entity-param-key="entityParamKey"
-                   :show-route-name="showRouteName"
+                   :show-route-name="showPageRouteName"
                    :show-close-button="false"
                    :show-edit-button="false"
                    :show-expand-button="false"
                    :show-save-button="false"
-                   :show-reload-button="false" />
+                   :show-reload-button="false">
+      <template #after-form-builder>
+        <div class="flex justify-end q-mt-md">
+          <q-btn color="primary"
+                 :label="'ایجاد ' + classroomTypeTitle + 'جدید'"
+                 @click="createClassroom" />
+        </div>
+      </template>
+    </entity-create>
   </div>
 </template>
 
 <script>
 import { shallowRef } from 'vue'
 import { EntityCreate } from 'quasar-crud'
+import Enums from 'src/assets/Enums/Enums.js'
 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'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import { UnitCategory } from 'src/models/UnitCategory.js'
+import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
 
-const BtnControlComp = shallowRef(BtnControl)
-const PostCategorySelectorComp = shallowRef(PostCategorySelector)
-const FormBuilderTiptapEditorComp = shallowRef(FormBuilderTiptapEditor)
+const FormBuilderInputEditorComp = shallowRef(FormBuilderInputEditor)
 
 export default {
-  name: 'AdminPostCreate',
-  components: { EntityCreate },
-  mixins: [mixinWidget, PostMixin],
+  name: 'Admin.Classroom.Create',
+  components: {
+    EntityCreate
+  },
+  mixins: [mixinWidget],
   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,
+      unitsLoading: false,
+      classroomEntityCreateKey: Date.now(),
+      newUnitLoading: false,
+      newUnitName: null,
+      newUnitSessionCount: null,
+      api: APIGateway.classroom.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: 'separator', name: 'space1', label: 'مشخصات', className: 'custom-separator', 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: 'category', options: [], 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: Enums.classroomHoldingTypes, 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: 'classroom_address', responseKey: 'classroom_address', placeholder: ' ', label: 'آدرس مکان فیزیکی', 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: '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: '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: 'select',
+          name: 'holding_month',
+          responseKey: 'holding_month',
+          options: [
+            'فروردین',
+            'اردیبهشت',
+            'خرداد',
+            'تیر',
+            'مرداد',
+            'شهریور',
+            'مهر',
+            'آبان',
+            'آذر',
+            'دی',
+            'بهمن',
+            'اسفند'
+          ],
+          value: null,
+          label: 'انتخاب ماه',
+          placeholder: ' ',
+          col: 'col-md-3'
         },
-        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ایجاد محتوا', placeholder: ' ', atClick: () => {}, col: 'col-md-6' }
+        { 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: Enums.genders, 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: {
+    classroomTypeTitle () {
+      const unitCategory = new UnitCategory({ type: this.localOptions.classroomType })
+      return unitCategory.type_info.label
+    },
+    createPageRouteName () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'Admin.Classroom.Create'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'Admin.Event.Create'
+      }
+      return 'Admin.Classroom.Create'
+    },
+    showPageRouteName () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'Admin.Classroom.Show'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'Admin.Event.Show'
+      }
+      return 'Admin.Classroom.Show'
+    },
+    selectedCategoryId () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'category')?.value
+    }
+  },
+  watch: {
+    selectedCategoryId () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'space1', 'value', null)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'options', [])
+      this.getUnits(this.selectedCategoryId)
+    }
+  },
   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
+    this.setClassroomTypeOfInputs()
+    this.preLoadData()
+      .then(() => {
+        this.mounted = true
+      })
+      .catch(() => {
+      })
   },
   methods: {
-    setActionBtn () {
-      this.inputs.forEach((item, index) => {
-        if (item.name === 'btn') {
-          this.inputs[index].atClick = this.onSubmit
-        }
+    setClassroomTypeOfInputs () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'professor', 'label', 'استاد')
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'professor', 'label', 'برگزار کننده')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'sessions_frequency', 'label', 'عنوان')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'label', 'دسته')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'type', 'hidden') // unit -> category__type filter
+        FormBuilderAssist.setAttributeByName(this.inputs, 'allowed_absence_count', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'effective_absence_coefficient', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'mandatory_assignment_count', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'special_passing_mark', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'minimum_conditional_passing_mark', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'minimum_clean_passing_mark', 'type', 'hidden')
+      }
+    },
+    setInputAttr (name, attr, value) {
+      this.$refs.classroomEntityCreate.setInputAttributeByName(name, attr, value)
+    },
+    preLoadData () {
+      return new Promise((resolve, reject) => {
+        const promise1 = this.getProfessors()
+        const promise2 = this.getCategories()
+        const promise3 = (this.localOptions.classroomType === 'EVENT') ? this.getUnits(null, this.localOptions.classroomType) : (Promise.resolve())
+        this.$nextTick(() => {
+          Promise.all([promise1, promise2, promise3])
+            .then(() => {
+              resolve()
+            })
+            .catch(() => {
+              reject()
+            })
+        })
       })
     },
-    onSubmit () {
-      this.create()
+    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(() => {})
     },
-    create() {
-      this.entityLoading = true
-      this.$refs.entityCreate.createEntity()
-        .then(() => {
-          this.entityLoading = false
+    getCategories () {
+      return APIGateway.unitCategory.index({ per_page: 9999, type: this.localOptions.classroomType })
+        .then((categories) => {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'options', categories.list.list.map(item => {
+            return {
+              value: item.id,
+              label: item.title
+            }
+          }))
+        })
+        .catch(() => {})
+    },
+    getUnits (selectedcategoryId = null, categoryType = null) {
+      this.unitsLoading = true
+      APIGateway.unit.index({ per_page: 9999, category: selectedcategoryId, category__type: categoryType })
+        .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.entityLoading = false
+          this.unitsLoading = false
         })
+    },
+    getUserFullname (user) {
+      return user.firstname + ' ' + user.lastname
+    },
+    createClassroom () {
+      this.$refs.classroomEntityCreate.createEntity()
     }
   }
 }
 </script>
-
-<style scoped lang="scss">
-.AdminPostCreate {
-  .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>

+ 9 - 2
src/components/Widgets/Classroom/AdminClassroomCreate/OptionPanel.vue

@@ -1,13 +1,15 @@
 <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 { UnitCategory } from 'src/models/UnitCategory.js'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
 
@@ -16,6 +18,11 @@ export default defineComponent({
   components: {
     OptionPanelTabs
   },
-  mixins: [mixinOptionPanel]
+  mixins: [mixinOptionPanel],
+  data () {
+    return {
+      classroomTypeOptions: (new UnitCategory()).typeEnums
+    }
+  }
 })
 </script>

+ 183 - 94
src/components/Widgets/Classroom/AdminClassroomList/AdminClassroomList.vue

@@ -1,26 +1,47 @@
 <template>
-  <div class="AdminInvoiceList"
-       :style="localOptions.style">
+  <div class="AdminClassroomIndex">
+    <div class="add-new-action-row">
+      <q-btn color="primary"
+             :to="{name: createPageRouteName}">
+        <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>
+        ایجاد
+        {{ classroomTypeTitle }}
+        جدید
+      </q-btn>
+    </div>
     <entity-index v-if="mounted"
                   ref="entityIndex"
                   v-model:value="inputs"
-                  title="لیست صورتحساب ها"
+                  :title="'لیست ' + classroomTypeTitle"
                   :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}">
+                  :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'">
-          <q-btn color="primary"
-                 :to="{name: 'Admin.Invoice.Show', params: {id: inputData.props.row.id}}">
-            جزییات جزییات
-          </q-btn>
+          <div class="action-column-entity-index">
+            <q-btn size="md"
+                   color="primary"
+                   label="جزییات"
+                   :to="{name: showPageRouteName, 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 }}
@@ -32,51 +53,54 @@
 
 <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 Enums from 'src/assets/Enums/Enums.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import BtnControl from 'src/components/Control/btn.vue'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import { UnitCategory } from 'src/models/UnitCategory.js'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
 
 const BtnControlComp = shallowRef(BtnControl)
 
 export default {
-  name: 'AdminInvoiceList',
-  components: { EntityIndex },
+  name: 'Admin.Classroom.Index',
+  components: {
+    EntityIndex,
+    DeleteBtn
+  },
   mixins: [mixinWidget],
   data () {
     return {
-      api: APIGateway.invoice.APIAdresses.base,
-      tableKeys: {
-        data: 'results',
-        total: 'count',
-        currentPage: 'current',
-        perPage: 'per_page',
-        pageKey: 'page'
-      },
+      mounted: false,
       inputs: [
         {
-          type: 'dateTime',
-          name: 'creation_time__gte',
-          label: 'از تاریخ',
+          type: 'select',
+          name: 'status',
+          options: Enums.classroomStatuses,
+          value: null,
+          label: 'وضعیت ',
           placeholder: ' ',
-          col: 'col-md-3 col-12'
+          col: 'col-md-2 col-12'
         },
         {
-          type: 'dateTime',
-          name: 'creation_time__lt',
-          label: 'تا تاریخ',
+          type: 'select',
+          name: 'unit',
+          options: [],
+          value: null,
+          label: 'درس',
           placeholder: ' ',
-          col: 'col-md-3 col-12'
+          col: 'col-md-2 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' }
+        { type: 'hidden', name: 'unit__category__type', value: null },
+        { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
       ],
+      defaultOptions: {
+        classroomType: 'TRAINING'
+      },
+      api: APIGateway.classroom.APIAdresses.base,
       table: {
         columns: [
           {
@@ -94,85 +118,119 @@ export default {
             field: row => row.id
           },
           {
-            name: 'id',
+            name: 'title',
             required: true,
-            label: 'سفارش دهنده',
+            label: 'عنوان',
             align: 'left',
-            field: row => row.owner_info.firstname + ' ' + row.owner_info.lastname
+            field: row => row.title
           },
           {
-            name: 'id',
+            name: 'audience_gender_type',
             required: true,
-            label: 'کدملی سفارش دهنده',
+            label: 'جنسیت',
             align: 'left',
-            field: row => row.owner_info.national_code
+            field: row => (row.audience_gender_type === 'FEMALE') ? 'خواهران' : (row.audience_gender_type === 'MALE') ? 'برادران' : 'خواهران و برادران'
           },
           {
-            name: 'id',
+            name: 'status',
             required: true,
-            label: 'عنوان',
+            label: 'وضعیت',
             align: 'left',
-            field: row => this.getInvoiceTitle(row)
+            field: row => this.getClassroomStatusesTitle(row.status)
           },
           {
-            name: 'id',
+            name: 'holding_type',
             required: true,
-            label: 'نوع صورتحساب',
+            label: 'نوع برگزاری',
             align: 'left',
-            field: row => (new Invoice(row)).type_info.label
+            field: row => this.getClassroomHoldingTypeTitle(row.holding_type)
           },
           {
-            name: 'amount',
+            name: 'price',
             required: true,
-            label: 'مبلغ تراکنش(ریال)',
+            label: 'هزینه(ریال)',
             align: 'left',
-            field: row => row.amount.toLocaleString('fa')
+            field: row => row.price
           },
           {
-            name: 'id',
+            name: 'professor_info',
             required: true,
-            label: 'وضعیت تراکنش',
+            label: 'استاد مربوطه',
             align: 'left',
-            field: row => (new Invoice(row)).status_info.label
+            field: row => row.professor_info?.firstname + ' ' + row.professor_info?.lastname
           },
           {
-            name: 'creation_time',
+            name: 'beginning_registration_period',
             required: true,
-            label: 'زمان ثبت',
+            label: 'تاریخ و زمان ثبت نام',
             align: 'left',
-            field: row => Assist.miladiToShamsi(row.creation_time)
+            field: row => row.beginning_registration_period ? ShamsiDate.getDateTime(row.beginning_registration_period) : '-'
           },
           {
-            name: 'last_modification_time',
+            name: 'beginning_registration_period',
             required: true,
-            label: 'زمان آخرین بروز رسانی',
+            label: 'ترم برگزاری',
             align: 'left',
-            field: row => Assist.miladiToShamsi(row.last_modification_time)
+            field: row => (row.beginning_registration_period) ? ShamsiDate.getTerm(row.beginning_registration_period) : '-'
           },
           {
             name: 'action',
             required: true,
-            label: 'جزییات',
+            label: 'عملیات',
             align: 'left',
-            field: row => ''
+            field: ''
           }
-        ]
+        ],
+        data: []
       },
-      mounted: false,
-      createRouteName: ''
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  computed: {
+    classroomTypeTitle () {
+      const unitCategory = new UnitCategory({ type: this.localOptions.classroomType })
+      return unitCategory.type_info.label
+    },
+    createPageRouteName () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'Admin.Classroom.Create'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'Admin.Event.Create'
+      }
+      return 'Admin.Classroom.Create'
+    },
+    showPageRouteName () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'Admin.Classroom.Show'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'Admin.Event.Show'
+      }
+      return 'Admin.Classroom.Show'
     }
   },
-  mounted() {
+  mounted () {
     this.setActionBtn()
+    this.getUnits()
+    this.setClassroomTypeOfInputs()
     this.mounted = true
   },
   methods: {
-    getInvoiceTitle (invoice) {
-      if (invoice.item_info.item_type === 'CLASSROOM') {
-        return invoice.item_info.title
-      } else {
-        return invoice.item_info.title || '-'
-      }
+    setClassroomTypeOfInputs () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'status', 'label', 'وضعیت ' + this.classroomTypeTitle)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit__category__type', 'value', this.localOptions.classroomType)
+      this.table.columns.forEach(col => {
+        if (col.name === 'title') {
+          col.label = 'نام ' + this.classroomTypeTitle
+        }
+      })
     },
     setActionBtn () {
       this.inputs.forEach((item, index) => {
@@ -183,33 +241,64 @@ export default {
     },
     search () {
       this.$refs.entityIndex.search()
+    },
+    getUnits () {
+      APIGateway.unit.index({ per_page: 9999, category__type: this.localOptions.classroomType })
+        .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 + ' اطمینان دارید؟'
+    },
+    getClassroomHoldingTypeTitle (type) {
+      const target = Enums.classroomHoldingTypes.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    getClassroomStatusesTitle (type) {
+      const target = Enums.classroomStatuses.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
     }
   }
 }
 </script>
 
-<style scoped lang="scss">
-.AdminInvoiceList {
-  .title {
-    font-style: normal;
-    font-weight: 700;
-    font-size: 24px;
-    line-height: 140%;
-    color: #424242;
-    margin-bottom: 27px;
+<style lang="scss" scoped>
+.AdminClassroomIndex {
+  .add-new-action-row {
+    display: flex;
+    flex-flow: row;
+    align-items: center;
+    justify-content: flex-end;
+    margin-bottom: 22px;
   }
-  :deep(.list) {
-    .quasar-crud-index-table {
-      padding: 0;
-      .q-table__container {
-        background-color: transparent;
-        box-shadow: none;
-        border: none;
-        .q-table__top {
-          display: none;
-        }
-      }
-    }
+  .action-column-entity-index {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
   }
 }
 </style>

+ 9 - 2
src/components/Widgets/Classroom/AdminClassroomList/OptionPanel.vue

@@ -1,13 +1,15 @@
 <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 { UnitCategory } from 'src/models/UnitCategory.js'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
 
@@ -16,6 +18,11 @@ export default defineComponent({
   components: {
     OptionPanelTabs
   },
-  mixins: [mixinOptionPanel]
+  mixins: [mixinOptionPanel],
+  data () {
+    return {
+      classroomTypeOptions: (new UnitCategory()).typeEnums
+    }
+  }
 })
 </script>

+ 115 - 14
src/components/Widgets/Classroom/AdminClassroomShow/AdminClassroomShow.vue

@@ -12,7 +12,8 @@
       >
     </q-btn>
   </div>
-  <q-card class="AdminClassroomShow">
+  <q-card v-if="mounted"
+          class="AdminClassroomShow">
     <q-tabs v-model="tab"
             dense
             class="text-grey"
@@ -20,19 +21,35 @@
             indicator-color="primary"
             align="justify"
             narrow-indicator>
-      <q-tab name="classroomInfo"
+      <q-tab v-if="canShowTabPage('classroomInfo')"
+             name="classroomInfo"
              label="مشخصات" />
-      <q-tab name="sessions"
-             label="لیست جلسات" />
-      <q-tab name="projects"
+      <q-tab v-if="canShowTabPage('sessions')"
+             name="sessions"
+             :label="sessionsTabTitle" />
+      <q-tab v-if="canShowTabPage('projects')"
+             name="projects"
              label="پروژه‌ها" />
-      <q-tab name="movies2"
+      <q-tab v-if="canShowTabPage('exams')"
+             name="exams"
              label="آزمون" />
-      <q-tab name="members"
+      <q-tab v-if="canShowTabPage('members')"
+             name="members"
              label="اندیشه جویان" />
-      <q-tab name="activity_sheet"
+      <q-tab v-if="canShowTabPage('teams')"
+             name="teams"
+             label="گروه" />
+      <q-tab v-if="canShowTabPage('graders')"
+             name="graders"
+             label="مصححین" />
+      <q-tab v-if="canShowTabPage('leaders')"
+             name="leaders"
+             label="سرگروه ها" />
+      <q-tab v-if="canShowTabPage('activity_sheet')"
+             name="activity_sheet"
              label="فعالیت کلاسی" />
-      <q-tab name="live_streaming_url"
+      <q-tab v-if="canShowTabPage('live_streaming_url')"
+             name="live_streaming_url"
              label="بخش آنلاین" />
     </q-tabs>
 
@@ -43,24 +60,38 @@
       <q-tab-panel name="classroomInfo"
                    class="q-pa-none">
         <classroom-info v-model:classroom="classroom"
+                        :classroom-type="localOptions.classroomType"
                         :classroom-id="$route.params.id" />
       </q-tab-panel>
       <q-tab-panel name="sessions"
                    class="q-pa-none">
-        <session-list :classroom-id="$route.params.id" />
+        <session-list :classroom-id="$route.params.id"
+                      :classroom-type="localOptions.classroomType" />
       </q-tab-panel>
 
       <q-tab-panel name="projects"
                    class="q-pa-none">
         <project-list :classroom-id="$route.params.id" />
       </q-tab-panel>
-      <q-tab-panel name="movies2">
+      <q-tab-panel name="exams">
         آزمون
       </q-tab-panel>
       <q-tab-panel name="members"
                    class="q-pa-none">
         <members-list :classroom-id="$route.params.id" />
       </q-tab-panel>
+      <q-tab-panel name="teams"
+                   class="q-pa-none">
+        <team-list :classroom-id="$route.params.id" />
+      </q-tab-panel>
+      <q-tab-panel name="graders"
+                   class="q-pa-none">
+        <grader-list :classroom-id="$route.params.id" />
+      </q-tab-panel>
+      <q-tab-panel name="leaders"
+                   class="q-pa-none">
+        <leader-list :classroom-id="$route.params.id" />
+      </q-tab-panel>
       <q-tab-panel name="activity_sheet"
                    class="q-pa-none">
         <activity-sheet-list :classroom-id="$route.params.id" />
@@ -73,10 +104,15 @@
 </template>
 
 <script>
+import TeamList from './components/TeamList.vue'
+import { mixinWidget } from 'src/mixin/Mixins.js'
 import { Classroom } from 'src/models/Classroom.js'
+import LeaderList from './components/LeaderList.vue'
+import GraderList from './components/GraderList.vue'
 import SessionList from './components/SessionList.vue'
 import ProjectList from './components/ProjectList.vue'
 import MembersList from './components/MembersList.vue'
+import { UnitCategory } from 'src/models/UnitCategory.js'
 import ClassroomInfo from './components/ClassroomInfo.vue'
 import LiveStreaming from './components/LiveStreaming.vue'
 import ActivitySheetList from './components/ActivitySheetList.vue'
@@ -85,6 +121,9 @@ import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
 export default {
   name: 'Admin.Classroom.Show',
   components: {
+    TeamList,
+    LeaderList,
+    GraderList,
     SessionList,
     ProjectList,
     MembersList,
@@ -93,8 +132,10 @@ export default {
     LiveStreaming,
     ActivitySheetList
   },
+  mixins: [mixinWidget],
   data () {
     return {
+      mounted: false,
       tab: 'classroomInfo',
       classroom: new Classroom()
     }
@@ -102,6 +143,63 @@ export default {
   computed: {
     classroomId () {
       return this.classroom.id
+    },
+    classroomTypeTitle () {
+      const unitCategory = new UnitCategory({ type: this.localOptions.classroomType })
+      return unitCategory.type_info.label
+    },
+    sessionsTabTitle () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'لیست جلسات'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'لیست برنامه ها'
+      }
+      return 'لیست جلسات'
+    },
+    indexPageRouteName () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'Admin.Classroom.Index'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'Admin.Event.Index'
+      }
+      return 'Admin.Classroom.Index'
+    },
+    showPageRouteName () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'Admin.Classroom.Show'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'Admin.Event.Show'
+      }
+      return 'Admin.Classroom.Show'
+    },
+    canShowTabPage () {
+      return (tabName) => {
+        const tabs = {
+          TRAINING: [
+            'classroomInfo',
+            'sessions',
+            'projects',
+            'exams',
+            'members',
+            'teams',
+            'graders',
+            'leaders',
+            'activity_sheet',
+            'live_streaming_url'
+          ],
+          EVENT: [
+            'classroomInfo',
+            'sessions',
+            'members',
+            'live_streaming_url'
+          ]
+        }
+
+        return tabs[this.localOptions.classroomType].includes(tabName)
+      }
     }
   },
   watch: {
@@ -113,6 +211,9 @@ export default {
       this.updateBreadcrumbs()
     }
   },
+  mounted () {
+    this.mounted = true
+  },
   methods: {
     updateBreadcrumbs () {
       this.$store.commit('AppLayout/updateBreadcrumbs', {
@@ -120,12 +221,12 @@ export default {
         loading: false,
         path: [
           {
-            label: 'دوره های آموزشی',
-            to: { name: 'Admin.Classroom.Index' }
+            label: 'لیست ' + this.classroomTypeTitle,
+            to: { name: this.indexPageRouteName }
           },
           {
             label: this.classroom.title,
-            to: { name: 'Admin.Classroom.Show', params: { id: this.$route.params.id } }
+            to: { name: this.showPageRouteName, params: { id: this.$route.params.id } }
           }
         ]
       })

+ 2 - 10
src/components/Widgets/Classroom/AdminClassroomShow/OptionPanel.vue

@@ -9,6 +9,7 @@
 
 <script>
 import { defineComponent } from 'vue'
+import { UnitCategory } from 'src/models/UnitCategory.js'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
 
@@ -20,16 +21,7 @@ export default defineComponent({
   mixins: [mixinOptionPanel],
   data () {
     return {
-      classroomTypeOptions: [
-        {
-          label: 'دوره آموزشی',
-          value: 'Classroom'
-        },
-        {
-          label: 'حلقه مباحثاتی',
-          value: 'DiscussionCircles'
-        }
-      ]
+      classroomTypeOptions: (new UnitCategory()).typeEnums
     }
   }
 })

+ 44 - 9
src/components/Widgets/Classroom/AdminClassroomShow/components/ClassroomInfo.vue

@@ -3,7 +3,7 @@
                ref="classroomEntityEdit"
                :key="classroomEntityEditKey"
                v-model:value="inputs"
-               title="مشخصات دوره آموزشی"
+               :title="'مشخصات ' + classroomTypeTitle"
                :api="api"
                :show-close-button="false"
                :show-edit-button="false"
@@ -31,6 +31,7 @@ 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 { UnitCategory } from 'src/models/UnitCategory.js'
 import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
 
 const FormBuilderInputEditorComp = shallowRef(FormBuilderInputEditor)
@@ -45,6 +46,10 @@ export default {
       type: Number,
       default: null
     },
+    classroomType: {
+      type: String,
+      default: 'TRAINING'
+    },
     classroom: {
       type: Classroom,
       default: new Classroom()
@@ -159,6 +164,10 @@ export default {
     selectedCategoryId () {
       return FormBuilderAssist.getInputsByName(this.inputs, 'category').value
     },
+    classroomTypeTitle () {
+      const unitCategory = new UnitCategory({ type: this.classroomType })
+      return unitCategory.type_info.label
+    },
     localClassroom: {
       get () {
         return this.classroom
@@ -173,11 +182,14 @@ export default {
       if (!this.mounted) {
         return
       }
-      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'value', null)
+      if (this.classroomType === 'TRAINING') {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'value', null)
+      }
       this.getUnits(this.selectedCategoryId)
     }
   },
   mounted () {
+    this.setClassroomTypeOfInputs()
     this.localClassroom.loading = true
     this.preLoadData()
       .then(() => {
@@ -188,6 +200,23 @@ export default {
       })
   },
   methods: {
+    setClassroomTypeOfInputs () {
+      if (this.classroomType === 'TRAINING') {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'professor', 'label', 'استاد')
+      }
+      if (this.classroomType === 'EVENT') {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'professor', 'label', 'برگزار کننده')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'label', 'دسته')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'sessions_frequency', 'label', 'عنوان')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'allowed_absence_count', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'effective_absence_coefficient', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'mandatory_assignment_count', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'special_passing_mark', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'minimum_conditional_passing_mark', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'minimum_clean_passing_mark', 'type', 'hidden')
+      }
+    },
     afterLoadInputData (data) {
       this.localClassroom = new Classroom(data)
       this.localClassroom.loading = false
@@ -201,9 +230,14 @@ export default {
       return new Promise((resolve, reject) => {
         APIGateway.classroom.get(this.classroomId)
           .then((classroom) => {
-            this.setInputAttr('category', 'value', classroom.unit_info.category_info.id)
-            this.setInputAttr('unit', 'value', classroom.unit)
-            this.beforeLoadInputData(classroom)
+            this.beforeLoadInputData(classroom, () => {
+              this.$nextTick(() => {
+                if (this.classroomType === 'TRAINING') {
+                  this.setInputAttr('category', 'value', classroom.unit_info.category_info.id)
+                }
+                this.setInputAttr('unit', 'value', classroom.unit)
+              })
+            })
               .then((classroom) => {
                 resolve(classroom)
               })
@@ -216,17 +250,18 @@ export default {
           })
       })
     },
-    beforeLoadInputData (responseData) {
+    beforeLoadInputData (responseData, callback) {
       return new Promise((resolve, reject) => {
         const promise1 = this.getProfessors()
         const promise2 = this.getCategories()
-        const promise3 = this.getUnits(responseData.unit_info.category)
+        const promise3 = (this.classroomType === 'EVENT') ? this.getUnits(null, this.classroomType) : 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.classroomEntityEditKey = Date.now()
+              callback()
               resolve()
             })
             .catch(() => {
@@ -259,8 +294,8 @@ export default {
         })
         .catch(() => {})
     },
-    getUnits (selectedcategoryId) {
-      return APIGateway.unit.index({ per_page: 9999, category: selectedcategoryId })
+    getUnits (selectedcategoryId = null, categoryType = null) {
+      return APIGateway.unit.index({ per_page: 9999, category: selectedcategoryId, category__type: categoryType })
         .then((units) => {
           this.setInputAttr('unit', 'options', units.list.list.map(item => {
             return {

+ 270 - 0
src/components/Widgets/Classroom/AdminClassroomShow/components/GraderList.vue

@@ -0,0 +1,270 @@
+<template>
+  <entity-index v-if="mounted"
+                ref="graderList"
+                v-model:value="graderListInputs"
+                title="لیست مصححین"
+                :api="graderListApi"
+                :table="graderListTable"
+                :table-keys="graderListTableKeys"
+                :show-search-button="false"
+                :show-reload-button="false"
+                :show-expand-button="false">
+    <template #toolbar>
+      <form-builder ref="formBuilder"
+                    v-model:value="addGraderInputs"
+                    :loading="addNewGraderLoading" />
+    </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">
+          <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.Classroom.Grader.Members', params: {classroom_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 { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { APIGateway } from 'src/api/APIGateway.js'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+import { FormBuilder, inputMixin } from 'quasar-form-builder'
+import EntityInput from 'quasar-crud/src/components/Entity/Attachment/EntityInput.vue'
+
+const EntityInputComp = shallowRef(EntityInput)
+
+export default {
+  name: 'GraderList',
+  components: {
+    DeleteBtn,
+    EntityIndex,
+    FormBuilder
+  },
+  mixins: [inputMixin],
+  props: {
+    classroomId: {
+      type: Number,
+      default: null
+    }
+  },
+  data () {
+    const classroomId = this.classroomId
+    return {
+      mounted: false,
+      addNewGraderLoading: false,
+
+      addGraderInputs: [
+        {
+          type: EntityInputComp,
+          name: 'graders',
+          selectionMode: 'multiple',
+          popUpButtonConfig: {
+            label: 'اختصاص مصحح جدید',
+            outline: true,
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+          dialogConfirmButtonConfig: {
+            label: 'تایید',
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+
+          apiAddress: APIGateway.user.APIAdresses.base,
+          tableTitle: 'لیست کل مصححین',
+          showTableItemsRouteName: 'Admin.BlockManagement.Show',
+          tableKeys: {
+            data: 'results',
+            total: 'count',
+            currentPage: 'current',
+            perPage: 'per_page',
+            pageKey: 'page'
+          },
+          table: {
+            columns: [
+              {
+                name: 'id',
+                required: true,
+                label: 'شناسه',
+                align: 'left',
+                field: row => row.id
+              },
+              {
+                name: 'fullname',
+                required: true,
+                label: 'نام و نام خانوادگی',
+                align: 'left',
+                field: row => row.firstname + ' ' + row.lastname
+              },
+              {
+                name: 'national_code',
+                required: true,
+                label: 'کد ملی',
+                align: 'left',
+                field: row => row.national_code
+              },
+              {
+                name: 'mobile_number',
+                required: true,
+                label: 'شماره همراه',
+                align: 'left',
+                field: row => row.mobile_number
+              }
+            ]
+          },
+          inputs: [
+            { type: 'input', name: 'id', value: null, label: 'شناسه', placeholder: ' ', col: 'col-md-4 col-12' },
+            { type: 'input', name: 'national_code', value: null, label: 'کد ملی', placeholder: ' ', col: 'col-md-4 col-12' },
+            { type: 'input', name: 'mobile_number', value: null, label: 'شماره همراه', placeholder: ' ', col: 'col-md-4 col-12' },
+            { type: 'input', name: 'email', value: null, label: 'ایمیل', placeholder: ' ', col: 'col-md-3 col-12' },
+            { type: 'input', name: 'firstname', value: null, label: 'نام', placeholder: ' ', col: 'col-md-3 col-12' },
+            { type: 'input', name: 'lastname', value: null, label: 'نام خانوادگی', placeholder: ' ', col: 'col-md-3 col-12' },
+            { type: 'hidden', name: 'role', value: 'team_grader' },
+            { type: 'hidden', name: 'classroom_graders__classroom__ne', value: classroomId }
+          ],
+          itemIndicatorKey: row => row.firstname + ' ' + row.lastname,
+          // itemIndicatorKey: 'firstname',
+          itemIdentifyKey: 'id',
+          onConfirmed: this.onConfirmedSelected,
+
+          value: [],
+          selected: [],
+          col: 'col-md-12 col-12'
+        }
+      ],
+
+      graderListInputs: [
+        { type: 'hidden', name: 'classroom', value: classroomId }
+      ],
+      graderListApi: APIGateway.classroomGrader.APIAdresses.base,
+      graderListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.grader_info.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'جنسیت',
+            align: 'left',
+            field: row => (row.grader_info.gender === 1) ? 'مذکر' : ((row.grader_info.gender === 2) ? 'مونث' : '-')
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.grader_info.firstname + ' ' + row.grader_info.lastname
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'کدملی',
+            align: 'left',
+            field: row => row.grader_info.national_code
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شماره تلفن',
+            align: 'left',
+            field: row => row.grader_info.mobile_number
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'دانشگاه',
+            align: 'left',
+            field: row => row.grader_info.last_academy_name
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شهر',
+            align: 'left',
+            field: row => row.grader_info.living_city
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      graderListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  computed: {
+    tableChosenValues: {
+      get () {
+        if (Array.isArray(this.tableSelectedValues)) {
+          return this.tableSelectedValues
+        }
+        return [this.tableSelectedValues]
+      },
+      set () {}
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    onConfirmedSelected (data) {
+      const graders = data.map(item => item.id)
+      this.addNewGraderLoading = true
+      APIGateway.classroom.appendGraders({ classroomId: this.classroomId, graders })
+        .then(() => {
+          this.$refs.graderList.search()
+          this.addNewGraderLoading = false
+        })
+        .catch(() => {
+          this.addNewGraderLoading = false
+        })
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.grader_info.firstname + ' ' + row.grader_info.lastname + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>

+ 270 - 0
src/components/Widgets/Classroom/AdminClassroomShow/components/LeaderList.vue

@@ -0,0 +1,270 @@
+<template>
+  <entity-index v-if="mounted"
+                ref="leaderList"
+                v-model:value="leaderListInputs"
+                title="لیست سرگروه ها"
+                :api="leaderListApi"
+                :table="leaderListTable"
+                :table-keys="leaderListTableKeys"
+                :show-search-button="false"
+                :show-reload-button="false"
+                :show-expand-button="false">
+    <template #toolbar>
+      <form-builder ref="formBuilder"
+                    v-model:value="addLeaderInputs"
+                    :loading="addNewLeaderLoading" />
+    </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">
+          <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.Classroom.Leader.Members', params: {classroom_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 { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import { APIGateway } from 'src/api/APIGateway.js'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+import { FormBuilder, inputMixin } from 'quasar-form-builder'
+import EntityInput from 'quasar-crud/src/components/Entity/Attachment/EntityInput.vue'
+
+const EntityInputComp = shallowRef(EntityInput)
+
+export default {
+  name: 'LeaderList',
+  components: {
+    DeleteBtn,
+    EntityIndex,
+    FormBuilder
+  },
+  mixins: [inputMixin],
+  props: {
+    classroomId: {
+      type: Number,
+      default: null
+    }
+  },
+  data () {
+    const classroomId = this.classroomId
+    return {
+      mounted: false,
+      addNewLeaderLoading: false,
+
+      addLeaderInputs: [
+        {
+          type: EntityInputComp,
+          name: 'leaders',
+          selectionMode: 'multiple',
+          popUpButtonConfig: {
+            label: 'اختصاص سرگروه جدید',
+            outline: true,
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+          dialogConfirmButtonConfig: {
+            label: 'تایید',
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+
+          apiAddress: APIGateway.user.APIAdresses.base,
+          tableTitle: 'لیست کل سرگروه ها',
+          showTableItemsRouteName: 'Admin.BlockManagement.Show',
+          tableKeys: {
+            data: 'results',
+            total: 'count',
+            currentPage: 'current',
+            perPage: 'per_page',
+            pageKey: 'page'
+          },
+          table: {
+            columns: [
+              {
+                name: 'id',
+                required: true,
+                label: 'شناسه',
+                align: 'left',
+                field: row => row.id
+              },
+              {
+                name: 'fullname',
+                required: true,
+                label: 'نام و نام خانوادگی',
+                align: 'left',
+                field: row => row.firstname + ' ' + row.lastname
+              },
+              {
+                name: 'national_code',
+                required: true,
+                label: 'کد ملی',
+                align: 'left',
+                field: row => row.national_code
+              },
+              {
+                name: 'mobile_number',
+                required: true,
+                label: 'شماره همراه',
+                align: 'left',
+                field: row => row.mobile_number
+              }
+            ]
+          },
+          inputs: [
+            { type: 'input', name: 'id', value: null, label: 'شناسه', placeholder: ' ', col: 'col-md-4 col-12' },
+            { type: 'input', name: 'national_code', value: null, label: 'کد ملی', placeholder: ' ', col: 'col-md-4 col-12' },
+            { type: 'input', name: 'mobile_number', value: null, label: 'شماره همراه', placeholder: ' ', col: 'col-md-4 col-12' },
+            { type: 'input', name: 'email', value: null, label: 'ایمیل', placeholder: ' ', col: 'col-md-3 col-12' },
+            { type: 'input', name: 'firstname', value: null, label: 'نام', placeholder: ' ', col: 'col-md-3 col-12' },
+            { type: 'input', name: 'lastname', value: null, label: 'نام خانوادگی', placeholder: ' ', col: 'col-md-3 col-12' },
+            { type: 'hidden', name: 'role', value: 'team_leader' },
+            { type: 'hidden', name: 'classroom_leaders__classroom__ne', value: classroomId }
+          ],
+          itemIndicatorKey: row => row.firstname + ' ' + row.lastname,
+          // itemIndicatorKey: 'firstname',
+          itemIdentifyKey: 'id',
+          onConfirmed: this.onConfirmedSelected,
+
+          value: [],
+          selected: [],
+          col: 'col-md-12 col-12'
+        }
+      ],
+
+      leaderListInputs: [
+        { type: 'hidden', name: 'classroom', value: classroomId }
+      ],
+      leaderListApi: APIGateway.classroomLeaders.APIAdresses.base,
+      leaderListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.leader_info.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'جنسیت',
+            align: 'left',
+            field: row => (row.leader_info.gender === 1) ? 'مذکر' : ((row.leader_info.gender === 2) ? 'مونث' : '-')
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.leader_info.firstname + ' ' + row.leader_info.lastname
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'کدملی',
+            align: 'left',
+            field: row => row.leader_info.national_code
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شماره تلفن',
+            align: 'left',
+            field: row => row.leader_info.mobile_number
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'دانشگاه',
+            align: 'left',
+            field: row => row.leader_info.last_academy_name
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شهر',
+            align: 'left',
+            field: row => row.leader_info.living_city
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      leaderListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  computed: {
+    tableChosenValues: {
+      get () {
+        if (Array.isArray(this.tableSelectedValues)) {
+          return this.tableSelectedValues
+        }
+        return [this.tableSelectedValues]
+      },
+      set () {}
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    onConfirmedSelected (data) {
+      const leaders = data.map(item => item.id)
+      this.addNewLeaderLoading = true
+      APIGateway.classroom.appendLeaders({ classroomId: this.classroomId, leaders })
+        .then(() => {
+          this.$refs.leaderList.search()
+          this.addNewLeaderLoading = false
+        })
+        .catch(() => {
+          this.addNewLeaderLoading = false
+        })
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.leader_info.firstname + ' ' + row.leader_info.lastname + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>

+ 44 - 5
src/components/Widgets/Classroom/AdminClassroomShow/components/SessionList.vue

@@ -2,7 +2,7 @@
   <entity-index v-if="mounted"
                 ref="sessionList"
                 v-model:value="sessionListInputs"
-                title="لیست جلسات"
+                :title="listTitle"
                 :api="sessionListApi"
                 :table="sessionListTable"
                 :table-keys="sessionListTableKeys"
@@ -14,7 +14,7 @@
              outline
              :loading="addNewSessionLoading"
              @click="addNewSession">
-        افزودن جلسه
+        {{ addBtnTitle }}
       </q-btn>
     </template>
     <template v-slot:entity-index-table-cell="{inputData, showConfirmRemoveDialog}">
@@ -27,7 +27,7 @@
                  outline
                  color="primary"
                  label="بررسی"
-                 :to="{name: 'Admin.Classroom.Session.AttendanceSheetList', params: {classroom_id: classroomId, session_id: inputData.props.row.id}}"
+                 :to="{name: sessionAttendanceSheetList, params: {classroom_id: classroomId, session_id: inputData.props.row.id}}"
                  class="q-mr-md" />
           <q-btn size="md"
                  color="primary"
@@ -63,12 +63,18 @@ export default {
     classroomId: {
       type: Number,
       default: null
+    },
+    classroomType: {
+      type: String,
+      default: 'TRAINING'
     }
   },
   data () {
     const classroomId = this.classroomId
     return {
       mounted: false,
+      listTitle: 'لیست جلسات',
+      addBtnTitle: 'افزودن جلسه',
       addNewSessionLoading: false,
 
       sessionListInputs: [
@@ -99,14 +105,14 @@ export default {
             field: row => row.title
           },
           {
-            name: 'creation_time',
+            name: 'beginning_time',
             required: true,
             label: 'زمان شروع جلسه',
             align: 'left',
             field: row => row.beginning_time ? ShamsiDate.getDateTime(row.beginning_time) : '-'
           },
           {
-            name: 'last_modification_time',
+            name: 'ending_time',
             required: true,
             label: 'زمان پایان جلسه',
             align: 'left',
@@ -130,10 +136,43 @@ export default {
       }
     }
   },
+  computed: {
+    sessionAttendanceSheetList () {
+      if (this.classroomType === 'TRAINING') {
+        return 'Admin.Classroom.Session.AttendanceSheetList'
+      }
+      if (this.classroomType === 'EVENT') {
+        return 'Admin.Event.Session.AttendanceSheetList'
+      }
+      return 'Admin.Classroom.Session.AttendanceSheetList'
+    }
+  },
   mounted () {
+    this.setClassroomTypeOfInputs()
     this.mounted = true
   },
   methods: {
+    setClassroomTypeOfInputs () {
+      if (this.classroomType === 'TRAINING') {
+        this.listTitle = 'لیست جلسات'
+        this.addBtnTitle = 'افزودن جلسه'
+      }
+      if (this.classroomType === 'EVENT') {
+        this.listTitle = 'لیست برنامه ها'
+        this.addBtnTitle = 'افزودن برنامه'
+        this.sessionListTable.columns.forEach(col => {
+          if (col.name === 'title') {
+            col.label = 'عنوان برنامه'
+          }
+          if (col.name === 'beginning_time') {
+            col.label = 'زمان شروع برنامه'
+          }
+          if (col.name === 'ending_time') {
+            col.label = 'زمان پایان برنامه'
+          }
+        })
+      }
+    },
     addNewSession () {
       this.addNewSessionLoading = true
       APIGateway.session.create({

+ 149 - 0
src/components/Widgets/Classroom/AdminClassroomShow/components/TeamList.vue

@@ -0,0 +1,149 @@
+<template>
+  <entity-index v-if="mounted"
+                ref="teamsList"
+                v-model:value="teamsListInputs"
+                title="لیست گروه ها"
+                :api="teamsListApi"
+                :table="teamsListTable"
+                :table-keys="teamsListTableKeys"
+                :show-search-button="false"
+                :show-reload-button="false"
+                :show-expand-button="false">
+    <template #toolbar>
+      <q-btn color="primary"
+             outline
+             :to="{name: 'Admin.Classroom.Team.Create', params: {classroom_id: classroomId}}">
+        ایجاد گروه جدید
+      </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 === 'action'">
+        <div class="action-column-entity-index">
+          <q-btn size="md"
+                 color="primary"
+                 label="جزییات"
+                 :to="{name: 'Admin.Classroom.Team.Show', params: {classroom_id: classroomId, team_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>
+  <q-skeleton v-else
+              type="rect"
+              height="200px" />
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+
+export default {
+  name: 'TeamList',
+  components: {
+    DeleteBtn,
+    EntityIndex
+  },
+  props: {
+    classroomId: {
+      type: Number,
+      default: null
+    }
+  },
+  data () {
+    const classroomId = this.classroomId
+    return {
+      mounted: false,
+
+      teamsListInputs: [
+        { type: 'hidden', name: 'classroom', value: classroomId }
+      ],
+      teamsListApi: APIGateway.team.APIAdresses.base,
+      teamsListTable: {
+        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: 'leader_info',
+            required: true,
+            label: 'سرگروه',
+            align: 'left',
+            field: row => row.leader_info?.firstname + ' ' + row.leader_info?.lastname
+          },
+          {
+            name: 'used_capacity',
+            required: true,
+            label: 'ظرفیت استفاده شده',
+            align: 'left',
+            field: row => row.used_capacity
+          },
+          {
+            name: 'capacity',
+            required: true,
+            label: 'ظرفیت',
+            align: 'left',
+            field: row => row.capacity
+          },
+          {
+            name: 'action',
+            required: true,
+            label: ' ',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      teamsListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted () {
+    this.setMembersListActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setMembersListActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.teamsListInputs, 'btn', 'atClick', this.searchMembersList)
+    },
+    searchMembersList () {
+      this.$refs.teamsList.search()
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.title + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>

+ 33 - 10
src/components/Widgets/Classroom/AllClassrooms/AllClassrooms.vue

@@ -3,7 +3,7 @@
     <div class="all-classroom-title">
       <div>
         <q-banner class="banner">
-          دوره های آموزشی
+          {{ classroomTypeTitle }}
         </q-banner>
       </div>
       <div class="all-classroom-filter">
@@ -68,7 +68,10 @@
       <div v-for="classroom in classrooms.list"
            :key="classroom.id"
            class="classroom-col col-md-4">
-        <classroom-item :classroom="classroom" />
+        <classroom-item v-if="localOptions.classroomType === 'TRAINING'"
+                        :classroom="classroom" />
+        <event-item v-else-if="localOptions.classroomType === 'EVENT'"
+                    :event="classroom" />
       </div>
     </div>
     <div v-else-if="!classrooms.loading && classrooms.list.length === 0">
@@ -84,6 +87,7 @@
                :units="units"
                :professors="professors"
                :categories="categories"
+               :classroom-type="localOptions.classroomType"
                @doFilter="doFilter"
                @cancelFilter="cancelFilter"
                @onChangeCategory="onChangeCategory" />
@@ -94,17 +98,18 @@
 <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 { Classroom, ClassroomList } from 'src/models/Classroom.js'
+import { mixinAuth, mixinWidget } from 'src/mixin/Mixins.js'
 import ClassroomItem from 'src/components/ClassroomItem.vue'
 import { UnitCategoryList } from 'src/models/UnitCategory.js'
+import EventItem from 'src/components/EventItem.vue'
+import { Classroom, ClassroomList } from 'src/models/Classroom.js'
 import { ClassroomRegistrationList } from 'src/models/ClassroomRegistration.js'
 
 export default {
   name: 'AllClassrooms',
-  components: { ClassroomItem, Filters },
-  mixins: [mixinAuth],
+  components: { ClassroomItem, EventItem, Filters },
+  mixins: [mixinAuth, mixinWidget],
   data: () => ({
     loading: false,
     units: new UnitList(),
@@ -121,9 +126,21 @@ export default {
       professor: null
     },
     filterDialog: false,
-    classrooms: new ClassroomList()
+    classrooms: new ClassroomList(),
+    defaultOptions: {
+      classroomType: 'TRAINING'
+    }
   }),
   computed: {
+    classroomTypeTitle () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'دوره های آموزشی'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'رویدادها'
+      }
+      return 'دوره های آموزشی'
+    },
     classroomStatuses () {
       return (new Classroom()).statusEnums
     },
@@ -206,6 +223,7 @@ export default {
     getClassrooms () {
       this.classrooms.loading = true
       this.filter.per_page = 9999
+      this.filter.unit__category__type = this.localOptions.classroomType
       APIGateway.classroom.index(this.filter)
         .then(classroomList => {
           this.classrooms = new ClassroomList(classroomList.list)
@@ -217,7 +235,9 @@ export default {
     },
     getCategories () {
       this.categories.loading = true
-      APIGateway.unitCategory.index()
+      APIGateway.unitCategory.index({
+        type: this.localOptions.classroomType
+      })
         .then(unitCategoryList => {
           this.categories.loading = false
           this.categories = new UnitCategoryList(unitCategoryList.list)
@@ -240,9 +260,12 @@ export default {
     getUserFullname (user) {
       return user.firstname + ' ' + user.lastname
     },
-    getUnits (categoryId) {
+    getUnits (categoryId = null) {
       this.units.loading = true
-      APIGateway.unit.index({ category: categoryId })
+      APIGateway.unit.index({
+        category: categoryId,
+        category__type: this.localOptions.classroomType
+      })
         .then(unitList => {
           this.units.loading = false
           this.units = new UnitList(unitList.list)

+ 8 - 144
src/components/Widgets/Classroom/AllClassrooms/OptionPanel.vue

@@ -1,163 +1,27 @@
 <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>
+      <q-option-group v-model="localOptions.classroomType"
+                      :options="classroomTypeOptions" />
     </template>
   </option-panel-tabs>
 </template>
 
 <script>
 import { defineComponent } from 'vue'
+import { UnitCategory } from 'src/models/UnitCategory.js'
 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 },
+  components: {
+    OptionPanelTabs
+  },
   mixins: [mixinOptionPanel],
-  data() {
+  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
-        }
-      }
+      classroomTypeOptions: (new UnitCategory()).typeEnums
     }
   }
 })

+ 16 - 2
src/components/Widgets/Classroom/AllClassrooms/components/Filters.vue

@@ -27,7 +27,8 @@
     </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 v-if="classroomType === 'TRAINING'"
+             class="col-md-3">
           <div>گروه آموزشی</div>
           <select-control v-model:value="localFilters.unit__category"
                           :options="categories.list"
@@ -38,7 +39,7 @@
                           @update:model-value="onChangeCategory" />
         </div>
         <div class="col-md-3">
-          <div>درس</div>
+          <div>{{ unitTitle }}</div>
           <select-control v-model:value="localFilters.unit"
                           :options="units.list"
                           :disable="units.loading"
@@ -119,6 +120,10 @@ export default {
     units: {
       type: UnitList,
       default: new UnitList()
+    },
+    classroomType: {
+      type: String,
+      default: 'TRAINING'
     }
   },
   emits: ['doFilter', 'cancelFilter', 'onChangeCategory', 'update:filters'],
@@ -128,6 +133,15 @@ export default {
     }
   },
   computed: {
+    unitTitle () {
+      if (this.classroomType === 'TRAINING') {
+        return 'درس'
+      }
+      if (this.classroomType === 'EVENT') {
+        return 'برنامه'
+      }
+      return 'درس'
+    },
     localFilters: {
       get () {
         return this.filters

+ 361 - 0
src/components/Widgets/Classroom/ClassroomList/ClassroomList.vue

@@ -0,0 +1,361 @@
+<template>
+  <breadcrumbs style="margin-top: 29px; margin-bottom: 19px;" />
+  <entity-index v-if="mounted"
+                ref="entityIndex"
+                v-model:value="inputs"
+                :title="listTitle"
+                :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: showPageRouteName, params: {id: inputData.props.row.classroom_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: showPageRouteName, params: {id: row.classroom_info.id}}" />
+          </template>
+        </template>
+      </entity-index-grid-item>
+    </template>
+  </entity-index>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityIndex } from 'quasar-crud'
+import Enums from 'src/assets/Enums/Enums.js'
+import { Invoice } from 'src/models/Invoice.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { Classroom } from 'src/models/Classroom.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+import { Registration } from 'src/models/Registration.js'
+import { mixinAuth, mixinWidget } from 'src/mixin/Mixins.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.AllClassrooms',
+  components: {
+    EntityIndex,
+    Breadcrumbs,
+    EntityIndexGridItem
+  },
+  mixins: [mixinAuth, mixinWidget],
+  data () {
+    return {
+      mounted: false,
+      inputs: [
+        {
+          type: 'select',
+          name: 'classroom__unit__category__type',
+          options: [
+            {
+              label: 'دوره های آموزشی',
+              value: 'TRAINING'
+            },
+            {
+              label: 'حلقه های مباحثاتی',
+              value: 'DISCUSSION_CIRCLE'
+            }
+          ],
+          value: 'TRAINING',
+          label: 'نوع',
+          placeholder: ' ',
+          col: 'col-md-2 col-12'
+        },
+        {
+          type: 'select',
+          name: 'status',
+          options: Enums.classroomStatuses,
+          value: null,
+          label: 'وضعیت دوره',
+          placeholder: ' ',
+          col: 'col-md-2 col-12'
+        },
+        {
+          type: 'select',
+          name: 'category',
+          options: [],
+          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.classroom_info.id
+          },
+          {
+            name: 'classroom_info.title',
+            required: true,
+            label: 'عنوان',
+            align: 'left',
+            field: row => row.classroom_info?.title
+          },
+          {
+            name: 'audience_gender_type',
+            required: true,
+            label: 'جنسیت',
+            align: 'left',
+            field: row => (new Classroom(row.classroom_info)).audience_gender_type_info.label
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'نوع برگزاری',
+            align: 'left',
+            field: row => this.getClassroomHoldingTypeTitle(row.classroom_info.holding_type)
+          },
+          {
+            name: 'professor_info',
+            required: true,
+            label: 'استاد',
+            align: 'left',
+            field: row => row.classroom_info?.professor_info?.firstname + ' ' + row.classroom_info?.professor_info?.lastname
+          },
+          {
+            name: 'sessions_info',
+            required: true,
+            label: 'تعداد جلسات',
+            align: 'left',
+            field: row => row.classroom_info?.sessions_info?.length
+          },
+          {
+            name: 'status',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => (new Registration(row)).status_info.label
+            // field: row => this.getClassroomStatusTitle(row.classroom_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: ''
+          }
+        ]
+      },
+      defaultOptions: {
+        classroomType: 'TRAINING'
+      },
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  computed: {
+    listTitle () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'لیست دوره های آموزشی'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'لیست رویدادها'
+      }
+      return 'لیست دوره های آموزشی'
+    },
+    showPageRouteName () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'UserPanel.Profile.ClassroomInfo'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'UserPanel.Profile.EventInfo'
+      }
+      return 'UserPanel.Profile.ClassroomInfo'
+    },
+    selectedCategoryId () {
+      return this.getInputValue('category')
+    }
+  },
+  watch: {
+    selectedCategoryId () {
+      this.setInputValue('unit', null)
+      this.getUnits(this.selectedCategoryId)
+    }
+  },
+  mounted () {
+    this.setClassroomTypeOfInputs()
+    this.setOwner()
+    this.setActionBtn()
+    this.loadInputDataOptions()
+    this.mounted = true
+  },
+  methods: {
+    setClassroomTypeOfInputs () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'classroom__unit__category__type', 'value', this.localOptions.classroomType)
+
+      // if (this.localOptions.classroomType === 'TRAINING') {
+      // }
+      if (this.localOptions.classroomType === 'EVENT') {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'classroom__unit__category__type', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'type', 'hidden')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'label', 'دسته')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'status', 'label', 'وضعیت رویداد')
+
+        this.table.columns.forEach(col => {
+          if (col.name === 'professor_info') {
+            col.label = 'برگزار کننده'
+          }
+          if (col.name === 'sessions_info') {
+            col.label = 'تعداد برنامه ها'
+          }
+        })
+      }
+    },
+    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()
+    },
+    getClassroomHoldingTypeTitle (type) {
+      const target = Enums.classroomHoldingTypes.find(item => item.value === type)
+      if (!target) {
+        return '-'
+      }
+
+      return target.label
+    },
+    getClassroomStatusTitle (type) {
+      const target = Enums.classroomStatuses.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.getCategories()
+      this.getUnits()
+    },
+    getCategories () {
+      APIGateway.unitCategory.index({ per_page: 9999, type: this.localOptions.classroomType })
+        .then((categories) => {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'options', categories.list.list.map(item => {
+            return {
+              value: item.id,
+              label: item.title
+            }
+          }))
+        })
+        .catch(() => {})
+    },
+    getUnits (selectedcategoryId) {
+      APIGateway.unit.index({
+        category: selectedcategoryId,
+        category__type: this.localOptions.classroomType
+      })
+        .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>

+ 28 - 0
src/components/Widgets/Classroom/ClassroomList/OptionPanel.vue

@@ -0,0 +1,28 @@
+<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 { UnitCategory } from 'src/models/UnitCategory.js'
+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: (new UnitCategory()).typeEnums
+    }
+  }
+})
+</script>

+ 627 - 0
src/components/Widgets/Classroom/ClassroomShow/ClassroomShow.vue

@@ -0,0 +1,627 @@
+<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 v-if="canShowTabPageForClassroomType('classroomInfo')"
+             name="classroomInfo"
+             label="اطلاعات دوره آموزشی" />
+      <q-tab v-if="canShowOtherTabsOfClassroom && canShowTabPageForClassroomType('educations')"
+             name="educations"
+             :label="educationsTabTitle" />
+      <q-tab v-if="canShowOtherTabsOfClassroom && canShowTabPageForClassroomType('team')"
+             name="team"
+             label="گروه" />
+      <q-tab v-if="canShowOtherTabsOfClassroom && canShowTabPageForClassroomType('exams')"
+             name="exams"
+             label="آزمون" />
+      <q-tab v-if="canShowOtherTabsOfClassroom && canShowTabPageForClassroomType('projects')"
+             name="projects"
+             label="پروژه" />
+      <q-tab v-if="canShowOtherTabsOfClassroom && classroom.live_streaming_url && canShowTabPageForClassroomType('live_streaming_url')"
+             name="live_streaming_url"
+             label="بخش آنلاین" />
+    </q-tabs>
+
+    <q-separator />
+
+    <q-tab-panels v-model="tab"
+                  animated>
+      <q-tab-panel name="classroomInfo">
+        <show-classroom-info v-if="mounted"
+                             :options="{ profileMode: true }"
+                             :classroom-type="localOptions.classroomType"
+                             @onloadn="onloadnClassroom" />
+      </q-tab-panel>
+
+      <q-tab-panel name="educations"
+                   class="q-pa-none">
+        <entity-index v-if="mounted"
+                      ref="entityIndex"
+                      v-model:value="sessionsInputs"
+                      :title="educationsListTitle"
+                      :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="team"
+                   class="q-pa-none">
+        <entity-index v-if="mounted"
+                      ref="teamsList"
+                      v-model:value="teamsListInputs"
+                      title="لیست گروه ها"
+                      :api="teamsListApi"
+                      :table="teamsListTable"
+                      :table-keys="teamsListTableKeys"
+                      :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 === 'action'">
+              <div class="action-column-entity-index">
+                <q-btn size="md"
+                       color="primary"
+                       :flat="inputData.props.row.join_status !== 'ENABLED'"
+                       :disable="inputData.props.row.join_status !== 'ENABLED'"
+                       :label="getTeamJoinStatusLabel(inputData.props.row)"
+                       class="q-mr-md"
+                       :loading="joinTeamLoading"
+                       @click="joinTeam(inputData.props.row)" />
+              </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 name="exams">
+        آزمون
+      </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.Classroom.Project.Show', params: {classroom_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="classroom.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="classroom.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 { Team } from 'src/models/Team.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { Classroom } from 'src/models/Classroom.js'
+import { mixinAuth, mixinWidget } from 'src/mixin/Mixins.js'
+import EntityIndexGridItem from 'src/components/EntityIndexGridItem.vue'
+import { Registration, RegistrationList } from 'src/models/Registration.js'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+import { ProjectAttendanceSheets } from 'src/models/ProjectAttendanceSheets.js'
+import { SessionAttendanceSheets } from 'src/models/SessionAttendanceSheets.js'
+import ShowClassroomInfo from 'src/components/Widgets/Other/ShowClassroomInfo/ShowClassroomInfo.vue'
+
+export default {
+  name: 'UserPanel.Profile.ClassroomInfo',
+  components: {
+    EntityIndex,
+    Breadcrumbs,
+    ShowClassroomInfo,
+    EntityIndexGridItem
+  },
+  mixins: [mixinAuth, mixinWidget],
+  data () {
+    const classroomId = this.$route.params.id
+    // const userId = this.$store.getters['Auth/user'].id
+    return {
+      mounted: false,
+      tab: 'classroomInfo',
+      inputs: [],
+      sessionsInputs: [
+        { type: 'hidden', name: 'classroom', value: classroomId }
+      ],
+      defaultOptions: {
+        classroomType: 'TRAINING'
+      },
+      api: APIGateway.classroom.get(classroomId),
+      sessionsApi: APIGateway.session.APIAdresses.attendanceSheets,
+      classroom: new Classroom(),
+      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: 'beginning_time',
+            required: true,
+            label: 'زمان شروع',
+            align: 'left',
+            field: row => row.beginning_time ? ShamsiDate.getDateTime(row.beginning_time) : '-'
+          },
+          {
+            name: 'ending_time',
+            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: 'beginning_time',
+            required: true,
+            label: 'زمان شروع',
+            align: 'left',
+            field: row => row.beginning_time ? ShamsiDate.getDateTime(row.beginning_time) : '-'
+          },
+          {
+            name: 'ending_time',
+            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: 'classroom', value: classroomId }
+        // { 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'
+      },
+
+      registrations: new RegistrationList(),
+      classroomRegistration: new Registration(),
+
+      joinTeamLoading: false,
+      teamsListInputs: [
+        { type: 'hidden', name: 'classroom', value: classroomId }
+      ],
+      teamsListApi: APIGateway.team.APIAdresses.classroomTeamsWithJoinStatus,
+      teamsListTable: {
+        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: 'leader_info',
+            required: true,
+            label: 'سرگروه',
+            align: 'left',
+            field: row => row.leader_info?.firstname + ' ' + row.leader_info?.lastname
+          },
+          {
+            name: 'used_capacity',
+            required: true,
+            label: 'ظرفیت استفاده شده',
+            align: 'left',
+            field: row => row.used_capacity
+          },
+          {
+            name: 'capacity',
+            required: true,
+            label: 'ظرفیت',
+            align: 'left',
+            field: row => row.capacity
+          },
+          {
+            name: 'action',
+            required: true,
+            label: ' ',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      teamsListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  computed: {
+    listTitle () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'لیست دوره های آموزشی'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'لیست رویدادها'
+      }
+      return 'لیست دوره های آموزشی'
+    },
+    educationsListTitle () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'لیست جلسات'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'لیست برنامه ها'
+      }
+      return 'لیست جلسات'
+    },
+    educationsTabTitle () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'جدول آموزشی'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'لیست برنامه ها'
+      }
+      return 'جدول آموزشی'
+    },
+    canShowTabPageForClassroomType () {
+      return (tabName) => {
+        const tabs = {
+          TRAINING: [
+            'classroomInfo',
+            'educations',
+            'team',
+            'exams',
+            'projects',
+            'live_streaming_url'
+          ],
+          EVENT: [
+            'classroomInfo',
+            'educations',
+            'live_streaming_url'
+          ]
+        }
+
+        return tabs[this.localOptions.classroomType].includes(tabName)
+      }
+    },
+    classroomId () {
+      return this.$route.params.id
+    },
+    canShowOtherTabsOfClassroom () {
+      return !this.classroomRegistration.loading && (this.classroomRegistration.status === 'REGISTERED' || this.classroomRegistration.status === 'ENROLLED')
+    }
+  },
+  mounted () {
+    this.getRegistrationInfo()
+    this.mounted = true
+  },
+  methods: {
+    getTeamJoinStatusLabel (team) {
+      if (team.join_status === 'ENABLED') {
+        return 'عضو شوید'
+      }
+      return (new Team(team)).join_status_info.label
+    },
+    joinTeam (team) {
+      this.joinTeamLoading = true
+      APIGateway.team.register(team.id)
+        .then(() => {
+          this.joinTeamLoading = false
+          this.$refs.teamsList.search()
+        })
+        .catch(() => {
+          this.joinTeamLoading = false
+          this.$refs.teamsList.search()
+        })
+    },
+    getRegistrationInfo () {
+      this.registrations.loading = true
+      this.classroomRegistration.loading = true
+      APIGateway.registration.index({ classroom: this.$route.params.id })
+        .then(({ list }) => {
+          this.registrations = new RegistrationList(list)
+          if (this.registrations.list.length > 0) {
+            this.classroomRegistration = this.registrations.list[0]
+          }
+          this.registrations.loading = false
+          this.classroomRegistration.loading = false
+        })
+        .catch(() => {
+          this.registrations.loading = false
+          this.classroomRegistration.loading = false
+        })
+    },
+    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.classroom.live_streaming_url, '_blank')
+    },
+    onloadnClassroom (classroom) {
+      this.classroom = new Classroom(classroom)
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'دوره های من',
+            to: { name: 'UserPanel.Profile.AllClassrooms' }
+          },
+          {
+            label: this.classroom.title,
+            to: { name: 'UserPanel.Profile.ClassroomInfo', params: { id: this.classroom.id } }
+          }
+        ]
+      })
+    }
+  }
+}
+</script>

+ 28 - 0
src/components/Widgets/Classroom/ClassroomShow/OptionPanel.vue

@@ -0,0 +1,28 @@
+<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 { UnitCategory } from 'src/models/UnitCategory.js'
+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: (new UnitCategory()).typeEnums
+    }
+  }
+})
+</script>

+ 1 - 1
src/components/Widgets/Content/AdminContentShow/AdminContentShow.vue

@@ -33,7 +33,7 @@ import { EntityEdit } 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 ContentMedias from 'components/FormBuilderCustumComponents/ContentMedias/ContentMedias.vue'
+import ContentMedias from 'src/components/FormBuilderCustumComponents/ContentMedias/ContentMedias.vue'
 import ContentCategorySelector from 'src/components/FormBuilderCustumComponents/ContentCategorySelector.vue'
 
 const BtnControlComp = shallowRef(BtnControl)

+ 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>

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

@@ -0,0 +1,105 @@
+<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="members"
+             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="members"
+                   class="q-pa-none">
+        <members-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 EventInfo from './components/EventInfo.vue'
+import MembersList from './components/MembersList.vue'
+import LiveStreaming from './components/LiveStreaming.vue'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+
+export default {
+  name: 'Admin.Event.Show',
+  components: {
+    EventInfo,
+    MembersList,
+    Breadcrumbs,
+    LiveStreaming
+  },
+  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>

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

@@ -0,0 +1,205 @@
+<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 ContentMedias from 'src/components/FormBuilderCustumComponents/ContentMedias/ContentMedias.vue'
+import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
+
+const ContentMediasComp = shallowRef(ContentMedias)
+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: 'category', options: (new Event()).categoryEnums, 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: '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: ContentMediasComp, name: 'medias', responseKey: 'medias_info', sourceType: 'EVENT', col: 'col-md-12 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: '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: {
+    localClassroom: {
+      get () {
+        return this.event
+      },
+      set (newValue) {
+        this.$emit('update:event', newValue)
+      }
+    }
+  },
+  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.beforeLoadInputData(event)
+              .then((event) => {
+                resolve(event)
+              })
+              .catch(() => {
+                reject()
+              })
+          })
+          .catch(() => {
+            reject()
+          })
+      })
+    },
+    beforeLoadInputData (/* responseData */) {
+      return new Promise((resolve, reject) => {
+        const promise1 = this.getProfessors()
+        this.$nextTick(() => {
+          Promise.all([promise1])
+            .then(() => {
+              // this.setInputAttr('category', 'value', responseData.unit_info.category)
+              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(() => {})
+    },
+    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.eventRegistration.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>

+ 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>

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

@@ -0,0 +1,257 @@
+<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()
+      return APIGateway.classroom.index({
+        unit__category__type: 'EVENT'
+      })
+    }
+  }
+}
+</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>

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

@@ -0,0 +1,28 @@
+<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 { UnitCategory } from 'src/models/UnitCategory.js'
+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: (new UnitCategory()).typeEnums
+    }
+  }
+})
+</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/updateOnRegisterClassroom', this.event)
+      this.$router.push({ name: 'UserPanel.ShopCompleteInfo', query: { type: this.acceptClassType, source: 'event' } })
+    },
+    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.classroom.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.classroom.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>

+ 39 - 16
src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue

@@ -15,7 +15,7 @@
       </div>
     </div>
     <q-card v-if="invoice.item_info"
-            class="q-mt-lg">
+            class="invoice-info q-mt-lg">
       <q-linear-progress v-if="invoice.loading"
                          indeterminate />
       <q-card-section>
@@ -31,10 +31,9 @@
       </q-card-section>
       <q-separator />
       <q-card-section>
-        <div class="row">
+        <div class="main-items row">
           <div class="col-md-3 col-12">
             <q-banner>
-              <q-icon name="isax:stickynote" />
               {{ getDateTime(invoice.creation_time) }}
             </q-banner>
           </div>
@@ -63,17 +62,25 @@
         <div class="row q-col-gutter-lg">
           <div class="col-md-4 col-12 flex items-center justify-start">
             <q-img :src="invoice.item_info.thumbnail"
+                   class="q-mr-md q-mb-md"
                    width="100px" />
             {{ invoice.item_info.title }}
           </div>
-          <div class="col-md-4 col-12 flex items-center justify-start">
-            <q-icon name="isax:user" />
+          <div v-if="invoice.type === 'CLASSROOM'"
+               class="col-md-4 col-12 flex items-center justify-start">
+            <q-icon name="co_present"
+                    size="20px"
+                    color="grey-6"
+                    class="q-mr-md" />
             استاد:
             {{ invoice.item_info.professor_info.firstname }}
             {{ invoice.item_info.professor_info.lastname }}
           </div>
           <div class="col-md-4 col-12 flex items-center justify-start">
-            <q-icon name="isax:card" />
+            <q-icon name="payment"
+                    size="20px"
+                    color="grey-6"
+                    class="q-mr-md" />
             قیمت:
             {{ invoice.item_info?.price ? invoice.item_info.price.toLocaleString('fa') : 0 }}
             ریال
@@ -133,6 +140,8 @@ export default {
       wallet: new Wallet(),
       invoice: new Invoice(),
       defaultOptions: {
+        invoice: null,
+        invoiceId: null,
         canAction: true,
         showBackBtn: true,
         showNeedInstallmentBtn: true
@@ -227,16 +236,20 @@ export default {
       return this.localOptions.invoiceId || this.$route.params.id
     },
     getInvoice () {
-      this.invoice.loading = true
-      const invoiceId = this.getInvoiceId()
-      APIGateway.invoice.get({ data: { id: invoiceId } })
-        .then((invoice) => {
-          this.invoice = new Invoice(invoice)
-          this.invoice.loading = false
-        })
-        .catch(() => {
-          this.invoice.loading = false
-        })
+      if (this.localOptions.invoice) {
+        this.invoice = new Invoice(this.localOptions.invoice)
+      } else {
+        this.invoice.loading = true
+        const invoiceId = this.getInvoiceId()
+        APIGateway.invoice.get({ data: { id: invoiceId } })
+          .then((invoice) => {
+            this.invoice = new Invoice(invoice)
+            this.invoice.loading = false
+          })
+          .catch(() => {
+            this.invoice.loading = false
+          })
+      }
     }
   }
 }
@@ -266,6 +279,16 @@ export default {
       top: 0;
     }
   }
+  .invoice-info {
+    .main-items {
+      :deep(.q-banner) {
+        padding: 8px 4px;
+        .q-banner__content {
+          font-size: 14px;
+        }
+      }
+    }
+  }
   .btn-need-installment {
     position: absolute;
     right: 18px;

+ 38 - 5
src/components/Widgets/Invoice/InvoiceThankyou/InvoiceThankyou.vue

@@ -19,23 +19,32 @@
         </q-card>
       </div>
     </div>
-    <div v-if="$route.query.invoice"
+    <div v-if="$route.query.invoice && !invoice.loading"
          class="row justify-center items-center">
       <div class="col-md-6 q-mt-lg">
-        <invoice-show :options="{invoiceId: $route.query.invoice}" />
+        <invoice-show :options="{invoiceId: $route.query.invoice, invoice: invoice}" />
       </div>
     </div>
-    <div class="flex justify-center">
-      <q-btn color="primary"
+    <div v-if="!invoice.loading"
+         class="flex justify-center q-mt-md">
+      <q-btn v-if="invoice.type === 'CLASSROOM'"
+             color="primary"
              :to="{name: 'UserPanel.Profile.AllClassrooms'}">
         مشاهده دوره های من
       </q-btn>
+      <q-btn v-if="invoice.type === 'EVENT'"
+             color="primary"
+             :to="{name: 'UserPanel.Profile.AllEvents'}">
+        مشاهده رویدادهای من
+      </q-btn>
     </div>
   </div>
 </template>
 
 <script>
+import { Invoice } from 'src/models/Invoice.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
 import InvoiceShow from 'src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue'
 
 export default {
@@ -43,6 +52,30 @@ export default {
   components: {
     InvoiceShow
   },
-  mixins: [mixinWidget]
+  mixins: [mixinWidget],
+  data () {
+    return {
+      invoice: new Invoice()
+    }
+  },
+  created() {
+    this.invoice.loading = true
+  },
+  mounted() {
+    this.getInvoice()
+  },
+  methods: {
+    getInvoice () {
+      this.invoice.loading = true
+      APIGateway.invoice.get({ data: { id: this.$route.query.invoice } })
+        .then(invoice => {
+          this.invoice = invoice
+          this.invoice.loading = false
+        })
+        .catch(() => {
+          this.invoice.loading = false
+        })
+    }
+  }
 }
 </script>

+ 3 - 1
src/components/Widgets/Other/ClassroomCarousel/ClassroomCarousel.vue

@@ -143,7 +143,9 @@ export default {
     },
     getClassrooms () {
       this.classrooms.loading = true
-      return APIGateway.classroom.index()
+      return APIGateway.classroom.index({
+        unit__category__type: 'TRAINING'
+      })
     }
   }
 }

+ 63 - 16
src/components/Widgets/Other/Shop/ShopCompleteInfo/ShopCompleteInfo.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="complete-info">
     <q-banner class="banner complete-info-top-banner">
-      ثبت نام در دوره آموزشی
+      {{ mainTitle }}
     </q-banner>
     <q-card class="complete-info-steps-card">
       <div class="complete-info-step">
@@ -19,7 +19,7 @@
           </g>
         </svg>
         <span class="title">
-          مشاهده جزییات و قوانین دوره
+          {{ step1Title }}
           <svg xmlns="http://www.w3.org/2000/svg"
                width="15"
                height="15"
@@ -220,6 +220,22 @@ export default {
       { type: 'hidden', name: 'id', responseKey: 'id' }
     ]
   }),
+  computed: {
+    mainTitle () {
+      if (this.$route.query.source === 'event') {
+        return 'ثبت نام در رویداد'
+      } else {
+        return 'ثبت نام در دوره آموزشی'
+      }
+    },
+    step1Title () {
+      if (this.$route.query.source === 'event') {
+        return 'مشاهده جزییات و قوانین رویداد'
+      } else {
+        return 'مشاهده جزییات و قوانین دوره'
+      }
+    }
+  },
   mounted () {
     this.loadClassroomFromStore()
     this.mounted = true
@@ -232,17 +248,55 @@ export default {
       this.$refs.entityEdit.editEntity(false)
         .then(() => {
           if (this.$route.query.type === 'register') {
-            this.createInvoice()
-              .then(() => {
-                this.$router.push({ name: 'UserPanel.ShopPaymentFromWallet', query: this.$route.query })
-              })
-              .catch(() => {})
+            this.createInvoiceForClassroomOrRegisterEvent()
           } else {
             this.$router.push({ name: 'UserPanel.ShopPaymentFromWallet', query: this.$route.query })
           }
         })
         .catch(() => {})
     },
+    createInvoiceForClassroomOrRegisterEvent () {
+      this.register()
+        .then((data) => {
+          if (data === 'Invoice') {
+            this.$router.push({ name: 'UserPanel.ShopPaymentFromWallet', query: this.$route.query })
+          }
+        })
+        .catch(() => {})
+    },
+    register () {
+      return new Promise((resolve, reject) => {
+        this.invoice.loading = true
+        APIGateway.classroom.register(this.classroom.id)
+          .then((data) => {
+            if (data.type === 'Invoice') {
+              this.invoice = new Invoice(data.model)
+              this.invoice.loading = false
+              this.$store.commit('Shop/updateRegisterClassroomInvoice', this.invoice)
+              this.$q.notify({
+                message: 'صورت حساب شما ایجاد شد',
+                type: 'positive'
+              })
+              resolve('Invoice')
+            } else if (data.type === 'Registration') {
+              this.$q.notify({
+                message: 'ثبت نام شما با موفقیت انجام شد.',
+                type: 'positive'
+              })
+              const eventRegistrationId = data.model.classroom_info.id
+              if (this.$route.query.source === 'event') {
+                this.$router.push({ name: 'UserPanel.Profile.EventInfo', params: { id: eventRegistrationId } })
+              } else {
+                this.$router.push({ name: 'UserPanel.Profile.ClassroomInfo', params: { id: eventRegistrationId } })
+              }
+            }
+          })
+          .catch(() => {
+            this.invoice.loading = false
+            reject()
+          })
+      })
+    },
     createInvoice () {
       return new Promise((resolve, reject) => {
         this.invoice.loading = true
@@ -275,14 +329,7 @@ export default {
       this.rulesDialog = true
     },
     getHoldingType (type) {
-      const target = Enums.classroomHoldingTypes.find(item => item.value === type)
-      if (target) {
-        return target.label
-      }
-      return '-'
-    },
-    getAudienceGenderType (type) {
-      const target = Enums.genders.find(item => item.value === type)
+      const target = (new Classroom()).holding_typeEnums.find(item => item.value === type)
       if (target) {
         return target.label
       }
@@ -346,7 +393,7 @@ export default {
         position: relative;
         background-color: #F5F5F5;
         z-index: 2;
-        padding-right: 24px;
+        padding-right: 20px;
       }
       svg {
         width: 17px;

+ 22 - 4
src/components/Widgets/Other/Shop/ShopPaymentFromWallet/ShopPaymentFromWallet.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="complete-info">
     <q-banner class="banner complete-info-top-banner">
-      ثبت نام در دوره آموزشی
+      {{ mainTitle }}
     </q-banner>
     <q-card class="complete-info-steps-card">
       <div class="complete-info-step">
@@ -19,7 +19,7 @@
           </g>
         </svg>
         <span class="title">
-          مشاهده جزییات و قوانین دوره
+          {{ step1Title }}
           <svg xmlns="http://www.w3.org/2000/svg"
                width="15"
                height="15"
@@ -272,6 +272,20 @@ export default {
     loading: true
   }),
   computed: {
+    mainTitle () {
+      if (this.$route.query.source === 'event') {
+        return 'ثبت نام در رویداد'
+      } else {
+        return 'ثبت نام در دوره آموزشی'
+      }
+    },
+    step1Title () {
+      if (this.$route.query.source === 'event') {
+        return 'مشاهده جزییات و قوانین رویداد'
+      } else {
+        return 'مشاهده جزییات و قوانین دوره'
+      }
+    },
     isRegisterPage () {
       return this.$route.query.type === 'register'
     },
@@ -328,7 +342,11 @@ export default {
         .then((message) => {
           this.payMessage = message
           this.invoice.loading = false
-          this.$router.push({ name: 'UserPanel.Profile.ClassroomInfo', params: { id: this.classroom.id } })
+          if (this.$route.query.source === 'event') {
+            this.$router.push({ name: 'UserPanel.Profile.EventInfo', params: { id: this.classroom.id } })
+          } else {
+            this.$router.push({ name: 'UserPanel.Profile.ClassroomInfo', params: { id: this.classroom.id } })
+          }
         })
         .catch(() => {
           this.invoice.loading = false
@@ -455,7 +473,7 @@ export default {
         position: relative;
         background-color: #F5F5F5;
         z-index: 2;
-        padding-right: 24px;
+        padding-right: 20px;
       }
       svg {
         width: 17px;

+ 18 - 5
src/components/Widgets/Other/ShowClassroomInfo/ShowClassroomInfo.vue

@@ -2,7 +2,7 @@
   <div class="classroom">
     <q-banner v-if="!loading && !defaultOptions.profileMode"
               class="banner classroom-banner">
-      دوره های آموزشی
+      {{ classroomTypeTitle }}
       {{classroom.title}}
     </q-banner>
     <q-card v-if="!loading"
@@ -23,7 +23,7 @@
           </div>
           <div class="col-md-4 col-12 classroom-top-attributes">
             <div class="title">
-              دوره آموزشی
+              {{ classroomTypeTitle }}
               {{classroom.title}}:
             </div>
             <div class="attributes">
@@ -72,7 +72,8 @@
                    color="primary"
                    class="btn-register q-ml-md"
                    :to="{name: 'UserPanel.Profile.ClassroomInfo', params: {id: classroom.id}}">
-              مشاهده دوره
+              مشاهده
+              {{ classroomTypeTitle }}
             </q-btn>
             <q-btn v-if="classroom.is_enabled_dropping"
                    outline
@@ -88,7 +89,8 @@
                    class="btn-register q-ml-md"
                    :loading="fetchCodesLoading"
                    @click="openCodeLink">
-              آیین نامه دوره
+              آیین نامه
+              {{ classroomTypeTitle }}
             </q-btn>
           </div>
         </div>
@@ -258,7 +260,7 @@
           <div v-if="(!classroom.is_enabled_enrolment || !classroom.is_enabled_adding) && acceptClassType !== 'rules'"
                class="rulesDialog-accept-chk">
             <q-checkbox v-model="rulesAccept"
-                        label="قوانین دوره آموزشی را خواندم و قصد ثبت نام در دوره را دارم" />
+                        :label="'قوانین '+classroomTypeTitle+' را خواندم و قصد ثبت نام در '+classroomTypeTitle+' را دارم'" />
           </div>
           <div class="rulesDialog-btns q-gutter-md">
             <q-btn v-if="acceptClassType === 'rules'"
@@ -289,6 +291,7 @@ import ShamsiDate from 'src/assets/ShamsiDate.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { Classroom } from 'src/models/Classroom.js'
 import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
+import { UnitCategory } from 'src/models/UnitCategory'
 
 export default {
   name: 'ShowClassroomInfo',
@@ -299,6 +302,10 @@ export default {
       default: () => {
         return {}
       }
+    },
+    classroomType: {
+      type: String,
+      default: 'TRAINING'
     }
   },
   emits: ['onloadn'],
@@ -314,6 +321,12 @@ export default {
     loading: true,
     classroom: new Classroom()
   }),
+  computed: {
+    classroomTypeTitle () {
+      const unitCategory = new UnitCategory({ type: this.localOptions.classroomType })
+      return unitCategory.type_info.label
+    }
+  },
   methods: {
     prefetchServerDataPromise () {
       return this.getClassroom()

+ 4 - 2
src/components/Widgets/ProjectAttendanceSheet/AdminProjectAttendanceSheetShow/AdminProjectAttendanceSheetShow.vue

@@ -125,7 +125,8 @@ export default {
       APIGateway.projectAttendanceSheets.verify(this.$route.params.project_attendance_sheets_id)
         .then(() => {
           this.loading = false
-          this.reloadData()
+          // this.reloadData()
+          this.$router.go(-1)
         })
         .catch(() => {
           this.loading = false
@@ -137,7 +138,8 @@ export default {
       APIGateway.projectAttendanceSheets.notVerify(this.$route.params.project_attendance_sheets_id)
         .then(() => {
           this.loading = false
-          this.reloadData()
+          // this.reloadData()
+          this.$router.go(-1)
         })
         .catch(() => {
           this.loading = false

+ 9 - 2
src/components/Widgets/Registration/RegistrationList/OptionPanel.vue

@@ -1,13 +1,15 @@
 <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 { UnitCategory } from 'src/models/UnitCategory.js'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
 
@@ -16,6 +18,11 @@ export default defineComponent({
   components: {
     OptionPanelTabs
   },
-  mixins: [mixinOptionPanel]
+  mixins: [mixinOptionPanel],
+  data () {
+    return {
+      classroomTypeOptions: (new UnitCategory()).typeEnums
+    }
+  }
 })
 </script>

+ 30 - 3
src/components/Widgets/Registration/RegistrationList/RegistrationList.vue

@@ -3,7 +3,7 @@
        :style="localOptions.style">
     <template v-if="isUserLogin">
       <q-banner class="q-mb-md">
-        دوره های شما
+        {{ mainTitle }}
       </q-banner>
 
       <template v-if="!registrations.loading && registrations.list.length > 0">
@@ -16,7 +16,7 @@
         </div>
       </template>
       <div v-else-if="!registrations.loading && registrations.list.length === 0">
-        دوره ای یافت نشد.
+        {{ notFoundMessage }}
       </div>
       <div v-else-if="registrations.loading">
         <q-linear-progress v-if="registrations.loading"
@@ -39,16 +39,43 @@ export default {
   mixins: [mixinWidget, mixinAuth],
   data: () => {
     return {
+      defaultOptions: {
+        classroomType: 'TRAINING'
+      },
       registrations: new RegistrationList()
     }
   },
+  computed: {
+    mainTitle () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'دوره های شما'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'رویداد های شما'
+      }
+      return 'دوره های شما'
+    },
+    notFoundMessage () {
+      if (this.localOptions.classroomType === 'TRAINING') {
+        return 'دوره ای یافت نشد.'
+      }
+      if (this.localOptions.classroomType === 'EVENT') {
+        return 'رویدادی یافت نشد.'
+      }
+      return 'دوره ای یافت نشد.'
+    }
+  },
   mounted() {
     this.getRegistrations()
   },
   methods: {
     getRegistrations () {
       this.registrations.loading = true
-      APIGateway.registration.index({ per_page: 9999, owner: this.user.id })
+      APIGateway.registration.index({
+        per_page: 9999,
+        owner: this.user.id,
+        classroom__unit__category__type: this.localOptions.classroomType
+      })
         .then((registrations) => {
           this.registrations = new RegistrationList(registrations.list)
           this.registrations.loading = false

+ 112 - 30
src/components/Widgets/SessionAttendanceSheet/AdminSessionAttendanceSheetList/AdminSessionAttendanceSheetList.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="AdminContentList"
+  <div class="AdminSessionAttendanceSheetList"
        :style="localOptions.style">
     <breadcrumbs class="q-mb-xl"
                  style="margin-top: 29px; margin-bottom: 19px;" />
@@ -12,6 +12,7 @@
       </q-btn>
     </div>
     <entity-index v-if="mounted"
+                  ref="entityIndex"
                   v-model:value="inputs"
                   title="لیست حضور و غیاب"
                   :api="api"
@@ -21,6 +22,25 @@
                   :show-search-button="false"
                   :show-expand-button="false"
                   :show-reload-button="false">
+      <template #toolbar>
+        <q-btn color="primary"
+               :loading="exportReportLoading"
+               @click="getReportExcel">
+          خروجی اکسل
+        </q-btn>
+      </template>
+      <template #after-index-table>
+        <q-separator class="q-my-lg" />
+        <entity-edit ref="uploadExcelEntity"
+                     v-model:value="uploadInputs"
+                     title="ارسال لیست حضور و غیاب"
+                     :loaded-data="{}"
+                     :api="uploadAPI"
+                     :show-reload-button="false"
+                     :show-expand-button="false"
+                     :show-save-button="false"
+                     :show-close-button="false" />
+      </template>
       <template #entity-index-table-cell="{inputData}">
         <template v-if="inputData.col.name === 'number'">
           {{ inputData.rowNumber }}
@@ -52,16 +72,28 @@
 </template>
 
 <script>
-import { EntityIndex } from 'quasar-crud'
+import { shallowRef } from 'vue'
+import Assist from 'assets/js/Assist.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { Classroom } from 'src/models/Classroom.js'
+import { EntityIndex, EntityEdit } from 'quasar-crud'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+import { Registration } from 'src/models/Registration.js'
+import { UnitCategory } from 'src/models/UnitCategory.js'
 import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
-import { SessionAttendanceSheets } from 'src/models/SessionAttendanceSheets'
+import { SessionAttendanceSheets } from 'src/models/SessionAttendanceSheets.js'
+
+const BtnControlComp = shallowRef(BtnControl)
 
 export default {
   name: 'AdminSessionAttendanceSheetList',
-  components: { EntityIndex, Breadcrumbs },
+  components: {
+    EntityIndex,
+    EntityEdit,
+    Breadcrumbs
+  },
   mixins: [mixinWidget],
   data () {
     const sessionId = this.$route.params.session_id
@@ -76,8 +108,16 @@ export default {
         pageKey: 'page'
       },
       inputs: [
-        { type: 'hidden', name: 'session', value: sessionId }
+        { type: 'hidden', name: 'session', value: sessionId },
+        { type: 'select', name: 'registration__status', label: 'وضعیت ثبت نام', placeholder: ' ', value: null, options: (new Registration()).statusEnums, col: 'col-md-3 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-3 col-12' }
+      ],
+      uploadInputs: [
+        { type: 'hidden', name: 'type', value: 'excel' },
+        { type: 'file', name: 'file', label: 'انتخاب فایل', placeholder: ' ', value: null, col: 'col-md-8 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ارسال', placeholder: ' ', atClick: () => {}, col: 'col-md-4 col-12' }
       ],
+      uploadAPI: APIGateway.sessionAttendanceSheets.APIAdresses.importList,
       table: {
         columns: [
           {
@@ -132,49 +172,91 @@ export default {
         ]
       },
       mounted: false,
+      exportReportLoading: false,
       createRouteName: ''
     }
   },
+  computed: {
+    classroomTypeTitle () {
+      const unitCategory = new UnitCategory({ type: this.localOptions.classroomType })
+      return unitCategory.type_info.label
+    }
+  },
   mounted() {
     this.mounted = true
+    this.setActionBtn()
     this.getClassroom()
       .then(classroom => {
-        this.classroom = new Classroom(classroom)
-        this.$store.commit('AppLayout/updateBreadcrumbs', {
-          visible: true,
-          loading: false,
-          path: [
-            {
-              label: 'دوره های آموزشی',
-              to: { name: 'Admin.Classroom.Index' }
-            },
-            {
-              label: this.classroom.title,
-              to: { name: 'Admin.Classroom.Show', params: { id: this.$route.params.classroom_id } }
-            },
-            {
-              label: 'لیست حضور و غیاب',
-              to: { name: 'Admin.Classroom.Session.AttendanceSheetList', params: { classroom_id: this.$route.params.classroom_id, session_id: this.$route.params.session_id } }
-            }
-          ]
-        })
+        this.updateBreadcrumbs(classroom)
       })
   },
   methods: {
+    updateBreadcrumbs (classroom) {
+      this.classroom = new Classroom(classroom)
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'لیست ' + this.classroomTypeTitle,
+            to: { name: 'Admin.Classroom.Index' }
+          },
+          {
+            label: this.classroom.title,
+            to: { name: 'Admin.Classroom.Show', params: { id: this.$route.params.classroom_id } }
+          },
+          {
+            label: 'لیست حضور و غیاب',
+            to: { name: 'Admin.Classroom.Session.AttendanceSheetList', params: { classroom_id: this.$route.params.classroom_id, session_id: this.$route.params.session_id } }
+          }
+        ]
+      })
+    },
+    search () {
+      this.$refs.entityIndex.search()
+    },
+    getReportExcel () {
+      this.exportReportLoading = true
+      const status = FormBuilderAssist.getInputsByName(this.inputs, 'registration__status').value ? FormBuilderAssist.getInputsByName(this.inputs, 'registration__status').value : null
+      APIGateway.sessionAttendanceSheets.exportReport({
+        session: this.$route.params.session_id,
+        type: 'list',
+        registration__status: status
+      })
+        .then((xlsxData) => {
+          Assist.saveXlsx(xlsxData, this.classroom.title)
+          this.exportReportLoading = false
+        })
+        .catch((e) => {
+          this.exportReportLoading = false
+        })
+    },
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.search)
+      FormBuilderAssist.setAttributeByName(this.uploadInputs, 'btn', 'atClick', this.uploadExcelEntity)
+    },
     getClassroom () {
       return APIGateway.classroom.get(this.$route.params.classroom_id)
+    },
+    uploadExcelEntity () {
+      this.$refs.uploadExcelEntity.editEntity(false)
+        .then(() => {
+          this.search()
+        })
+        .catch(() => {
+          this.search()
+        })
     }
   }
 }
 </script>
 
 <style scoped lang="scss">
-.AdminContentList {
-  .more-action {
-    display: flex;
-    flex-flow: row;
-    justify-content: flex-end;
-    margin-bottom: 10px;
+.AdminSessionAttendanceSheetList {
+  :deep(.entity-edit) {
+    .q-item {
+      display: none;
+    }
   }
 }
 </style>

+ 9 - 2
src/components/Widgets/SessionAttendanceSheet/AdminSessionAttendanceSheetList/OptionPanel.vue

@@ -1,13 +1,15 @@
 <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 { UnitCategory } from 'src/models/UnitCategory.js'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
 
@@ -16,6 +18,11 @@ export default defineComponent({
   components: {
     OptionPanelTabs
   },
-  mixins: [mixinOptionPanel]
+  mixins: [mixinOptionPanel],
+  data () {
+    return {
+      classroomTypeOptions: (new UnitCategory()).typeEnums
+    }
+  }
 })
 </script>

+ 215 - 0
src/components/Widgets/Team/AdminTeamCreate/AdminTeamCreate.vue

@@ -0,0 +1,215 @@
+<template>
+  <div class="AdminTeamCreate"
+       :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 { FormBuilderAssist } from 'quasar-form-builder'
+import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+const FormBuilderInputEditorComp = shallowRef(FormBuilderInputEditor)
+
+export default {
+  name: 'AdminTeamCreate',
+  components: { EntityCreate },
+  mixins: [mixinWidget],
+  data () {
+    const classroomId = this.$route.params.classroom_id
+    return {
+      mounted: false,
+      entityLoading: false,
+      api: APIGateway.team.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Classroom.Team.Show',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان گروه', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'capacity', responseKey: 'capacity', label: 'ظرفیت', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_joinable', responseKey: 'is_joinable', label: 'قابل پیوستن', placeholder: ' ', value: false, col: 'col-md-3 col-12' },
+        {
+          type: 'entity',
+          name: 'leader',
+          selectionMode: 'single',
+          popUpButtonConfig: {
+            label: 'انتخاب سرگروه',
+            outline: true,
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+          dialogConfirmButtonConfig: {
+            label: 'تایید',
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+
+          apiAddress: APIGateway.classroomLeaders.APIAdresses.base,
+          tableTitle: 'لیست کل سرگروه ها',
+          showTableItemsRouteName: 'Admin.BlockManagement.Show',
+          tableKeys: {
+            data: 'results',
+            total: 'count',
+            currentPage: 'current',
+            perPage: 'per_page',
+            pageKey: 'page'
+          },
+          table: {
+            columns: [
+              {
+                name: 'number',
+                required: true,
+                label: 'شماره',
+                align: 'left',
+                field: () => ''
+              },
+              {
+                name: 'id',
+                required: true,
+                label: 'شناسه',
+                align: 'left',
+                field: row => row.leader_info.id
+              },
+              {
+                name: 'title',
+                required: true,
+                label: 'جنسیت',
+                align: 'left',
+                field: row => (row.leader_info.gender === 1) ? 'مذکر' : ((row.leader_info.gender === 2) ? 'مونث' : '-')
+              },
+              {
+                name: 'creation_time',
+                required: true,
+                label: 'نام و نام خانوادگی',
+                align: 'left',
+                field: row => row.leader_info.firstname + ' ' + row.leader_info.lastname
+              },
+              {
+                name: 'creation_time',
+                required: true,
+                label: 'کدملی',
+                align: 'left',
+                field: row => row.leader_info.national_code
+              },
+              {
+                name: 'creation_time',
+                required: true,
+                label: 'شماره تلفن',
+                align: 'left',
+                field: row => row.leader_info.mobile_number
+              },
+              {
+                name: 'creation_time',
+                required: true,
+                label: 'دانشگاه',
+                align: 'left',
+                field: row => row.leader_info.last_academy_name
+              },
+              {
+                name: 'creation_time',
+                required: true,
+                label: 'شهر',
+                align: 'left',
+                field: row => row.leader_info.living_city
+              },
+              {
+                name: 'actions',
+                required: true,
+                label: 'عملیات',
+                align: 'left',
+                field: ''
+              }
+            ]
+          },
+          inputs: [
+            { type: 'hidden', name: 'classroom', value: classroomId }
+          ],
+          itemIndicatorKey: row => row.leader_info.firstname + ' ' + row.leader_info.lastname,
+          // itemIndicatorKey: 'firstname',
+          itemIdentifyKey: 'leader',
+          indexConfig: {
+            itemIdentifyKey: 'leader'
+          },
+
+          value: null,
+          selected: null,
+          col: 'col-md-3 col-12'
+        },
+        { type: FormBuilderInputEditorComp, name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' },
+        { type: 'hidden', name: 'classroom', value: classroomId },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ایجاد گروه', placeholder: ' ', atClick: () => {}, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.create)
+    },
+    create() {
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity(false)
+        .then((response) => {
+          this.entityLoading = false
+          this.$router.push({ name: 'Admin.Classroom.Team.Show', params: { classroom_id: this.$route.params.classroom_id, team_id: response.data.id } })
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminTeamCreate {
+  .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/Team/AdminTeamCreate/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>

+ 446 - 0
src/components/Widgets/Team/AdminTeamShow/AdminTeamShow.vue

@@ -0,0 +1,446 @@
+<template>
+  <div class="AdminTeamShow"
+       :style="localOptions.style">
+    <div class="flex justify-end">
+      <q-btn flat
+             color="grey"
+             @click="$router.go(-1)">
+        بازگشت
+        >
+      </q-btn>
+    </div>
+    <entity-edit v-if="mounted"
+                 ref="entityEdit"
+                 v-model:value="inputs"
+                 title="اطلاعات گروه"
+                 :api="api"
+                 :entity-id-key="entityIdKey"
+                 :entity-param-key="entityParamKey"
+                 :show-close-button="false"
+                 :show-edit-button="false"
+                 :show-expand-button="false"
+                 :show-save-button="false"
+                 :show-reload-button="false"
+                 :redirect-after-edit="false"
+                 :after-load-input-data="afterLoadInputData" />
+
+    <q-separator class="q-my-lg" />
+
+    <entity-index v-if="mounted"
+                  ref="teamRegistrationList"
+                  v-model:value="teamRegistrationListInputs"
+                  title="لیست اعضای گروه"
+                  :api="teamRegistrationListApi"
+                  :table="teamRegistrationListTable"
+                  :table-keys="teamRegistrationListTableKeys"
+                  :show-search-button="false"
+                  :show-reload-button="false"
+                  :show-expand-button="false">
+      <template #toolbar>
+        <form-builder ref="formBuilder"
+                      :key="addTeamRegistrationKey"
+                      v-model:value="addTeamRegistrationInputs"
+                      :loading="addNewTeamRegistrationLoading" />
+      </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">
+            <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 { Team } from 'src/models/Team.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { EntityEdit, EntityIndex } from 'quasar-crud'
+import BtnControl from 'src/components/Control/btn.vue'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+import { FormBuilder, inputMixin, FormBuilderAssist } from 'quasar-form-builder'
+import EntityInput from 'quasar-crud/src/components/Entity/Attachment/EntityInput.vue'
+import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+const EntityInputComp = shallowRef(EntityInput)
+const FormBuilderInputEditorComp = shallowRef(FormBuilderInputEditor)
+
+export default {
+  name: 'AdminTeamShow',
+  components: {
+    DeleteBtn,
+    EntityEdit,
+    EntityIndex,
+    FormBuilder
+  },
+  mixins: [mixinWidget, inputMixin],
+  data () {
+    const teamId = this.$route.params.team_id
+    const classroomId = this.$route.params.classroom_id
+    return {
+      mounted: false,
+      team: new Team(),
+      entityLoading: true,
+      api: APIGateway.team.APIAdresses.byId(teamId),
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان گروه', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'capacity', responseKey: 'capacity', label: 'ظرفیت', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_joinable', responseKey: 'is_joinable', label: 'قابل پیوستن', placeholder: ' ', value: false, col: 'col-md-3 col-12' },
+        { type: 'separator', name: 'space', label: 'مشخصات سرگروه', className: 'custom-separator', col: 'col-12' },
+        { type: 'input', name: 'leader_info.firstname', responseKey: 'leader_info.firstname', label: 'نام سرگروه', placeholder: ' ', readonly: true, ignoreValue: true, col: 'col-md-3 col-12' },
+        { type: 'input', name: 'leader_info.lastname', responseKey: 'leader_info.lastname', label: 'نام خانوادگی سرگروه', placeholder: ' ', readonly: true, ignoreValue: true, col: 'col-md-3 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-12' },
+        {
+          type: 'entity',
+          name: 'leader',
+          responseKey: 'leader_info',
+          selectionMode: 'single',
+          popUpButtonConfig: {
+            label: 'انتخاب سرگروه',
+            outline: true,
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+          dialogConfirmButtonConfig: {
+            label: 'تایید',
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+
+          apiAddress: APIGateway.classroomLeaders.APIAdresses.base,
+          tableTitle: 'لیست کل سرگروه ها',
+          showTableItemsRouteName: 'Admin.BlockManagement.Show',
+          tableKeys: {
+            data: 'results',
+            total: 'count',
+            currentPage: 'current',
+            perPage: 'per_page',
+            pageKey: 'page'
+          },
+          table: {
+            columns: [
+              {
+                name: 'leader',
+                required: true,
+                label: 'شناسه',
+                align: 'left',
+                field: row => row.leader
+              },
+              {
+                name: 'leader_info.gender',
+                required: true,
+                label: 'جنسیت',
+                align: 'left',
+                field: row => (row.leader_info.gender === 1) ? 'مذکر' : ((row.leader_info.gender === 2) ? 'مونث' : '-')
+              },
+              {
+                name: 'leader_info.full_name',
+                required: true,
+                label: 'نام و نام خانوادگی',
+                align: 'left',
+                field: row => row.leader_info.firstname + ' ' + row.leader_info.lastname
+              },
+              {
+                name: 'leader_info.national_code',
+                required: true,
+                label: 'کدملی',
+                align: 'left',
+                field: row => row.leader_info.national_code
+              },
+              {
+                name: 'leader_info.mobile_number',
+                required: true,
+                label: 'شماره تلفن',
+                align: 'left',
+                field: row => row.leader_info.mobile_number
+              },
+              {
+                name: 'leader_info.last_academy_name',
+                required: true,
+                label: 'دانشگاه',
+                align: 'left',
+                field: row => row.leader_info.last_academy_name
+              },
+              {
+                name: 'leader_info.living_city',
+                required: true,
+                label: 'شهر',
+                align: 'left',
+                field: row => row.leader_info.living_city
+              }
+            ]
+          },
+          inputs: [
+            { type: 'hidden', name: 'classroom', value: classroomId }
+          ],
+          // itemIndicatorKey: row => row.firstname + ' ' + row.lastname,
+          itemIndicatorKey: 'leader_info.id',
+          // itemIndicatorKey: 'firstname',
+          itemIdentifyKey: 'leader_info.id',
+          itemIdentifyKeyOfIndexTable: 'leader',
+          indexConfig: {
+            itemIndicatorKey: row => row.leader_info.firstname + ' ' + row.leader_info.lastname,
+            itemIdentifyKey: 'leader'
+            // itemIdentifyKey: 'id'
+          },
+
+          value: null,
+          selected: null,
+          col: 'col-md-3 col-12'
+        },
+        { type: FormBuilderInputEditorComp, name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' },
+        { type: 'hidden', name: 'classroom', value: classroomId },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ویرایش گروه', placeholder: ' ', atClick: () => {}, col: 'col-md-6' }
+      ],
+
+      addNewTeamRegistrationLoading: false,
+      addTeamRegistrationKey: Date.now(),
+      addTeamRegistrationInputs: [
+        {
+          type: EntityInputComp,
+          name: 'leaders',
+          selectionMode: 'multiple',
+          popUpButtonConfig: {
+            label: 'اختصاص عضو جدید',
+            outline: true,
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+          dialogConfirmButtonConfig: {
+            label: 'تایید',
+            buttonColor: 'deep-purple',
+            buttonTextColor: 'white',
+            buttonBadgeColor: 'pink'
+          },
+
+          apiAddress: APIGateway.registration.APIAdresses.base,
+          tableTitle: 'لیست کاربران',
+          showTableItemsRouteName: 'Admin.BlockManagement.Show',
+          tableKeys: {
+            data: 'results',
+            total: 'count',
+            currentPage: 'current',
+            perPage: 'per_page',
+            pageKey: 'page'
+          },
+          table: {
+            columns: [
+              {
+                name: 'id',
+                required: true,
+                label: 'شناسه',
+                align: 'left',
+                field: row => row.owner_info.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 => row.owner_info.mobile_number
+              }
+            ]
+          },
+          inputs: [
+            { type: 'hidden', name: 'teams__isnull', value: true },
+            { type: 'hidden', name: 'classroom', value: classroomId }
+          ],
+          itemIndicatorKey: row => row.owner_info.firstname + ' ' + row.owner_info.lastname,
+          // itemIndicatorKey: 'firstname',
+          itemIdentifyKey: 'id',
+          onConfirmed: this.onConfirmedSelectedRegistrations,
+
+          value: [],
+          selected: [],
+          col: 'col-md-12 col-12'
+        }
+      ],
+      teamRegistrationListInputs: [
+        { type: 'hidden', name: 'team', value: teamId }
+      ],
+      teamRegistrationListApi: APIGateway.teamRegistrations.APIAdresses.base,
+      teamRegistrationListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.registration_info.owner_info.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'جنسیت',
+            align: 'left',
+            field: row => (row.registration_info.owner_info.gender === 1) ? 'مذکر' : ((row.registration_info.owner_info.gender === 2) ? 'مونث' : '-')
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.registration_info.owner_info.firstname + ' ' + row.registration_info.owner_info.lastname
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'کدملی',
+            align: 'left',
+            field: row => row.registration_info.owner_info.national_code
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شماره تلفن',
+            align: 'left',
+            field: row => row.registration_info.owner_info.mobile_number
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'دانشگاه',
+            align: 'left',
+            field: row => row.registration_info.owner_info.last_academy_name
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شهر',
+            align: 'left',
+            field: row => row.registration_info.owner_info.living_city
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      teamRegistrationListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    onConfirmedSelectedRegistrations (data) {
+      const registrations = data.map(item => item.id)
+      this.addNewLeaderLoading = true
+      APIGateway.team.appendRegistrations({ teamId: this.$route.params.team_id, registrations })
+        .then(() => {
+          this.$refs.teamRegistrationList.search()
+          this.addTeamRegistrationInputs[0].value = []
+          this.addTeamRegistrationInputs[0].selected = []
+          this.addTeamRegistrationKey = Date.now()
+          this.addNewLeaderLoading = false
+        })
+        .catch(() => {
+          this.addTeamRegistrationInputs[0].value = []
+          this.addTeamRegistrationInputs[0].selected = []
+          this.addTeamRegistrationKey = Date.now()
+          this.addNewLeaderLoading = false
+        })
+    },
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.edit)
+    },
+    afterLoadInputData (responseData) {
+      this.team = new Team(responseData)
+      this.entityLoading = false
+      FormBuilderAssist.setAttributeByName(this.inputs, 'leader', 'value', this.team.leader)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'leader', 'selected', this.team.leader_info)
+    },
+    edit() {
+      this.entityLoading = true
+      this.$refs.entityEdit.editEntity(false)
+        .then(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.registration_info.owner_info.firstname + ' ' + row.registration_info.owner_info.lastname + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminTeamShow {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+    display: flex;
+    flex-flow: row;
+    position: relative;
+    .static-title {
+
+    }
+    .dynamic-title {
+
+    }
+    .back-action {
+      position: absolute;
+      right: 0;
+      top: 0;
+    }
+  }
+  :deep(.form) {
+    padding: 24px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Team/AdminTeamShow/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>

+ 219 - 0
src/components/Widgets/Team/LeaderTeamShow/LeaderTeamShow.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="LeaderTeamShow"
+       :style="localOptions.style">
+    <div class="flex justify-end">
+      <q-btn flat
+             color="grey"
+             @click="$router.go(-1)">
+        بازگشت
+        >
+      </q-btn>
+    </div>
+    <entity-show v-if="mounted"
+                 ref="entityShow"
+                 v-model:value="inputs"
+                 title="اطلاعات گروه"
+                 :api="api"
+                 :entity-id-key="entityIdKey"
+                 :entity-param-key="entityParamKey"
+                 :show-close-button="false"
+                 :show-edit-button="false"
+                 :show-expand-button="false"
+                 :show-save-button="false"
+                 :show-reload-button="false"
+                 :redirect-after-edit="false"
+                 :show-index-button="false"
+                 :after-load-input-data="afterLoadInputData" />
+
+    <q-separator class="q-my-lg" />
+
+    <entity-index v-if="mounted"
+                  ref="teamRegistrationList"
+                  v-model:value="teamRegistrationListInputs"
+                  title="لیست اعضای گروه"
+                  :api="teamRegistrationListApi"
+                  :table="teamRegistrationListTable"
+                  :table-keys="teamRegistrationListTableKeys"
+                  :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">
+            <!--            <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 { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { EntityShow, EntityIndex } from 'quasar-crud'
+
+export default {
+  name: 'LeaderTeamShow',
+  components: {
+    EntityShow,
+    EntityIndex
+  },
+  mixins: [mixinWidget],
+  data () {
+    const teamId = this.$route.params.team_id
+    return {
+      mounted: false,
+      entityLoading: true,
+      api: APIGateway.team.APIAdresses.byId(teamId),
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان گروه', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'capacity', responseKey: 'capacity', label: 'ظرفیت', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'input', name: 'used_capacity', responseKey: 'used_capacity', label: 'ظرفیت استفاده شده', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'separator', name: 'space', size: '0', col: 'col-12' },
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' }
+      ],
+
+      teamRegistrationListInputs: [
+        { type: 'hidden', name: 'team', value: teamId }
+      ],
+      teamRegistrationListApi: APIGateway.teamRegistrations.APIAdresses.base,
+      teamRegistrationListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.registration_info.owner_info.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'جنسیت',
+            align: 'left',
+            field: row => (row.registration_info.owner_info.gender === 1) ? 'مذکر' : ((row.registration_info.owner_info.gender === 2) ? 'مونث' : '-')
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'نام و نام خانوادگی',
+            align: 'left',
+            field: row => row.registration_info.owner_info.firstname + ' ' + row.registration_info.owner_info.lastname
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'کدملی',
+            align: 'left',
+            field: row => row.registration_info.owner_info.national_code
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شماره تلفن',
+            align: 'left',
+            field: row => row.registration_info.owner_info.mobile_number
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'دانشگاه',
+            align: 'left',
+            field: row => row.registration_info.owner_info.last_academy_name
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'شهر',
+            align: 'left',
+            field: row => row.registration_info.owner_info.living_city
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      teamRegistrationListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted() {
+    this.mounted = true
+  },
+  methods: {
+    onConfirmedSelectedRegistrations (data) {
+      const registrations = data.map(item => item.id)
+      this.addNewLeaderLoading = true
+      APIGateway.team.appendRegistrations({ teamId: this.$route.params.team_id, registrations })
+        .then(() => {
+          this.$refs.teamRegistrationList.search()
+          this.addNewLeaderLoading = false
+        })
+        .catch(() => {
+          this.addNewLeaderLoading = false
+        })
+    },
+    afterLoadInputData () {
+      this.entityLoading = false
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.registration_info.owner_info.firstname + ' ' + row.registration_info.owner_info.lastname + ' اطمینان دارید؟'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.LeaderTeamShow {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+    display: flex;
+    flex-flow: row;
+    position: relative;
+    .static-title {
+
+    }
+    .dynamic-title {
+
+    }
+    .back-action {
+      position: absolute;
+      right: 0;
+      top: 0;
+    }
+  }
+  :deep(.form) {
+    padding: 24px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Team/LeaderTeamShow/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>

+ 138 - 0
src/components/Widgets/Team/LeaderTeamsIndex/LeaderTeamsIndex.vue

@@ -0,0 +1,138 @@
+<template>
+  <entity-index v-if="mounted"
+                ref="teamsList"
+                v-model:value="teamsListInputs"
+                title="لیست گروه ها"
+                :api="teamsListApi"
+                :table="teamsListTable"
+                :table-keys="teamsListTableKeys"
+                :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 === 'action'">
+        <div class="action-column-entity-index">
+          <q-btn size="md"
+                 color="primary"
+                 label="جزییات"
+                 :to="{name: 'UserPanel.Leader.Teams.Show', params: {team_id:inputData.props.row.id}}"
+                 :loading="createInvoiceLoading"
+                 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 { APIGateway } from 'src/api/APIGateway.js'
+import { Classroom } from 'src/models/Classroom.js'
+import { mixinAuthData } from 'src/mixin/Mixins.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+
+export default {
+  name: 'LeaderTeamsShow',
+  components: {
+    EntityIndex
+  },
+  mixins: [mixinAuthData],
+  data () {
+    return {
+      mounted: false,
+      createInvoiceLoading: false,
+      classroom: new Classroom(),
+
+      dropClassroomByAdminLoading: false,
+      exportReportLoading: false,
+      teamsListInputs: [
+        { type: 'hidden', name: 'leader', value: null }
+      ],
+      teamsListApi: APIGateway.team.APIAdresses.myLeadingTeams,
+      teamsListTable: {
+        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: 'leader_info',
+            required: true,
+            label: 'سرگروه',
+            align: 'left',
+            field: row => row.leader_info?.firstname + ' ' + row.leader_info?.lastname
+          },
+          {
+            name: 'used_capacity',
+            required: true,
+            label: 'ظرفیت باقی مانده',
+            align: 'left',
+            field: row => row.capacity - row.used_capacity
+          },
+          {
+            name: 'capacity',
+            required: true,
+            label: 'ظرفیت',
+            align: 'left',
+            field: row => row.capacity
+          },
+          {
+            name: 'action',
+            required: true,
+            label: ' ',
+            align: 'left',
+            field: ''
+          }
+        ],
+        data: []
+      },
+      teamsListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      }
+    }
+  },
+  mounted () {
+    this.setMembersListActionBtn()
+    FormBuilderAssist.setAttributeByName(this.teamsListInputs, 'leader', 'value', this.user.id)
+    this.mounted = true
+  },
+  methods: {
+    setMembersListActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.teamsListInputs, 'btn', 'atClick', this.searchMembersList)
+    },
+    searchMembersList () {
+      this.$refs.teamsList.search()
+    }
+  }
+}
+</script>

+ 21 - 0
src/components/Widgets/Team/LeaderTeamsIndex/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>

+ 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 }

+ 56 - 0
src/models/EventRegistration.js

@@ -0,0 +1,56 @@
+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' }
+    ], {
+      status: {
+        infoKey: 'status_info',
+        enumListKey: 'statusEnums',
+        enums: [
+          {
+            label: 'پیش ثبت نام',
+            value: 'ENROLLED'
+          },
+          {
+            label: 'ثبت نام',
+            value: 'REGISTERED'
+          },
+          {
+            label: 'حذف توسط کاربر',
+            value: 'DROPPED_BY_ITSELF'
+          },
+          {
+            label: 'حذف توسط ادمین',
+            value: 'DROPPED_BY_ADMIN'
+          },
+          {
+            label: 'حذف سیستمی (شرایط غیر مجاز)',
+            value: 'DROPPED_FOR_CONDITION'
+          }
+        ]
+      }
+    })
+  }
+}
+
+class EventRegistrationList extends Collection {
+  model () {
+    return EventRegistration
+  }
+}
+export { EventRegistration, EventRegistrationList }

+ 8 - 0
src/models/ProjectAttendanceSheets.js

@@ -38,6 +38,14 @@ class ProjectAttendanceSheets extends Model {
           {
             label: 'بررسی شده',
             value: 'REVIEWED'
+          },
+          {
+            label: 'تایید شده',
+            value: 'VERIFIED'
+          },
+          {
+            label: 'رد شده',
+            value: 'NOT_VERIFIED'
           }
         ]
       },

+ 6 - 0
src/models/Registration.js

@@ -27,6 +27,7 @@ class Registration extends Model {
     ], {
       status: {
         infoKey: 'status_info',
+        enumListKey: 'statusEnums',
         enums: [
           {
             label: 'پیش ثبت نام',
@@ -43,11 +44,16 @@ class Registration extends Model {
           {
             label: 'حذف توسط ادمین',
             value: 'DROPPED_BY_ADMIN'
+          },
+          {
+            label: 'حذف سیستمی (شرایط غیر مجاز)',
+            value: 'DROPPED_FOR_CONDITION'
           }
         ]
       },
       time_status: {
         infoKey: 'time_status_info',
+        enumListKey: 'time_statusEnums',
         enums: [
           {
             label: 'فعال',

+ 56 - 0
src/models/Team.js

@@ -0,0 +1,56 @@
+import { User } from 'src/models/User.js'
+import { Collection, Model } from 'js-abstract-model'
+
+class Team extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'title' },
+      { key: 'capacity' },
+      { key: 'used_capacity' },
+      { key: 'description' },
+      { key: 'is_active' },
+      { key: 'is_deleted' },
+      { key: 'join_status' },
+      { key: 'classroom' },
+      { key: 'leader' },
+      {
+        key: 'leader_info',
+        relatedModel: User
+      },
+      { key: 'creation_time' },
+      { key: 'last_modification_time' }
+    ], {
+      join_status: {
+        infoKey: 'join_status_info',
+        enumListKey: 'join_statusEnums',
+        enums: [
+          {
+            label: 'فعال',
+            value: 'ENABLED'
+          },
+          {
+            label: 'ظرفیت تکمیل',
+            value: 'FULL'
+          },
+          {
+            label: 'عضویت توسط ادمین',
+            value: 'DISABLED'
+          },
+          {
+            label: 'عضوهستید',
+            value: 'JOINED'
+          }
+        ]
+      }
+    })
+  }
+}
+
+class TeamList extends Collection {
+  model () {
+    return Team
+  }
+}
+
+export { Team, TeamList }

+ 21 - 1
src/models/UnitCategory.js

@@ -16,11 +16,31 @@ class UnitCategory extends Model {
         key: 'units_info',
         relatedModel: UnitList
       },
+      { key: 'type' },
       { key: 'creation_time' },
       { key: 'last_modification_time' },
       { key: 'is_active' },
       { key: 'is_deleted' }
-    ])
+    ], {
+      type: {
+        infoKey: 'type_info',
+        enumListKey: 'typeEnums',
+        enums: [
+          {
+            label: 'دوره آموزشی',
+            value: 'TRAINING'
+          },
+          {
+            label: 'حلقه مباحثاتی',
+            value: 'DiscussionCircles'
+          },
+          {
+            label: 'رویداد',
+            value: 'EVENT'
+          }
+        ]
+      }
+    })
   }
 }
 

+ 16 - 2
src/models/User.js

@@ -61,8 +61,22 @@ class User extends Model {
       { key: 'address' },
       { key: 'moballegh_info' },
       { key: 'user_permissions' }
-
-    ])
+    ], {
+      gender: {
+        infoKey: 'gender_info',
+        enumListKey: 'genderEnums',
+        enums: [
+          {
+            label: 'مذکر',
+            value: 1
+          },
+          {
+            label: 'مونث',
+            value: 0
+          }
+        ]
+      }
+    })
 
     if (this.user_id !== null) {
       this.id = this.user_id

+ 1 - 1
src/pages/Admin/Classroom/SessionActivitySheets/sessionActivitySheets.vue

@@ -13,7 +13,7 @@ export default {
   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' } } }] } }]
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminSessionAttendanceSheetList', options: { style: {}, classroomType: 'TRAINING' } }], 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() {

+ 23 - 0
src/pages/Admin/Classroom/Team/create.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.Team.Create',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminTeamCreate' }], 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/Classroom/Team/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.Team.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminTeamShow' }], 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>

+ 10 - 238
src/pages/Admin/Classroom/create.vue

@@ -1,251 +1,23 @@
 <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="classroomEntityCreate"
-                   :key="classroomEntityCreateKey"
-                   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="createClassroom" />
-        </div>
-      </template>
-    </entity-create>
-  </div>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
 </template>
 
 <script>
-import { shallowRef } from 'vue'
-import { EntityCreate } from 'quasar-crud'
-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)
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
 
 export default {
   name: 'Admin.Classroom.Create',
-  components: {
-    EntityCreate
-  },
-  data () {
+  mixins: [mixinPageOptions],
+  data: () => {
     return {
-      mounted: false,
-      unitsLoading: false,
-      classroomEntityCreateKey: Date.now(),
-      newUnitLoading: false,
-      newUnitName: null,
-      newUnitSessionCount: null,
-      api: APIGateway.classroom.APIAdresses.base,
-      entityIdKey: 'id',
-      entityParamKey: 'id',
-      showRouteName: 'Admin.Classroom.Show',
-      inputs: [
-        { type: 'separator', name: 'space', label: 'مشخصات دوره', className: 'custom-separator', 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: [], 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: Enums.classroomHoldingTypes, 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: 'classroom_address', responseKey: 'classroom_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: Enums.genders, 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)
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminClassroomCreate', options: { style: {}, classroomType: 'TRAINING' } }], 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' } } }] } }]
     }
   },
-  mounted () {
-    this.preLoadData()
-      .then(() => {
-        this.mounted = true
-      })
-      .catch(() => {
-      })
-  },
-  methods: {
-    setInputAttr (name, attr, value) {
-      this.$refs.classroomEntityCreate.setInputAttributeByName(name, attr, value)
-    },
-    preLoadData () {
-      return new Promise((resolve, reject) => {
-        const promise1 = this.getProfessors()
-        const promise2 = this.getCategories()
-        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(() => {})
-    },
-    getCategories () {
-      return APIGateway.unitCategory.index({ per_page: 9999, type: 'TRAINING' })
-        .then((categories) => {
-          FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'options', categories.list.list.map(item => {
-            return {
-              value: item.id,
-              label: item.title
-            }
-          }))
-        })
-        .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
-    },
-    createClassroom () {
-      this.$refs.classroomEntityCreate.createEntity()
-    }
+  created() {
+    this.currenSections = this.sections
   }
 }
 </script>

+ 11 - 249
src/pages/Admin/Classroom/index.vue

@@ -1,261 +1,23 @@
 <template>
-  <div class="AdminClassroomIndex">
-    <div class="add-new-action-row">
-      <q-btn color="primary"
-             :to="{name: 'Admin.Classroom.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.Classroom.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>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
 </template>
 
 <script>
-import { shallowRef } from 'vue'
-import { EntityIndex } from 'quasar-crud'
-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)
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
 
 export default {
-  name: 'Admin.Classroom.Index',
-  components: {
-    EntityIndex,
-    DeleteBtn
-  },
-  data () {
+  name: 'Admin.Classroom.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
     return {
-      mounted: false,
-      inputs: [
-        {
-          type: 'select',
-          name: 'status',
-          options: Enums.classroomStatuses,
-          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.classroom.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.getClassroomStatusesTitle(row.status)
-          },
-          {
-            name: 'title',
-            required: true,
-            label: 'نوع برگزاری',
-            align: 'left',
-            field: row => this.getClassroomHoldingTypeTitle(row.holding_type)
-          },
-          {
-            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'
-      }
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminClassroomList', options: { style: {}, classroomType: 'TRAINING' } }], 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' } } }] } }]
     }
   },
-  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 + ' اطمینان دارید؟'
-    },
-    getClassroomHoldingTypeTitle (type) {
-      const target = Enums.classroomHoldingTypes.find(item => item.value === type)
-      if (!target) {
-        return '-'
-      }
-
-      return target.label
-    },
-    getClassroomStatusesTitle (type) {
-      const target = Enums.classroomStatuses.find(item => item.value === type)
-      if (!target) {
-        return '-'
-      }
-
-      return target.label
-    }
+  created() {
+    this.currenSections = this.sections
   }
 }
 </script>
-
-<style lang="scss" scoped>
-.AdminClassroomIndex {
-  .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>

+ 9 - 4
src/pages/Admin/Classroom/project/members.vue

@@ -47,12 +47,15 @@
 </template>
 
 <script>
-// import { shallowRef } from 'vue'
+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'
+import BtnControl from 'src/components/Control/btn.vue'
+import { Registration } from 'src/models/Registration.js'
+import { ProjectAttendanceSheets } from 'src/models/ProjectAttendanceSheets.js'
+
+const BtnControlComp = shallowRef(BtnControl)
 
 // const BtnControlComp = shallowRef(BtnControl)
 
@@ -67,7 +70,9 @@ export default {
       mounted: false,
       exportReportLoading: false,
       inputs: [
-        { type: 'hidden', name: 'project', value: projectId }
+        { type: 'hidden', name: 'project', value: projectId },
+        { type: 'select', name: 'registration__status', label: 'وضعیت ثبت نام', placeholder: ' ', value: null, options: (new Registration()).statusEnums, col: 'col-md-3 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-3 col-12' }
       ],
       api: APIGateway.projectAttendanceSheets.APIAdresses.project_sheets,
       table: {

+ 1 - 1
src/pages/Admin/Classroom/show.vue

@@ -13,7 +13,7 @@ export default {
   mixins: [mixinPageOptions],
   data: () => {
     return {
-      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminClassroomShow', 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' } } }] } }]
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminClassroomShow', options: { style: {}, classroomType: 'TRAINING' } }], 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() {

+ 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.Event.Session.AttendanceSheetList',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminSessionAttendanceSheetList', options: { style: {}, classroomType: 'EVENT' } }], 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>

+ 23 - 0
src/pages/Admin/Event/create.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.Event.Create',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminClassroomCreate', options: { style: {}, classroomType: 'EVENT' } }], 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>

+ 172 - 0
src/pages/Admin/Event/ema-create.vue

@@ -0,0 +1,172 @@
+<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 { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import ContentMedias from 'src/components/FormBuilderCustumComponents/ContentMedias/ContentMedias.vue'
+import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
+
+const ContentMediasComp = shallowRef(ContentMedias)
+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: 'category', options: (new Event()).categoryEnums, 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: '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: ContentMediasComp, name: 'medias', responseKey: 'medias_info', sourceType: 'EVENT', col: 'col-md-12 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: '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' }
+      ]
+    }
+  },
+  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()
+        this.$nextTick(() => {
+          Promise.all([promise1])
+            .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(() => {})
+    },
+    getUserFullname (user) {
+      return user.firstname + ' ' + user.lastname
+    },
+    createEvent () {
+      this.$refs.eventEntityCreate.createEntity()
+    }
+  }
+}
+</script>

部分文件因文件數量過多而無法顯示