ali vor 2 Jahren
Ursprung
Commit
cbc369bbec

+ 2 - 2
package.json

@@ -46,8 +46,8 @@
     "process": "^0.11.10",
     "quasar": "^2.14.2",
     "quasar-app-extension-q-page-builder": "^0.4.4",
-    "quasar-crud": "^2.1.10",
-    "quasar-form-builder": "^2.4.6",
+    "quasar-crud": "^2.1.12",
+    "quasar-form-builder": "^2.4.7",
     "quasar-template-builder": "^1.0.23",
     "quasar-ui-q-page-builder": "^0.4.4",
     "socket.io-client": "^4.6.1",

+ 2 - 0
src/api/APIGateway.js

@@ -11,6 +11,7 @@ import NoticeAPI from 'src/api/models/Notice.js'
 import TicketAPI from 'src/api/models/Ticket.js'
 import WalletAPI from 'src/api/models/Wallet.js'
 import BasketAPI from 'src/api/models/Basket.js'
+import HadithAPI from 'src/api/models/Hadith.js'
 import TestSetAPI from 'src/api/models/TestSet.js'
 import InvoiceAPI from 'src/api/models/Invoice.js'
 import PaymentAPI from 'src/api/models/Payment.js'
@@ -66,6 +67,7 @@ export const APIGateway = {
   ticket: new TicketAPI(),
   wallet: new WalletAPI(),
   basket: new BasketAPI(),
+  hadith: new HadithAPI(),
   testSet: new TestSetAPI(),
   project: new ProjectAPI(),
   package: new PackageAPI(),

+ 92 - 0
src/api/models/Hadith.js

@@ -0,0 +1,92 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { Hadith, HadithList } from 'src/models/Hadith.js'
+
+export default class HadithAPI extends APIRepository {
+  constructor() {
+    super('hadithes', appApi)
+    this.APIAdresses = {
+      base: '/cma/hadithes',
+      byId: (id) => '/cma/hadithes/' + id
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base
+    }
+    this.restUrl = (id) => this.APIAdresses.base + '/' + id
+    /* Setting the callback functions for the CRUD operations. */
+    this.setCrudCallbacks({
+      get: (response) => { return new Hadith(response.data) },
+      post: (response) => { return new Hadith(response.data) },
+      put: (response) => { return new Hadith(response.data) },
+      delete: (response) => { return response.data }
+    })
+  }
+
+  index(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        category: 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 HadithList(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 Hadith(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  add (data) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        persian_text: null, // String
+        arabian_text: null, // String
+        narrator: null, // String
+        source: null, // String
+        publish_date: null // String
+      }, data),
+      resolveCallback: (response) => {
+        return new Hadith(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 8 - 0
src/components/Template/SideBar/AdminPanelSideBar.vue

@@ -173,6 +173,14 @@ export default {
             }
           ]
         },
+        {
+          title: 'احادیث',
+          roles: ['superuser'],
+          routeName: 'Admin.Hadith.List',
+          icon: 'person_add',
+          show: true,
+          active: false
+        },
         {
           title: 'مدیریت مالی',
           roles: ['superuser', 'financial_department_head'],

+ 97 - 0
src/components/Widgets/Hadithe/AdminHadithCreate/AdminHadithCreate.vue

@@ -0,0 +1,97 @@
+<template>
+  <div class="AdminDonationCreate"
+       :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'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'AdminDonationCreate',
+  components: { EntityCreate },
+  mixins: [mixinWidget],
+  data () {
+    return {
+      mounted: false,
+      entityLoading: false,
+      api: APIGateway.hadith.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Hadith.Show',
+      inputs: [
+        { type: 'inputEditor', name: 'persian_text', responseKey: 'persian_text', label: 'متن فارسی', col: 'col-12' },
+        { type: 'inputEditor', name: 'arabian_text', responseKey: 'arabian_text', label: 'متن عربی', col: 'col-12' },
+        { type: 'input', name: 'narrator', responseKey: 'narrator', label: 'گوینده', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'source', responseKey: 'source', label: 'منبع', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'date', name: 'publish_date', responseKey: 'publish_date', outsideLabel: 'تاریخ انتشار', col: 'col-md-4 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'تایید', placeholder: ' ', atClick: this.create, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted() {
+    this.mounted = true
+  },
+  methods: {
+    create() {
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity()
+        .then(() => {
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminDonationCreate {
+  .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/Hadithe/AdminHadithCreate/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>

+ 134 - 0
src/components/Widgets/Hadithe/AdminHadithList/AdminHadithList.vue

@@ -0,0 +1,134 @@
+<template>
+  <div class="AdminHadithList"
+       :style="localOptions.style">
+    <div class="more-action">
+      <q-btn label="ایجاد حدیث جدید"
+             color="primary"
+             :to="{name: 'Admin.Hadith.Create'}" />
+    </div>
+    <entity-index v-if="mounted"
+                  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 === 'thumbnail'">
+          <q-img :src="inputData.col.value"
+                 width="100px" />
+        </template>
+        <template v-else-if="inputData.col.name === 'action'">
+          <q-btn color="primary"
+                 :to="{name: 'Admin.Hadith.Show', params: {id: inputData.props.row.id}}">
+            مشاهده جزییات
+          </q-btn>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import Assist from 'src/assets/js/Assist.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+
+export default {
+  name: 'AdminHadithList',
+  components: { EntityIndex },
+  mixins: [mixinWidget],
+  data: () => {
+    return {
+      api: APIGateway.hadith.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [],
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'persian_text',
+            required: true,
+            label: 'متن حدیث',
+            align: 'left',
+            field: row => row.persian_text
+          },
+          {
+            name: 'arabian_text',
+            required: true,
+            label: 'متن عربی',
+            align: 'left',
+            field: row => row.arabian_text
+          },
+          {
+            name: 'source',
+            required: true,
+            label: 'منبع',
+            align: 'left',
+            field: row => row.source
+          },
+          {
+            name: 'narrator',
+            required: true,
+            label: 'کوینده',
+            align: 'left',
+            field: row => row.narrator
+          },
+          {
+            name: 'publish_date',
+            required: true,
+            label: 'تاریخ انتشار',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.publish_date)
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: row => ''
+          }
+        ]
+      },
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  mounted() {
+    this.mounted = true
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminHadithList {
+  .more-action {
+    display: flex;
+    flex-flow: row;
+    justify-content: flex-end;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Hadithe/AdminHadithList/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>

+ 115 - 0
src/components/Widgets/Hadithe/AdminHadithShow/AdminHadithShow.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="AdminHadithShow"
+       :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-route-name="showRouteName"
+                 :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" />
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+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'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'AdminHadithShow',
+  components: {
+    EntityEdit
+  },
+  mixins: [mixinWidget],
+  data () {
+    return {
+      mounted: false,
+      entityLoading: true,
+      api: APIGateway.hadith.APIAdresses.byId(this.$route.params.id),
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Hadith.Show',
+      indexRouteName: 'Admin.Hadith.List',
+      inputs: [
+        { type: 'inputEditor', name: 'persian_text', responseKey: 'persian_text', label: 'متن فارسی', col: 'col-12' },
+        { type: 'inputEditor', name: 'arabian_text', responseKey: 'arabian_text', label: 'متن عربی', col: 'col-12' },
+        { type: 'input', name: 'narrator', responseKey: 'narrator', label: 'گوینده', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'source', responseKey: 'source', label: 'منبع', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'date', name: 'publish_date', responseKey: 'publish_date', outsideLabel: 'تاریخ انتشار', col: 'col-md-4 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ویرایش حدیث', placeholder: ' ', atClick: this.edit, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted() {
+    this.mounted = true
+  },
+  methods: {
+    afterLoadInputData () {
+      this.entityLoading = false
+    },
+    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
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminHadithShow {
+  .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/Hadithe/AdminHadithShow/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>

+ 120 - 0
src/components/Widgets/Hadithe/HadithList/HadithList.vue

@@ -0,0 +1,120 @@
+<template>
+  <div class="DonationList"
+       :style="localOptions.style">
+    <div class="more-action">
+      <breadcrumbs />
+    </div>
+    <entity-index v-if="mounted"
+                  ref="entityIndex"
+                  v-model:value="inputs"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :create-route-name="createRouteName"
+                  :default-layout="false"
+                  :table-grid-size="true"
+                  :show-search-button="false"
+                  :show-expand-button="false"
+                  :show-reload-button="false">
+      <template #entity-index-table-cell="{inputData}">
+        <template v-if="inputData.col.name === 'action'">
+          <q-btn color="primary"
+                 :to="{name: 'UserPanel.Ticket.Show', params: {id: inputData.props.row.id}}">
+            مشاهده جزییات
+          </q-btn>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+      <template #entity-index-table-item-cell="{inputData}">
+        <div class="col-md-6 col-12 q-pa-sm">
+          <donation-item :donation="getDonation(inputData.props.row)" />
+        </div>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import { Donation } from 'src/models/Donation.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import DonationItem from './components/DonationItem.vue'
+import { DonationCategory } from 'src/models/DonationCategory.js'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+
+export default {
+  name: 'DonationList',
+  components: { EntityIndex, DonationItem, Breadcrumbs },
+  mixins: [mixinWidget],
+  data () {
+    return {
+      api: APIGateway.donation.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [],
+      table: {
+        columns: []
+      },
+      donationCategory: new DonationCategory(),
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  mounted() {
+    this.mounted = true
+    this.getDonationCategory()
+  },
+  methods: {
+    getDonation (donation) {
+      return new Donation(donation)
+    },
+    getDonationCategory () {
+      this.donationCategory.loading = true
+      APIGateway.donationCategory.get(this.$route.params.donation_category_id)
+        .then((donationCategory) => {
+          this.donationCategory = new DonationCategory(donationCategory)
+          this.updateBreadcrumbs(this.donationCategory)
+        })
+    },
+    updateBreadcrumbs (donationCategory) {
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'خانه',
+            to: { name: 'Public.Home' }
+          },
+          {
+            label: 'پرداخت کمک مالی',
+            to: { name: 'Public.DonationCategory.List' }
+          },
+          {
+            label: donationCategory.title,
+            to: { name: 'Public.DonationCategory.Show', params: { donation_category_id: donationCategory.id } }
+          }
+        ]
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.DonationList {
+  .more-action {
+    display: flex;
+    flex-flow: row;
+    justify-content: flex-start;
+    margin-bottom: 24px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Hadithe/HadithList/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>

+ 65 - 0
src/components/Widgets/Hadithe/HadithList/components/DonationItem.vue

@@ -0,0 +1,65 @@
+<template>
+  <q-card flat
+          class="donation-categoty__item">
+    <q-card-section class="donation-categoty__wrapper">
+      <div class="row">
+        <div class="col-md-5 col-12 donation-categoty__thumbnail-col">
+          <q-img class="donation-categoty__thumbnail"
+                 :src="donation.thumbnail" />
+        </div>
+        <div class="col-md-7 col-12">
+          <div class="donation-categoty__title">
+            {{ donation.title }}
+          </div>
+          <div class="donation-categoty__description ellipsis-3-lines"
+               v-html="donation.description" />
+          <div class="donation-categoty__actions">
+            <q-btn flat
+                   :to="{name: 'Public.DonationCategory.Donation.Show', params: {donation_category_id: $route.params.donation_category_id, donation_id: donation.id}}">
+              جزییات بیشتر
+              <q-icon name="keyboard_backspace" />
+            </q-btn>
+          </div>
+        </div>
+      </div>
+    </q-card-section>
+  </q-card>
+</template>
+
+<script>
+import { Donation } from 'src/models/Donation'
+
+export default {
+  name: 'DonationItem',
+  props: {
+    donation: {
+      type: Donation,
+      default: new Donation()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.donation-categoty__item {
+  border-radius: 8px;
+  background-color: #FBF4EA;
+  .donation-categoty__wrapper {
+    position: relative;
+    padding-bottom: 48px;
+    .donation-categoty__thumbnail-col {
+      padding-right: 16px;
+      .donation-categoty__thumbnail {
+        width: 100%;
+      }
+    }
+    .donation-categoty__actions {
+      display: flex;
+      justify-content: flex-end;
+      position: absolute;
+      right: 16px;
+      bottom: 16px;
+    }
+  }
+}
+</style>

+ 172 - 0
src/components/Widgets/Hadithe/HadithShow/HadithShow.vue

@@ -0,0 +1,172 @@
+<template>
+  <div class="DonationShow"
+       :style="localOptions.style">
+    <div class="more-action">
+      <breadcrumbs />
+    </div>
+    <div class="main-section">
+      <div v-if="donationCategory.id"
+           class="show-donation-category-section">
+        <div class="donation-category-content">
+          <q-banner class="donation-category-title">
+            {{donationCategory.title}}
+          </q-banner>
+          <div class="donation-category-description"
+               v-html="donationCategory.description" />
+        </div>
+        <div class="donation-category-thumbnail">
+          <q-img :src="donationCategory.thumbnail" />
+        </div>
+      </div>
+      <div v-if="donation.id"
+           class="show-donation-section">
+        <div class="show-donation-payment-section">
+          <q-banner class="donation-category-title">
+            پرداخت
+            {{donation.title}}
+          </q-banner>
+        </div>
+        <div class="show-donation-info-section">
+          <div class="donation-content">
+            <q-banner class="donation-title">
+              {{donation.title}}
+            </q-banner>
+            <div class="donation-description"
+                 v-html="donation.description" />
+          </div>
+          <div class="donation-thumbnail">
+            <q-img :src="donation.thumbnail" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Donation } from 'src/models/Donation.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { DonationCategory } from 'src/models/DonationCategory.js'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+
+export default {
+  name: 'DonationList',
+  components: { Breadcrumbs },
+  mixins: [mixinWidget],
+  data () {
+    return {
+      api: APIGateway.donation.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [],
+      table: {
+        columns: []
+      },
+      donation: new Donation(),
+      donationCategory: new DonationCategory(),
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  mounted() {
+    this.getDonation()
+  },
+  methods: {
+    getDonation () {
+      this.donation.loading = true
+      APIGateway.donation.get(this.$route.params.donation_category_id)
+        .then((donation) => {
+          this.donation = new Donation(donation)
+          this.getDonationCategory()
+        })
+    },
+    getDonationCategory () {
+      this.donationCategory.loading = true
+      APIGateway.donationCategory.get(this.$route.params.donation_category_id)
+        .then((donationCategory) => {
+          this.donationCategory = new DonationCategory(donationCategory)
+          this.updateBreadcrumbs(this.donationCategory, this.donation)
+        })
+    },
+    updateBreadcrumbs (donationCategory, donation) {
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'خانه',
+            to: { name: 'Public.Home' }
+          },
+          {
+            label: 'پرداخت کمک مالی',
+            to: { name: 'Public.DonationCategory.List' }
+          },
+          {
+            label: donationCategory.title,
+            to: { name: 'Public.DonationCategory.Show', params: { donation_category_id: donationCategory.id } }
+          },
+          {
+            label: donation.title,
+            to: { name: 'Public.DonationCategory.Donation.Show', params: { donation_category_id: donationCategory.id, donation_id: donation.id } }
+          }
+        ]
+      })
+      this.mounted = true
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.DonationShow {
+  .more-action {
+    display: flex;
+    flex-flow: row;
+    justify-content: flex-start;
+    margin-bottom: 24px;
+  }
+  .main-section {
+    .show-donation-category-section {
+      display: flex;
+      justify-content: flex-start;
+      gap: 16px;
+      .donation-category-thumbnail {
+        width: 30%;
+      }
+      .donation-category-content {
+        width: 70%;
+        .donation-category-title {
+          margin-bottom: 16px;
+        }
+        .donation-category-description {}
+      }
+    }
+    .show-donation-section {
+      .show-donation-payment-section {
+        margin-bottom: 16px;
+      }
+      .show-donation-info-section {
+        display: flex;
+        justify-content: flex-start;
+        gap: 16px;
+        .donation-thumbnail {
+          width: 30%;
+        }
+        .donation-content {
+          width: 70%;
+          .donation-title {
+            margin-bottom: 16px;
+          }
+          .donation-description {}
+        }
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Hadithe/HadithShow/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 - 1
src/components/Widgets/Store/Discount/AdminStoreDiscountList/AdminStoreDiscountList.vue

@@ -40,8 +40,8 @@
 </template>
 
 <script>
-import Assist from 'assets/js/Assist.js'
 import { EntityIndex } from 'quasar-crud'
+import Assist from 'src/assets/js/Assist.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 

+ 22 - 0
src/models/Hadith.js

@@ -0,0 +1,22 @@
+import { Model, Collection } from 'js-abstract-model'
+
+class Hadith extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'persian_text' },
+      { key: 'arabian_text' },
+      { key: 'narrator' },
+      { key: 'source' },
+      { key: 'publish_date' }
+    ])
+  }
+}
+
+class HadithList extends Collection {
+  model() {
+    return Hadith
+  }
+}
+
+export { Hadith, HadithList }

+ 23 - 0
src/pages/Admin/Hadith/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.Hadith.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminHadithCreate' }], options: {} }], 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/Hadith/List.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.Hadith.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminHadithList' }], options: {} }], 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/Hadith/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.Hadith.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminHadithShow' }], 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 - 0
src/router/routes/Admin.js

@@ -208,6 +208,16 @@ export default {
         { name: 'Admin.Content.Ticket', path: 'ticket', component: () => import('src/pages/Admin/Content/Ticket.vue'), breadcrumbs: { title: 'درخواست ها دوره های آموزشی' } }
       ]
     },
+    {
+      path: 'hadith',
+      name: 'Admin.Hadith',
+      component: () => import('layouts/bareLayout.vue'),
+      children: [
+        { path: '', name: 'Admin.Hadith.List', component: () => import('src/pages/Admin/Hadith/List.vue') },
+        { path: 'create', name: 'Admin.Hadith.Create', component: () => import('src/pages/Admin/Hadith/Create.vue') },
+        { path: ':id', name: 'Admin.Hadith.Show', component: () => import('src/pages/Admin/Hadith/Show.vue') }
+      ]
+    },
     {
       path: 'donation',
       name: 'Admin.Donation',

+ 9 - 9
yarn.lock

@@ -5074,22 +5074,22 @@ quasar-app-extension-q-page-builder@^0.4.4:
   dependencies:
     quasar-ui-q-page-builder "^0.4.4"
 
-quasar-crud@^2.1.10:
-  version "2.1.10"
-  resolved "https://registry.yarnpkg.com/quasar-crud/-/quasar-crud-2.1.10.tgz#a0e87910869e4ffa593ca574c31ff2b267389724"
-  integrity sha512-kWK9W9D4LaPfdOU7GfGlspQEryXa7MP+Ci/5aZr0IEWcZyIC3YD0uzGJDVExyZyZJjDw2fgfYCwsymWM+b+WPQ==
+quasar-crud@^2.1.12:
+  version "2.1.12"
+  resolved "https://registry.yarnpkg.com/quasar-crud/-/quasar-crud-2.1.12.tgz#a7e8adf22cd403c3cad504930ac3e07a23c057ca"
+  integrity sha512-lcLBEmPPaHC9pXvHPq2my/W0i1mTy18pxV1gpx2Q8h7vcXDc/rgxdkiHDS+LEb8JleGytc6x4nuv2BSz+ezE2Q==
   dependencies:
     "@quasar/extras" "^1.16.9"
     axios "^1.6.8"
     quasar "^2.14.2"
-    quasar-form-builder "^2.4.6"
+    quasar-form-builder "^2.4.7"
     shvl "^3.0.0"
     vue "^3.4.21"
 
-quasar-form-builder@^2.4.6:
-  version "2.4.6"
-  resolved "https://registry.yarnpkg.com/quasar-form-builder/-/quasar-form-builder-2.4.6.tgz#8265a6600f0c9614e6ab240c1de4f5fd331261a3"
-  integrity sha512-Tef35LsFr3VP3zhn1c8l9bf2Do8E7Tu9pdXtsIkwIrOdXsdLFzLTGtBV6/WtXsh4koDOK5jGDmWZhESf1dQz0w==
+quasar-form-builder@^2.4.7:
+  version "2.4.7"
+  resolved "https://registry.yarnpkg.com/quasar-form-builder/-/quasar-form-builder-2.4.7.tgz#ef3bcb8785423d327e82f24dfd5d6b42d52fe6fc"
+  integrity sha512-r98H8fV6Y+WbTNYxOs6m++4V459wPFlmMxNAE+tsVlaKjQp7tv/rooxwwQ8XKLnGe0yQP/7O5YcC0s0EPRfayA==
   dependencies:
     "@quasar/extras" "^1.16.9"
     core-js "^3.34.0"