2 Комити 05ff48c3c6 ... c8c8274353

Аутор SHA1 Порука Датум
  ali c8c8274353 Merge branch 'master' of http://git.hghweb.ir/ali.s/palizvani-front-vite пре 1 година
  ali 3d4fb4a213 fix #483 пре 1 година

+ 65 - 32
src/components/Widgets/ContentCategory/ContentCategoryList/ContentCategoryList.vue

@@ -30,37 +30,36 @@
         دسته بندی جزئی
       </span>
     </q-banner>
-    <entity-index v-if="mounted"
-                  ref="entityIndex"
-                  v-model:value="inputs"
-                  :api="api"
-                  :table="table"
-                  :table-keys="tableKeys"
-                  :table-grid-size="true"
-                  :default-layout="false"
-                  :show-search-button="false"
-                  :show-expand-button="false"
-                  :show-reload-button="false">
-      <template #entity-index-table-item-cell="{inputData}">
-        <content-category-item :content-category="getContentCategory(inputData.props.row)"
-                               :show-category-route-name="showCategoryRouteName" />
+    <filters :show-category-route-name="showCategoryRouteName"
+             :content-categories="contentCategories"
+             @filter="onChangeFilter" />
+    <div class="contents row">
+      <q-linear-progress v-if="contents.loading"
+                         class="q-my-md"
+                         indeterminate />
+      <template v-else>
+        <div v-for="(content, contentIndex) in contents.list"
+             :key="contentIndex"
+             class="col-md-4">
+          <content-item :content="content" />
+        </div>
       </template>
-    </entity-index>
+    </div>
   </div>
 </template>
 
 <script>
-import { EntityIndex } from 'quasar-crud'
+import Filters from './filters.vue'
+import ContentItem from './ContentItem.vue'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
-import { FormBuilderAssist } from 'quasar-form-builder'
-import ContentCategoryItem from './ContentCategoryItem.vue'
-import { ContentCategory } from 'src/models/ContentCategory.js'
+import { ContentList } from 'src/models/Content.js'
 import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+import { ContentCategory, ContentCategoryList } from 'src/models/ContentCategory.js'
 
 export default {
   name: 'ContentCategoryList',
-  components: { ContentCategoryItem, EntityIndex, Breadcrumbs },
+  components: { Breadcrumbs, Filters, ContentItem },
   mixins: [mixinWidget],
   data () {
     return {
@@ -80,6 +79,9 @@ export default {
         columns: []
       },
       mounted: false,
+      subFilterExpanded: false,
+      contents: new ContentList(),
+      contentCategories: new ContentCategoryList(),
       contentCategory: new ContentCategory(),
       contentCategoryParent: new ContentCategory(),
       contentCategoryParentParent: new ContentCategory(),
@@ -144,21 +146,13 @@ export default {
   },
   created() {
     this.updateBreadcrumbs()
-
-    if (this.localOptions.listType === 'category_parent_parent') {
-      FormBuilderAssist.setAttributeByName(this.inputs, 'parent__isnull', 'value', true)
-    } else if (this.localOptions.listType === 'category_parent') {
-      FormBuilderAssist.setAttributeByName(this.inputs, 'parent__isnull', 'value', null)
-      FormBuilderAssist.setAttributeByName(this.inputs, 'parent', 'value', this.contentCategoryId)
-    } else if (this.localOptions.listType === 'category') {
-      FormBuilderAssist.setAttributeByName(this.inputs, 'parent__isnull', 'value', null)
-      FormBuilderAssist.setAttributeByName(this.inputs, 'parent', 'value', this.contentCategoryId)
-    }
+  },
+  mounted () {
     if (this.contentCategoryId) {
       this.loadContentCategory()
     }
-  },
-  mounted () {
+    this.getContentCategories()
+    this.getContents({ selected_category: this.contentCategoryId })
     this.mounted = true
   },
   methods: {
@@ -207,6 +201,45 @@ export default {
       }
 
       this.$store.commit('AppLayout/updateBreadcrumbs', breadcrumbs)
+    },
+
+    getFilterOfCategorySearch () {
+      const filter = {}
+      if (this.localOptions.listType === 'category_parent_parent') {
+        filter.parent__isnull = true
+      } else if (this.localOptions.listType === 'category_parent') {
+        filter.parent__isnull = null
+        filter.parent = this.contentCategoryId
+      } else if (this.localOptions.listType === 'category') {
+        filter.parent__isnull = null
+        filter.parent = this.contentCategoryId
+      }
+
+      return filter
+    },
+    getContents (filters) {
+      this.contents.loading = true
+      APIGateway.content.index({ ...filters, per_page: 9999 })
+        .then(({ list }) => {
+          this.contents = new ContentList(list)
+        })
+        .finally(() => {
+          this.contents.loading = false
+        })
+    },
+    getContentCategories () {
+      const filter = this.getFilterOfCategorySearch()
+      this.contentCategories.loading = true
+      APIGateway.contentCategory.index({ ...filter, per_page: 9999 })
+        .then(({ list }) => {
+          this.contentCategories = new ContentCategoryList(list)
+        })
+        .finally(() => {
+          this.contentCategories.loading = false
+        })
+    },
+    onChangeFilter (newFilter) {
+      this.getContents({ ...newFilter, selected_category: this.contentCategoryId })
     }
   }
 }

+ 125 - 0
src/components/Widgets/ContentCategory/ContentCategoryList/ContentItem.vue

@@ -0,0 +1,125 @@
+<template>
+  <q-card class="ContentItem"
+          flat>
+    <div class="thumbnail"
+         @click="goToContentPageShow">
+      <q-img :src="content.thumbnail" />
+    </div>
+    <div class="title ellipsis-2-lines"
+         @click="goToContentPageShow">
+      {{ content.title }}
+    </div>
+    <div class="categories">
+      <q-btn v-if="content.category_info.id"
+             flat
+             class="category-btn">
+        <div class="category-btn-title q-px-sm">
+          {{ content.category_info.title }}
+        </div>
+      </q-btn>
+      <q-btn v-if="content.category_info.parent.id"
+             flat
+             class="category-btn">
+        <div class="category-btn-title q-px-sm">
+          {{ content.category_info.parent.title }}
+        </div>
+      </q-btn>
+      <q-btn v-if="content.category_info.parent.parent.id"
+             flat
+             class="category-btn">
+        <div class="category-btn-title q-px-sm">
+          {{ content.category_info.parent.parent.title }}
+        </div>
+      </q-btn>
+    </div>
+    <q-btn color="primary"
+           class="show-content-btn"
+           :to="{name: 'Public.Content.Show', params: {id: content.id}}">
+      مشاهده
+    </q-btn>
+  </q-card>
+</template>
+
+<script>
+import { Content } from 'src/models/Content.js'
+
+export default {
+  name: 'ContentItem',
+  props: {
+    content: {
+      type: Content,
+      default: new Content()
+    }
+  },
+  methods: {
+    goToContentPageShow () {
+      this.$router.push({ name: 'Public.Content.Show', params: { id: this.content.id } })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ContentItem {
+  background-color: #f6f6f6;
+  display: flex;
+  flex-flow: column;
+  position: relative;
+  cursor: pointer;
+  padding: 16px;
+  margin: 8px;
+  border-radius: 16px;
+  .thumbnail {
+    margin-bottom: 16px;
+    .q-img {
+      width: 100%;
+      border-radius: 8px;
+    }
+  }
+  .title {
+    text-align: center;
+    margin-bottom: 16px;
+    font-size: 16px;
+  }
+  .description {
+    position: absolute;
+    left: 21px;
+    color: #FFFFFF;
+    font-size: 16px;
+    display: none;
+  }
+  .show-content-btn {
+    width: 100%;
+    border-top-right-radius: 0;
+    border-top-left-radius: 0;
+    height: 60px;
+  }
+  .categories {
+    display: flex;
+    gap: 4px;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+    margin-bottom: 16px;
+    .category-btn {
+      border: 1px solid #000000;
+      border-radius: 20px;
+      padding: 4px;
+      display: flex;
+      justify-content: space-between;
+      gap: 4px;
+      align-items: center;
+      background: transparent;
+      .category-btn-title {
+        font-size: 12px;
+        color: #212121;
+      }
+      &.category-btn__selected {
+        background: #444444;
+        .category-btn-title {
+          color: white;
+        }
+      }
+    }
+  }
+}
+</style>

+ 183 - 0
src/components/Widgets/ContentCategory/ContentCategoryList/filters.vue

@@ -0,0 +1,183 @@
+<template>
+  <div class="filters">
+    <div class="main-filters">
+      <div class="contentCategories">
+        <q-btn v-if="contentCategories.list.length > 0"
+               flat
+               class="filter-btn contentCategory filter-btn__selected">
+          <div class="filter-btn-title">
+            همه
+          </div>
+        </q-btn>
+        <q-btn v-for="(contentCategory, contentCategoryIndex) in contentCategories.list"
+               :key="contentCategoryIndex"
+               flat
+               :to="{name: showCategoryRouteName, params: {category_id: contentCategory.id}}"
+               class="filter-btn contentCategory">
+          <div class="filter-btn-title">
+            {{ contentCategory.title }}
+          </div>
+        </q-btn>
+      </div>
+      <div class="content-type-filters">
+        <q-btn flat
+               class="filter-btn"
+               :class="{'filter-btn__selected': subFilterExpanded}"
+               @click="subFilterExpanded = !subFilterExpanded">
+          <div class="filter-btn-title q-px-sm">
+            <q-icon name="filter_alt" />
+          </div>
+        </q-btn>
+      </div>
+    </div>
+    <div class="sub-filters">
+      <q-expansion-item v-model="subFilterExpanded"
+                        :header-style="'display: none;'">
+        <q-card class="sub-filters-card">
+          <div class="media-types">
+            <q-btn v-for="(mediaType, mediaTypeIndex) in mediaTypes"
+                   :key="mediaTypeIndex"
+                   flat
+                   class="filter-btn contentCategory"
+                   :class="{'filter-btn__selected': selectedMediaType === mediaType.value}"
+                   @click="filterMediaType(mediaType.value)">
+              <div class="filter-btn-title">
+                {{ mediaType.label }}
+              </div>
+            </q-btn>
+          </div>
+          <div class="search-input">
+            <q-input v-model="searchText"
+                     debounce="500">
+              <template #prepend>
+                <q-icon name="search" />
+              </template>
+            </q-input>
+          </div>
+        </q-card>
+      </q-expansion-item>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Media } from 'src/models/Media.js'
+import { ContentCategory, ContentCategoryList } from 'src/models/ContentCategory.js'
+
+export default {
+  name: 'ContentCategoryItem',
+  props: {
+    contentCategories: {
+      type: ContentCategoryList,
+      default: new ContentCategoryList()
+    },
+    contentCategory: {
+      type: ContentCategory,
+      default: new ContentCategory()
+    },
+    showCategoryRouteName: {
+      type: String,
+      default: 'Public.ContentCategory.Show'
+    },
+    listType: {
+      type: String,
+      default: 'category_parent_parent'
+    }
+  },
+  emits: ['filter'],
+  data () {
+    return {
+      media: new Media(),
+      searchText: null,
+      selectedMediaType: null,
+      subFilterExpanded: false
+    }
+  },
+  computed: {
+    mediaTypes () {
+      return [{
+        label: 'همه',
+        value: null
+      }].concat(this.media.typeEnums)
+    }
+  },
+  watch: {
+    searchText () {
+      this.emitFilters()
+    }
+  },
+  methods: {
+    filterMediaType (mediaType) {
+      this.selectedMediaType = mediaType
+      this.emitFilters()
+    },
+    emitFilters () {
+      this.$emit('filter', this.getFilterObject())
+    },
+    getFilterObject () {
+      return {
+        medias__type: this.selectedMediaType,
+        search: this.searchText
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.filters {
+  .main-filters {
+    display: flex;
+    gap: 8px;
+    flex-wrap: wrap;
+    justify-content: space-between;
+
+    .contentCategories {
+      display: flex;
+      gap: 8px;
+      flex-wrap: wrap;
+      justify-content: flex-start;
+    }
+    .content-type-filters {
+
+    }
+  }
+  .sub-filters {
+    .sub-filters-card {
+      display: flex;
+      gap: 8px;
+      flex-wrap: wrap;
+      align-items: center;
+      justify-content: space-between;
+      padding-top: 12px;
+      .media-types {
+        display: flex;
+        gap: 8px;
+        flex-wrap: wrap;
+        justify-content: flex-start;
+        height: 42.63px;
+      }
+    }
+  }
+  .filter-btn {
+    border: 1px solid #000000;
+    border-radius: 20px;
+    padding: 8px;
+    display: flex;
+    justify-content: space-between;
+    gap: 8px;
+    align-items: center;
+    background: #FFFFFF;
+    .filter-btn-title {
+      font-size: 14px;
+      color: #212121;
+    }
+    &.filter-btn__selected {
+      background: #444444;
+      .filter-btn-title {
+        color: white;
+      }
+    }
+  }
+}
+</style>

+ 10 - 0
src/css/Theme/Controls/input.scss

@@ -1,5 +1,15 @@
 .q-field {
   $input-height: 48px;
+  .q-field__control {
+    height: $input-height;
+    .q-field__marginal {
+      height: $input-height;
+    }
+    .q-field__prepend {
+      padding-left: 12px;
+      padding-right: 0;
+    }
+  }
   &.q-field--outlined {
     .q-field__inner {
       .q-field__control {

+ 4 - 0
src/models/Media.js

@@ -54,6 +54,10 @@ class Media extends Model {
           {
             label: 'سند',
             value: 'DOCUMENT'
+          },
+          {
+            label: 'سایر',
+            value: 'OTHER'
           }
         ]
       },