Browse Source

fixed #111

ali 3 years ago
parent
commit
0cc045b3ff

+ 2 - 0
src/api/APIGateway.js

@@ -10,6 +10,7 @@ import InvoiceAPI from 'src/api/models/Invoice.js'
 import PaymentAPI from 'src/api/models/Payment.js'
 import SessionAPI from 'src/api/models/Session.js'
 import ContentAPI from 'src/api/models/Content.js'
+import ProjectAPI from 'src/api/models/Project.js'
 import ClassroomAPI from 'src/api/models/Classroom.js'
 import InstalmentAPI from 'src/api/models/Instalment.js'
 import TransactionAPI from 'src/api/models/Transaction.js'
@@ -35,6 +36,7 @@ export const APIGateway = {
   media: new MediaAPI(),
   ticket: new TicketAPI(),
   wallet: new WalletAPI(),
+  project: new ProjectAPI(),
   invoice: new InvoiceAPI(),
   payment: new PaymentAPI(),
   session: new SessionAPI(),

+ 3 - 3
src/components/FormBuilderCustumComponents/ContentsSelector/SelectorComponent.vue

@@ -34,7 +34,7 @@
                 :option-label="'title'"
                 :option-value="'id'"
                 multiple
-                :loading="contentOptions.laoding" />
+                :loading="contentOptions.loading" />
     </div>
   </div>
 </template>
@@ -185,10 +185,10 @@ export default {
       APIGateway.content.index({ category: categoryId, per_page: 9999 })
         .then(contents => {
           this.contentOptions = new ContentList(contents.list)
-          this.contentOptions.laoding = false
+          this.contentOptions.loading = false
         })
         .catch(() => {
-          this.contentOptions.laoding = false
+          this.contentOptions.loading = false
         })
     },
     onChangeBakhsh () {

+ 246 - 0
src/pages/Admin/Classroom/project/create.vue

@@ -0,0 +1,246 @@
+<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>
+</template>
+
+<script>
+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'
+
+export default {
+  name: 'Admin.Classroom.Create',
+  components: {
+    EntityCreate
+  },
+  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: '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: 'inputEditor', 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: 'inputEditor', name: 'rules', responseKey: 'rules', label: 'قوانین دوره آموزشی', col: 'col-12' },
+
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' },
+        { type: 'inputEditor', name: 'specification', responseKey: 'specification', label: 'مشخصات', col: 'col-12' },
+        { type: 'inputEditor', name: 'holding_detail', responseKey: 'holding_detail', label: 'نحوه برگزاری', col: 'col-12' },
+
+        // -----------------------------------------------------------------------------------------------------------
+
+        { type: 'separator', name: 'space', label: 'موارد زیر در طرح نبودند', className: 'custom-separator', col: 'col-12' },
+        { type: 'select', name: 'audience_role', responseKey: 'audience_role', options: Enums.groups, placeholder: ' ', label: 'نقش', col: 'col-md-4 col-12' },
+
+        { type: 'hidden', name: 'id', responseKey: 'id', label: 'id', col: 'col-md-3 col-12' }
+      ]
+    }
+  },
+  computed: {
+    selectedCategoryId () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'category')?.value
+    }
+  },
+  watch: {
+    selectedCategoryId () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'value', null)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'unit', 'options', [])
+      this.getUnits(this.selectedCategoryId)
+    }
+  },
+  mounted () {
+    this.preLoadData()
+      .then(() => {
+        this.mounted = true
+      })
+      .catch(() => {
+      })
+  },
+  methods: {
+    setInputAttr (name, attr, value) {
+      this.$refs.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()
+    }
+  }
+}
+</script>

+ 221 - 0
src/pages/Admin/Classroom/project/members.vue

@@ -0,0 +1,221 @@
+<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 #toolbar>
+        <q-btn color="primary"
+               :loading="exportReportLoading"
+               @click="exportExcel">
+          خروجی اکسل
+        </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.User.Show', params: {id: inputData.props.row.id}}"
+                   class="q-mr-md" />
+            <delete-btn @click="showConfirmRemoveDialog(inputData.props.row, 'id', getRemoveMessage(inputData.props.row))">
+              <q-tooltip>
+                حذف
+              </q-tooltip>
+            </delete-btn>
+          </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 Assist from 'src/assets/js/Assist.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import BtnControl from 'src/components/Control/btn.vue'
+import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'Admin.User.Index',
+  components: {
+    DeleteBtn,
+    EntityIndex
+  },
+  data () {
+    return {
+      mounted: false,
+      exportReportLoading: false,
+      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: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-3 col-12' }
+      ],
+      api: APIGateway.user.APIAdresses.base,
+      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.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
+          },
+          {
+            name: 'email',
+            required: true,
+            label: 'ایمیل',
+            align: 'left',
+            field: row => row.email
+          },
+          {
+            name: 'roles',
+            required: true,
+            label: 'نقش',
+            align: 'left',
+            field: row => row.roles.join(', ')
+          },
+          {
+            name: 'is_active',
+            required: true,
+            label: 'وضعیت',
+            align: 'left',
+            field: row => row.is_active ? 'فعال' : 'غیر فعال'
+          },
+          {
+            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: {
+    exportExcel () {
+      const params = this.$refs.entityIndex.createParams()
+      this.exportReportLoading = true
+      delete params.btn
+      params.type = 'users'
+      APIGateway.user.exportReport(params)
+        .then((xlsxData) => {
+          Assist.saveXlsx(xlsxData, 'لیست کاربران')
+          this.exportReportLoading = false
+        })
+        .catch(() => {
+          this.exportReportLoading = false
+        })
+    },
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].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>

+ 98 - 0
src/pages/Admin/Classroom/project/show.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="flex justify-end">
+    <q-btn flat
+           color="grey"
+           @click="$router.go(-1)">
+      بازگشت
+      >
+    </q-btn>
+  </div>
+  <entity-edit v-if="mounted"
+               ref="projectEntityEdit"
+               v-model:value="inputs"
+               title="جزییات جلسه"
+               :api="api"
+               :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">
+    <template #after-form-builder>
+      <div class="flex justify-end q-mt-md">
+        <q-btn color="primary"
+               label="ذخیره جزییات"
+               @click="updateSessionTemplates" />
+      </div>
+    </template>
+  </entity-edit>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityEdit } from 'quasar-crud'
+import { Project } from 'src/models/Project.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import ContentsSelector from 'src/components/FormBuilderCustumComponents/ContentsSelector/ContentsSelector.vue'
+
+const ContentsSelectorComp = shallowRef(ContentsSelector)
+
+export default {
+  name: 'Admin.Classroom.Project.Show',
+  components: {
+    EntityEdit
+  },
+  data () {
+    const projectId = this.$route.params.project_id
+    return {
+      mounted: false,
+      newUnitLoading: false,
+      newUnitName: null,
+      newUnitSessionCount: null,
+      api: APIGateway.project.APIAdresses.byId(projectId),
+      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: 'description', responseKey: 'description', label: 'توضیحات پروژه', placeholder: ' ', col: 'col-12' },
+        { type: ContentsSelectorComp, name: 'contents', responseKey: 'contents_info', col: 'col-12' },
+        { type: 'hidden', name: 'id', responseKey: 'id' },
+        { type: 'hidden', name: 'classroom', responseKey: 'classroom' }
+      ]
+    }
+  },
+  mounted () {
+    this.mounted = true
+  },
+  methods: {
+    updateSessionTemplates () {
+      this.$refs.projectEntityEdit.editEntity(false)
+    },
+    getRemoveMessage (row) {
+      return 'آیا از حذف ' + row.title + ' اطمینان دارید؟'
+    },
+    reloadUnits () {
+      this.$refs.unitEntityIndex.reload()
+    }
+  }
+}
+</script>
+
+<style>
+.fit-to-card {
+  margin: -16px;
+}
+</style>

+ 154 - 7
src/pages/Admin/Classroom/show.vue

@@ -19,7 +19,7 @@
              label="مشخصات" />
       <q-tab name="educations"
              label="لیست جلسات" />
-      <q-tab name="movies1"
+      <q-tab name="projects"
              label="پروژه‌ها" />
       <q-tab name="movies2"
              label="آزمون" />
@@ -85,7 +85,10 @@
             </q-btn>
           </template>
           <template v-slot:entity-index-table-cell="{inputData, showConfirmRemoveDialog}">
-            <template v-if="inputData.col.name === 'actions'">
+            <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"
@@ -105,8 +108,58 @@
                     height="200px" />
       </q-tab-panel>
 
-      <q-tab-panel name="movies1">
-        پروژه‌ها
+      <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 #toolbar>
+            <q-btn color="primary"
+                   outline
+                   :loading="addNewProjectLoading"
+                   @click="addNewProject">
+              افزودن پروژه جدید
+            </q-btn>
+          </template>
+          <template v-slot:entity-index-table-cell="{inputData, showConfirmRemoveDialog}">
+            <template v-if="inputData.col.name === 'number'">
+              {{ inputData.rowNumber }}
+            </template>
+            <template v-else-if="inputData.col.name === 'actions'">
+              <div class="action-column-entity-index">
+                <q-btn size="md"
+                       color="primary"
+                       label="جزییات"
+                       :to="{name: 'Admin.Classroom.Project.Show', params: {id: $route.params.id, project_id: inputData.props.row.id}}"
+                       class="q-mr-md" />
+                <delete-btn @click="showConfirmRemoveDialog(inputData.props.row, 'id', getRemoveMessage(inputData.props.row))" />
+              </div>
+            </template>
+            <template v-else-if="inputData.col.name === 'members'">
+              <div class="action-column-entity-index">
+                <q-btn size="md"
+                       color="primary"
+                       outline
+                       label="بررسی پروژه"
+                       :to="{name: 'Admin.Classroom.Project.Members', params: {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 name="movies2">
         آزمون
@@ -222,6 +275,7 @@ import { EntityEdit, EntityIndex } from 'quasar-crud'
 import { FormBuilderAssist } from 'quasar-form-builder'
 import BtnControl from 'src/components/Control/btn.vue'
 import DeleteBtn from 'src/components/Control/DeleteBtn.vue'
+import { Project } from 'src/models/Project'
 
 const BtnControlComp = shallowRef(BtnControl)
 
@@ -237,6 +291,7 @@ export default {
     return {
       mounted: false,
       addNewSessionLoading: false,
+      addNewProjectLoading: false,
       createInvoiceLoading: false,
       classroomEntityEditKey: Date.now(),
       tab: 'classroomInfo',
@@ -349,10 +404,17 @@ export default {
       sessionListTable: {
         columns: [
           {
-            name: 'id',
+            name: 'number',
             required: true,
             label: 'شماره',
             align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
             field: row => row.id
           },
           {
@@ -383,8 +445,7 @@ export default {
             align: 'left',
             field: ''
           }
-        ],
-        data: []
+        ]
       },
       sessionListTableKeys: {
         data: 'results',
@@ -394,6 +455,78 @@ export default {
         pageKey: 'page'
       },
 
+      projectListInputs: [
+        { type: 'hidden', name: 'classroom', value: this.$route.params.id, label: 'نام دوره', col: 'col-12' }
+      ],
+      projectListApi: APIGateway.project.APIAdresses.base,
+      projectListTable: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'عنوان پروژه',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'type',
+            required: true,
+            label: 'نوع',
+            align: 'left',
+            field: row => (new Project(row)).typeEnums
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'زمان شروع پروژه',
+            align: 'left',
+            field: row => row.beginning_doing_period ? ShamsiDate.getDateTime(row.beginning_doing_period) : '-'
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'مهلت ارسال پروژه',
+            align: 'left',
+            field: row => row.ending_doing_period ? ShamsiDate.getDateTime(row.ending_doing_period) : '-'
+          },
+          {
+            name: 'members',
+            required: true,
+            label: 'بررسی',
+            align: 'left',
+            field: ''
+          },
+          {
+            name: 'actions',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: ''
+          }
+        ]
+      },
+      projectListTableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+
       dropClassroomByAdminLoading: false,
       exportReportLoading: false,
       membersListInputs: [
@@ -603,6 +736,20 @@ export default {
           this.addNewSessionLoading = false
         })
     },
+    addNewProject () {
+      this.addNewProjectLoading = true
+      APIGateway.project.create({
+        title: '-',
+        classroom: this.$route.params.id
+      })
+        .then(() => {
+          this.$refs.projectList.search()
+          this.addNewProjectLoading = false
+        })
+        .catch(() => {
+          this.addNewProjectLoading = false
+        })
+    },
     setMembersListActionBtn () {
       FormBuilderAssist.setAttributeByName(this.membersListInputs, 'btn', 'atClick', this.searchMembersList)
     },

+ 4 - 0
src/router/routes.js

@@ -518,6 +518,10 @@ const routes = [
               { name: 'Admin.Classroom.Index', path: '', component: () => import('src/pages/Admin/Classroom/index.vue') },
               { name: 'Admin.Classroom.Create', path: 'create', component: () => import('src/pages/Admin/Classroom/create.vue'), breadcrumbs: { title: 'ساخت دوره آموزشی' } },
               { name: 'Admin.Classroom.Show', path: ':id', component: () => import('src/pages/Admin/Classroom/show.vue'), breadcrumbs: { title: 'مشاهده اطلاعات دوره آموزشی' } },
+              // { name: 'Admin.Classroom.Project.Create', path: ':id/project/create', component: () => import('src/pages/Admin/Classroom/project/create.vue'), breadcrumbs: { title: 'ساخت دوره آموزشی' } },
+              { name: 'Admin.Classroom.Project.Show', path: ':id/project/:project_id', component: () => import('src/pages/Admin/Classroom/project/show.vue'), breadcrumbs: { title: 'مشاهده اطلاعات دوره آموزشی' } },
+              { name: 'Admin.Classroom.Project.Members', path: ':id/project/:project_id/members', component: () => import('src/pages/Admin/Classroom/project/members.vue'), breadcrumbs: { title: 'مشاهده اطلاعات دوره آموزشی' } },
+              { name: 'Admin.Classroom.Project', path: ':id', component: () => import('src/pages/Admin/Classroom/show.vue'), breadcrumbs: { title: 'مشاهده اطلاعات دوره آموزشی' } },
               { name: 'Admin.Classroom.Ticket', path: 'ticket', component: () => import('src/pages/Admin/Classroom/Ticket.vue'), breadcrumbs: { title: 'درخواست ها دوره های آموزشی' } }
             ]
           },