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

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

 Conflicts:
	src/components/Template/Header/MainHeader.vue
	src/components/Template/SideBar/Main.vue
HgH 1 год назад
Родитель
Сommit
41bef3f197
100 измененных файлов с 11347 добавлено и 552 удалено
  1. 1 1
      package.json
  2. 4 0
      src/api/APIGateway.js
  3. 32 0
      src/api/models/Aggregator.js
  4. 100 0
      src/api/models/AppSetting.js
  5. 2 1
      src/api/models/Basket.js
  6. 34 1
      src/api/models/Product.js
  7. 2 2
      src/assets/ShamsiDate.js
  8. 6915 0
      src/assets/js/IranianCities.js
  9. 5 1
      src/components/FormBuilderCustumComponents/ContentCategorySelector.vue
  10. 15 14
      src/components/FormBuilderCustumComponents/ContentMedias/ContentMedias.vue
  11. 36 9
      src/components/FormBuilderCustumComponents/ProductSelector.vue
  12. 1 1
      src/components/InvoicePaymentCard/InvoicePaymentCard.vue
  13. 2 2
      src/components/Template/Header/MainHeader.vue
  14. 14 0
      src/components/Template/SideBar/AdminPanelSideBar.vue
  15. 2 2
      src/components/Template/SideBar/Main.vue
  16. 1 0
      src/components/Widgets/Content/AdminContentCreate/AdminContentCreate.vue
  17. 9 0
      src/components/Widgets/Content/AdminContentList/AdminContentList.vue
  18. 1 0
      src/components/Widgets/Content/AdminContentShow/AdminContentShow.vue
  19. 158 0
      src/components/Widgets/Content/ContentCarousel/ContentCarousel.vue
  20. 74 0
      src/components/Widgets/Content/ContentCarousel/OptionPanel.vue
  21. 128 0
      src/components/Widgets/Content/ContentCarousel/componetns/ContentItem.vue
  22. 1 1
      src/components/Widgets/Content/ContentShow/components/productItem.vue
  23. 2 2
      src/components/Widgets/Content/ContentTopicList/components/packageItem.vue
  24. 2 2
      src/components/Widgets/Content/ContentTopicList/components/productItem.vue
  25. 90 0
      src/components/Widgets/Content/LastContentTopics/LastContentTopics.vue
  26. 0 0
      src/components/Widgets/Content/LastContentTopics/OptionPanel.vue
  27. 84 0
      src/components/Widgets/Content/LastContentTopics/TopicItem.vue
  28. 0 61
      src/components/Widgets/Content/LastContents/LastContents.vue
  29. 0 98
      src/components/Widgets/Content/LastContents/PostItem.vue
  30. 152 0
      src/components/Widgets/Donation/DonationIntro/DonationIntro.vue
  31. 100 0
      src/components/Widgets/Donation/DonationIntro/OptionPanel.vue
  32. 128 0
      src/components/Widgets/Donation/DonationIntro/componetns/ContentItem.vue
  33. 86 22
      src/components/Widgets/Donation/DonationShow/DonationShow.vue
  34. 1 1
      src/components/Widgets/Event/LastEvents/LastEvents.vue
  35. 28 4
      src/components/Widgets/FeatureBox/OptionPanel.vue
  36. 1 5
      src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue
  37. 179 5
      src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue
  38. 4 2
      src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue
  39. 1 1
      src/components/Widgets/Other/ClassroomCarousel/ClassroomCarousel.vue
  40. 197 4
      src/components/Widgets/Other/Shop/ShopCompleteInfo/ShopCompleteInfo.vue
  41. 192 0
      src/components/Widgets/Post/LastPosts/LastPosts.vue
  42. 65 0
      src/components/Widgets/Post/LastPosts/OptionPanel.vue
  43. 149 0
      src/components/Widgets/Post/LastPosts/components/PostItem.vue
  44. 6 0
      src/components/Widgets/Post/PostShow/PostShow.vue
  45. 1 1
      src/components/Widgets/ProjectAttendanceSheet/ProjectAttendanceSheetShow/ProjectAttendanceSheetShow.vue
  46. 1 1
      src/components/Widgets/Registration/RegistrationShow/RegistrationShow.vue
  47. 48 33
      src/components/Widgets/Statictic/PublicWebStatistic/PublicWebStatistic.vue
  48. 68 0
      src/components/Widgets/Statictic/PublicWebStatistic/components/Item.vue
  49. 53 10
      src/components/Widgets/Store/Basket/AdminStoreBasketList/AdminStoreBasketList.vue
  50. 135 0
      src/components/Widgets/Store/Basket/AdminStoreBasketPurchaseRegulations/AdminStoreBasketPurchaseRegulations.vue
  51. 21 0
      src/components/Widgets/Store/Basket/AdminStoreBasketPurchaseRegulations/OptionPanel.vue
  52. 56 11
      src/components/Widgets/Store/Basket/AdminStoreBasketShow/AdminStoreBasketShow.vue
  53. 113 0
      src/components/Widgets/Store/Basket/AdminStoreDeliveryCost/AdminStoreDeliveryCost.vue
  54. 21 0
      src/components/Widgets/Store/Basket/AdminStoreDeliveryCost/OptionPanel.vue
  55. 5 5
      src/components/Widgets/Store/Basket/UserStoreBasketList/UserStoreBasketList.vue
  56. 57 0
      src/components/Widgets/Store/Basket/UserStoreBasketShow/ProductOrPackageMedias.vue
  57. 56 14
      src/components/Widgets/Store/Basket/UserStoreBasketShow/UserStoreBasketShow.vue
  58. 2 2
      src/components/Widgets/Store/Cart/components/packageItem.vue
  59. 2 2
      src/components/Widgets/Store/Cart/components/productItem.vue
  60. 24 5
      src/components/Widgets/Store/Cart/components/step1/cartItem.vue
  61. 5 5
      src/components/Widgets/Store/Cart/components/step1/sidebar.vue
  62. 51 3
      src/components/Widgets/Store/Cart/components/step2/cartAddress.vue
  63. 28 4
      src/components/Widgets/Store/Cart/components/step2/deliveryInfo.vue
  64. 33 6
      src/components/Widgets/Store/Cart/components/step2/sidebar.vue
  65. 2 2
      src/components/Widgets/Store/Cart/components/step2/step2.vue
  66. 2 2
      src/components/Widgets/Store/Shop/Shop.vue
  67. 50 27
      src/components/Widgets/Store/Shop/components/packageItem.vue
  68. 48 27
      src/components/Widgets/Store/Shop/components/productItem.vue
  69. 3 3
      src/components/Widgets/Store/Shop/components/sidebar.vue
  70. 9 8
      src/components/Widgets/Store/StorePackage/AdminStorePackageCreate/AdminStorePackageCreate.vue
  71. 56 3
      src/components/Widgets/Store/StorePackage/AdminStorePackageList/AdminStorePackageList.vue
  72. 3 2
      src/components/Widgets/Store/StorePackage/AdminStorePackageShow/AdminStorePackageShow.vue
  73. 114 44
      src/components/Widgets/Store/StorePackage/StorePackageShow/StorePackageShow.vue
  74. 4 3
      src/components/Widgets/Store/StorePackage/StorePackageShow/components/productItem.vue
  75. 4 3
      src/components/Widgets/Store/StoreProduct/AdminStoreProductCreate/AdminStoreProductCreate.vue
  76. 55 2
      src/components/Widgets/Store/StoreProduct/AdminStoreProductList/AdminStoreProductList.vue
  77. 3 3
      src/components/Widgets/Store/StoreProduct/AdminStoreProductShow/AdminStoreProductShow.vue
  78. 1 1
      src/components/Widgets/Store/StoreProduct/ProductCarousel/ProductCarousel.vue
  79. 49 27
      src/components/Widgets/Store/StoreProduct/ProductCarousel/productItem.vue
  80. 88 16
      src/components/Widgets/Store/StoreProduct/StoreProductShow/StoreProductShow.vue
  81. 1 1
      src/components/Widgets/Store/StoreProduct/StoreProductShow/components/productItem.vue
  82. 21 0
      src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/OptionPanel.vue
  83. 160 0
      src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/StoreRelatedProducts.vue
  84. 248 0
      src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/components/productItem.vue
  85. 56 3
      src/components/Widgets/Team/AdminTeamShow/AdminTeamShow.vue
  86. 30 5
      src/components/Widgets/TextWidget/OptionPanel.vue
  87. 1 1
      src/components/Widgets/User/Profile/Profile.vue
  88. 9 5
      src/components/cart/cartCountAction/cartCountAction.vue
  89. 1 0
      src/css/Theme/Controls/carousel.scss
  90. 1 1
      src/css/Theme/Widgets/widgets.scss
  91. 19 0
      src/models/Basket.js
  92. 21 1
      src/models/Content.js
  93. 5 0
      src/models/Invoice.js
  94. 29 1
      src/models/Package.js
  95. 1 0
      src/models/Post.js
  96. 30 2
      src/models/Product.js
  97. 23 0
      src/pages/Admin/StoreBasket/DeliveryCost.vue
  98. 23 0
      src/pages/Admin/StoreBasket/PurchaseRegulations.vue
  99. 193 10
      src/pages/Admin/User/show.vue
  100. 200 5
      src/pages/User/Profile/profile.vue

+ 1 - 1
package.json

@@ -56,7 +56,7 @@
     "vue-router": "^4.1.6",
     "vue-tiptap-katex-core": "^2.1.5",
     "vue3-otp-input": "^0.4.1",
-    "vue3-tiptap-katex": "^1.1.27",
+    "vue3-tiptap-katex": "^1.1.31",
     "vuex": "^4.1.0",
     "vuex-persistedstate": "^4.1.0",
     "workbox-build": "^7.0.0",

+ 4 - 0
src/api/APIGateway.js

@@ -25,6 +25,8 @@ import DonationAPI from 'src/api/models/Donation.js'
 import QuestionApi from 'src/api/models/Question.js'
 import DiscountAPI from 'src/api/models/Discount.js'
 import ClassroomAPI from 'src/api/models/Classroom.js'
+import AggregatorAPI from 'src/api/models/Aggregator.js'
+import AppSettingAPI from 'src/api/models/AppSetting.js'
 import AnswerBookApi from 'src/api/models/AnswerBook.js'
 import InstalmentAPI from 'src/api/models/Instalment.js'
 import BasketItemAPI from 'src/api/models/BasketItem.js'
@@ -84,6 +86,8 @@ export const APIGateway = {
   question: new QuestionApi(),
   discount: new DiscountAPI(),
   classroom: new ClassroomAPI(),
+  aggregator: new AggregatorAPI(),
+  appSetting: new AppSettingAPI(),
   answerBook: new AnswerBookApi(),
   instalment: new InstalmentAPI(),
   basketItem: new BasketItemAPI(),

+ 32 - 0
src/api/models/Aggregator.js

@@ -0,0 +1,32 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+
+export default class AggregatorAPI extends APIRepository {
+  constructor() {
+    super('aggregator', appApi)
+    this.APIAdresses = {
+      statistics: '/rma/aggregator/statistics'
+    }
+    this.CacheList = {
+      statistics: this.name + this.APIAdresses.statistics
+    }
+  }
+
+  statistics() {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.statistics,
+      resolveCallback: (response) => {
+        return {
+          users_count: response.data.users_count, // کاربر فعال
+          training_classrooms_count: response.data.training_classrooms_count, // دوره
+          event_classrooms_count: response.data.event_classrooms_count // رویداد
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 100 - 0
src/api/models/AppSetting.js

@@ -0,0 +1,100 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+
+export default class PageSettingAPI extends APIRepository {
+  constructor() {
+    super('page-setting', appApi)
+    this.APIAdresses = {
+      base: '/cma/settings',
+      getWithKey: (key) => '/cma/settings/key/' + key,
+      updateWithKey: (key) => '/admin/setting/' + key
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base,
+      getWithKey: (key) => this.name + this.APIAdresses.getWithKey(key)
+    }
+  }
+
+  index (data, cache = { TTL: 1000 }) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      cacheKey: this.CacheList.base,
+      ...(cache !== undefined && { cache }),
+      resolveCallback: (response) => {
+        return {
+          referralCodeList: response.data.data,
+          paginate: response.data.meta
+          // {
+          //   current_page: 1,
+          //   from: 1,
+          //   last_page: 1,
+          //   path: '...',
+          //   per_page: 15,
+          //   to: 10,
+          //   total: 10
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  create (data) {
+    const formData = new FormData()
+    formData.append('key', data.key)
+    formData.append('value', data.value)
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: formData,
+      resolveCallback: (response) => {
+        return response.data
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  update (data) {
+    // const settingKey = encodeURI(data.key)
+    const settingKey = data.key
+    const settingValue = data.value
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: {
+        key: settingKey,
+        value: settingValue
+      },
+      resolveCallback: (response) => {
+        return response
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  get (data, cache = { TTL: 1000 }) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.getWithKey(encodeURI(data)),
+      cacheKey: this.CacheList.getWithKey(encodeURI(data)),
+      ...(cache !== undefined && { cache }),
+      resolveCallback: (response) => {
+        return response.data
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 2 - 1
src/api/models/Basket.js

@@ -12,6 +12,7 @@ export default class BasketAPI extends APIRepository {
       listOrders: '/store/baskets/list_orders',
       byId: (id) => '/store/baskets/' + id,
       send: (id) => '/store/baskets/' + id + '/send',
+      setAddress: (id) => '/store/baskets/' + id + '/set_address',
       createInvoice: (id) => '/store/baskets/' + id + '/create_invoice',
       submitDiscountCode: (id) => '/store/baskets/' + id + '/submit_discount_code',
       removeDiscountCode: (id) => '/store/baskets/' + id + '/remove_discount_code'
@@ -127,7 +128,7 @@ export default class BasketAPI extends APIRepository {
     return this.sendRequest({
       apiMethod: 'put',
       api: this.api,
-      request: this.APIAdresses.byId(basketId),
+      request: this.APIAdresses.setAddress(basketId),
       data: this.getNormalizedSendData({
         province: null, // String
         city: null, // String

+ 34 - 1
src/api/models/Product.js

@@ -7,7 +7,8 @@ export default class ProductAPI extends APIRepository {
     super('product', appApi)
     this.APIAdresses = {
       base: '/store/products',
-      byId: (id) => '/store/products/' + id
+      byId: (id) => '/store/products/' + id,
+      relatedProducts: (id) => '/store/products/' + id + '/related_products'
     }
     this.CacheList = {
       base: this.name + this.APIAdresses.base
@@ -69,6 +70,38 @@ export default class ProductAPI extends APIRepository {
     })
   }
 
+  relatedProducts(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.relatedProducts(data.id),
+      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 ProductList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
   add (data) {
     return this.sendRequest({
       apiMethod: 'post',

+ 2 - 2
src/assets/ShamsiDate.js

@@ -15,8 +15,8 @@ class ShamsiDate {
     return moment(dateTime, 'YYYY-M-D HH:mm:ss').format('HH:mm:ss jYYYY/jMM/jDD')
   }
 
-  static getTitledDateTime (dateTime) {
-    return moment(dateTime, 'YYYY-M-D HH:mm:ss').format('jD jMMMM jYYYY')
+  static getTitledDateTime (dateTime, format = 'jD jMMMM jYYYY') {
+    return moment(dateTime, 'YYYY-M-D HH:mm:ss').format(format)
   }
 }
 

+ 6915 - 0
src/assets/js/IranianCities.js

@@ -0,0 +1,6915 @@
+// https://github.com/sajaddp/list-of-cities-in-Iran
+
+export const Cities = [
+  {
+    id: 1,
+    name: 'اسکو',
+    slug: 'اسکو',
+    province_id: 1
+  },
+  {
+    id: 2,
+    name: 'اهر',
+    slug: 'اهر',
+    province_id: 1
+  },
+  {
+    id: 3,
+    name: 'ایلخچی',
+    slug: 'ایلخچی',
+    province_id: 1
+  },
+  {
+    id: 4,
+    name: 'آبش احمد',
+    slug: 'آبش-احمد',
+    province_id: 1
+  },
+  {
+    id: 5,
+    name: 'آذرشهر',
+    slug: 'آذرشهر',
+    province_id: 1
+  },
+  {
+    id: 6,
+    name: 'آقکند',
+    slug: 'آقکند',
+    province_id: 1
+  },
+  {
+    id: 7,
+    name: 'باسمنج',
+    slug: 'باسمنج',
+    province_id: 1
+  },
+  {
+    id: 8,
+    name: 'بخشایش',
+    slug: 'بخشایش',
+    province_id: 1
+  },
+  {
+    id: 9,
+    name: 'بستان آباد',
+    slug: 'بستان-آباد',
+    province_id: 1
+  },
+  {
+    id: 10,
+    name: 'بناب',
+    slug: 'بناب',
+    province_id: 1
+  },
+  {
+    id: 11,
+    name: 'بناب جدید',
+    slug: 'بناب-جدید',
+    province_id: 1
+  },
+  {
+    id: 12,
+    name: 'تبریز',
+    slug: 'تبریز',
+    province_id: 1
+  },
+  {
+    id: 13,
+    name: 'ترک',
+    slug: 'ترک',
+    province_id: 1
+  },
+  {
+    id: 14,
+    name: 'ترکمانچای',
+    slug: 'ترکمانچای',
+    province_id: 1
+  },
+  {
+    id: 15,
+    name: 'تسوج',
+    slug: 'تسوج',
+    province_id: 1
+  },
+  {
+    id: 16,
+    name: 'تیکمه داش',
+    slug: 'تیکمه-داش',
+    province_id: 1
+  },
+  {
+    id: 17,
+    name: 'جلفا',
+    slug: 'جلفا',
+    province_id: 1
+  },
+  {
+    id: 18,
+    name: 'خاروانا',
+    slug: 'خاروانا',
+    province_id: 1
+  },
+  {
+    id: 19,
+    name: 'خامنه',
+    slug: 'خامنه',
+    province_id: 1
+  },
+  {
+    id: 20,
+    name: 'خراجو',
+    slug: 'خراجو',
+    province_id: 1
+  },
+  {
+    id: 21,
+    name: 'خسروشهر',
+    slug: 'خسروشهر',
+    province_id: 1
+  },
+  {
+    id: 22,
+    name: 'خضرلو',
+    slug: 'خضرلو',
+    province_id: 1
+  },
+  {
+    id: 23,
+    name: 'خمارلو',
+    slug: 'خمارلو',
+    province_id: 1
+  },
+  {
+    id: 24,
+    name: 'خواجه',
+    slug: 'خواجه',
+    province_id: 1
+  },
+  {
+    id: 25,
+    name: 'دوزدوزان',
+    slug: 'دوزدوزان',
+    province_id: 1
+  },
+  {
+    id: 26,
+    name: 'زرنق',
+    slug: 'زرنق',
+    province_id: 1
+  },
+  {
+    id: 27,
+    name: 'زنوز',
+    slug: 'زنوز',
+    province_id: 1
+  },
+  {
+    id: 28,
+    name: 'سراب',
+    slug: 'سراب',
+    province_id: 1
+  },
+  {
+    id: 29,
+    name: 'سردرود',
+    slug: 'سردرود',
+    province_id: 1
+  },
+  {
+    id: 30,
+    name: 'سهند',
+    slug: 'سهند',
+    province_id: 1
+  },
+  {
+    id: 31,
+    name: 'سیس',
+    slug: 'سیس',
+    province_id: 1
+  },
+  {
+    id: 32,
+    name: 'سیه رود',
+    slug: 'سیه-رود',
+    province_id: 1
+  },
+  {
+    id: 33,
+    name: 'شبستر',
+    slug: 'شبستر',
+    province_id: 1
+  },
+  {
+    id: 34,
+    name: 'شربیان',
+    slug: 'شربیان',
+    province_id: 1
+  },
+  {
+    id: 35,
+    name: 'شرفخانه',
+    slug: 'شرفخانه',
+    province_id: 1
+  },
+  {
+    id: 36,
+    name: 'شندآباد',
+    slug: 'شندآباد',
+    province_id: 1
+  },
+  {
+    id: 37,
+    name: 'صوفیان',
+    slug: 'صوفیان',
+    province_id: 1
+  },
+  {
+    id: 38,
+    name: 'عجب شیر',
+    slug: 'عجب-شیر',
+    province_id: 1
+  },
+  {
+    id: 39,
+    name: 'قره آغاج',
+    slug: 'قره-آغاج',
+    province_id: 1
+  },
+  {
+    id: 40,
+    name: 'کشکسرای',
+    slug: 'کشکسرای',
+    province_id: 1
+  },
+  {
+    id: 41,
+    name: 'کلوانق',
+    slug: 'کلوانق',
+    province_id: 1
+  },
+  {
+    id: 42,
+    name: 'کلیبر',
+    slug: 'کلیبر',
+    province_id: 1
+  },
+  {
+    id: 43,
+    name: 'کوزه کنان',
+    slug: 'کوزه-کنان',
+    province_id: 1
+  },
+  {
+    id: 44,
+    name: 'گوگان',
+    slug: 'گوگان',
+    province_id: 1
+  },
+  {
+    id: 45,
+    name: 'لیلان',
+    slug: 'لیلان',
+    province_id: 1
+  },
+  {
+    id: 46,
+    name: 'مراغه',
+    slug: 'مراغه',
+    province_id: 1
+  },
+  {
+    id: 47,
+    name: 'مرند',
+    slug: 'مرند',
+    province_id: 1
+  },
+  {
+    id: 48,
+    name: 'ملکان',
+    slug: 'ملکان',
+    province_id: 1
+  },
+  {
+    id: 49,
+    name: 'ملک کیان',
+    slug: 'ملک-کیان',
+    province_id: 1
+  },
+  {
+    id: 50,
+    name: 'ممقان',
+    slug: 'ممقان',
+    province_id: 1
+  },
+  {
+    id: 51,
+    name: 'مهربان',
+    slug: 'مهربان',
+    province_id: 1
+  },
+  {
+    id: 52,
+    name: 'میانه',
+    slug: 'میانه',
+    province_id: 1
+  },
+  {
+    id: 53,
+    name: 'نظرکهریزی',
+    slug: 'نظرکهریزی',
+    province_id: 1
+  },
+  {
+    id: 54,
+    name: 'هادی شهر',
+    slug: 'هادی-شهر',
+    province_id: 1
+  },
+  {
+    id: 55,
+    name: 'هرگلان',
+    slug: 'هرگلان',
+    province_id: 1
+  },
+  {
+    id: 56,
+    name: 'هریس',
+    slug: 'هریس',
+    province_id: 1
+  },
+  {
+    id: 57,
+    name: 'هشترود',
+    slug: 'هشترود',
+    province_id: 1
+  },
+  {
+    id: 58,
+    name: 'هوراند',
+    slug: 'هوراند',
+    province_id: 1
+  },
+  {
+    id: 59,
+    name: 'وایقان',
+    slug: 'وایقان',
+    province_id: 1
+  },
+  {
+    id: 60,
+    name: 'ورزقان',
+    slug: 'ورزقان',
+    province_id: 1
+  },
+  {
+    id: 61,
+    name: 'یامچی',
+    slug: 'یامچی',
+    province_id: 1
+  },
+  {
+    id: 62,
+    name: 'ارومیه',
+    slug: 'ارومیه',
+    province_id: 2
+  },
+  {
+    id: 63,
+    name: 'اشنویه',
+    slug: 'اشنویه',
+    province_id: 2
+  },
+  {
+    id: 64,
+    name: 'ایواوغلی',
+    slug: 'ایواوغلی',
+    province_id: 2
+  },
+  {
+    id: 65,
+    name: 'آواجیق',
+    slug: 'آواجیق',
+    province_id: 2
+  },
+  {
+    id: 66,
+    name: 'باروق',
+    slug: 'باروق',
+    province_id: 2
+  },
+  {
+    id: 67,
+    name: 'بازرگان',
+    slug: 'بازرگان',
+    province_id: 2
+  },
+  {
+    id: 68,
+    name: 'بوکان',
+    slug: 'بوکان',
+    province_id: 2
+  },
+  {
+    id: 69,
+    name: 'پلدشت',
+    slug: 'پلدشت',
+    province_id: 2
+  },
+  {
+    id: 70,
+    name: 'پیرانشهر',
+    slug: 'پیرانشهر',
+    province_id: 2
+  },
+  {
+    id: 71,
+    name: 'تازه شهر',
+    slug: 'تازه-شهر',
+    province_id: 2
+  },
+  {
+    id: 72,
+    name: 'تکاب',
+    slug: 'تکاب',
+    province_id: 2
+  },
+  {
+    id: 73,
+    name: 'چهاربرج',
+    slug: 'چهاربرج',
+    province_id: 2
+  },
+  {
+    id: 74,
+    name: 'خوی',
+    slug: 'خوی',
+    province_id: 2
+  },
+  {
+    id: 75,
+    name: 'دیزج دیز',
+    slug: 'دیزج-دیز',
+    province_id: 2
+  },
+  {
+    id: 76,
+    name: 'ربط',
+    slug: 'ربط',
+    province_id: 2
+  },
+  {
+    id: 77,
+    name: 'سردشت',
+    slug: 'آذربایجان-غربی-سردشت',
+    province_id: 2
+  },
+  {
+    id: 78,
+    name: 'سرو',
+    slug: 'سرو',
+    province_id: 2
+  },
+  {
+    id: 79,
+    name: 'سلماس',
+    slug: 'سلماس',
+    province_id: 2
+  },
+  {
+    id: 80,
+    name: 'سیلوانه',
+    slug: 'سیلوانه',
+    province_id: 2
+  },
+  {
+    id: 81,
+    name: 'سیمینه',
+    slug: 'سیمینه',
+    province_id: 2
+  },
+  {
+    id: 82,
+    name: 'سیه چشمه',
+    slug: 'سیه-چشمه',
+    province_id: 2
+  },
+  {
+    id: 83,
+    name: 'شاهین دژ',
+    slug: 'شاهین-دژ',
+    province_id: 2
+  },
+  {
+    id: 84,
+    name: 'شوط',
+    slug: 'شوط',
+    province_id: 2
+  },
+  {
+    id: 85,
+    name: 'فیرورق',
+    slug: 'فیرورق',
+    province_id: 2
+  },
+  {
+    id: 86,
+    name: 'قره ضیاءالدین',
+    slug: 'قره-ضیاءالدین',
+    province_id: 2
+  },
+  {
+    id: 87,
+    name: 'قطور',
+    slug: 'قطور',
+    province_id: 2
+  },
+  {
+    id: 88,
+    name: 'قوشچی',
+    slug: 'قوشچی',
+    province_id: 2
+  },
+  {
+    id: 89,
+    name: 'کشاورز',
+    slug: 'کشاورز',
+    province_id: 2
+  },
+  {
+    id: 90,
+    name: 'گردکشانه',
+    slug: 'گردکشانه',
+    province_id: 2
+  },
+  {
+    id: 91,
+    name: 'ماکو',
+    slug: 'ماکو',
+    province_id: 2
+  },
+  {
+    id: 92,
+    name: 'محمدیار',
+    slug: 'محمدیار',
+    province_id: 2
+  },
+  {
+    id: 93,
+    name: 'محمودآباد',
+    slug: 'آذربایجان-غربی-محمودآباد',
+    province_id: 2
+  },
+  {
+    id: 94,
+    name: 'مهاباد',
+    slug: 'آذربایجان-غربی-مهاباد',
+    province_id: 2
+  },
+  {
+    id: 95,
+    name: 'میاندوآب',
+    slug: 'میاندوآب',
+    province_id: 2
+  },
+  {
+    id: 96,
+    name: 'میرآباد',
+    slug: 'میرآباد',
+    province_id: 2
+  },
+  {
+    id: 97,
+    name: 'نالوس',
+    slug: 'نالوس',
+    province_id: 2
+  },
+  {
+    id: 98,
+    name: 'نقده',
+    slug: 'نقده',
+    province_id: 2
+  },
+  {
+    id: 99,
+    name: 'نوشین',
+    slug: 'نوشین',
+    province_id: 2
+  },
+  {
+    id: 100,
+    name: 'اردبیل',
+    slug: 'اردبیل',
+    province_id: 3
+  },
+  {
+    id: 101,
+    name: 'اصلاندوز',
+    slug: 'اصلاندوز',
+    province_id: 3
+  },
+  {
+    id: 102,
+    name: 'آبی بیگلو',
+    slug: 'آبی-بیگلو',
+    province_id: 3
+  },
+  {
+    id: 103,
+    name: 'بیله سوار',
+    slug: 'بیله-سوار',
+    province_id: 3
+  },
+  {
+    id: 104,
+    name: 'پارس آباد',
+    slug: 'پارس-آباد',
+    province_id: 3
+  },
+  {
+    id: 105,
+    name: 'تازه کند',
+    slug: 'تازه-کند',
+    province_id: 3
+  },
+  {
+    id: 106,
+    name: 'تازه کندانگوت',
+    slug: 'تازه-کندانگوت',
+    province_id: 3
+  },
+  {
+    id: 107,
+    name: 'جعفرآباد',
+    slug: 'جعفرآباد',
+    province_id: 3
+  },
+  {
+    id: 108,
+    name: 'خلخال',
+    slug: 'خلخال',
+    province_id: 3
+  },
+  {
+    id: 109,
+    name: 'رضی',
+    slug: 'رضی',
+    province_id: 3
+  },
+  {
+    id: 110,
+    name: 'سرعین',
+    slug: 'سرعین',
+    province_id: 3
+  },
+  {
+    id: 111,
+    name: 'عنبران',
+    slug: 'عنبران',
+    province_id: 3
+  },
+  {
+    id: 112,
+    name: 'فخرآباد',
+    slug: 'فخرآباد',
+    province_id: 3
+  },
+  {
+    id: 113,
+    name: 'کلور',
+    slug: 'کلور',
+    province_id: 3
+  },
+  {
+    id: 114,
+    name: 'کوراییم',
+    slug: 'کوراییم',
+    province_id: 3
+  },
+  {
+    id: 115,
+    name: 'گرمی',
+    slug: 'گرمی',
+    province_id: 3
+  },
+  {
+    id: 116,
+    name: 'گیوی',
+    slug: 'گیوی',
+    province_id: 3
+  },
+  {
+    id: 117,
+    name: 'لاهرود',
+    slug: 'لاهرود',
+    province_id: 3
+  },
+  {
+    id: 118,
+    name: 'مشگین شهر',
+    slug: 'مشگین-شهر',
+    province_id: 3
+  },
+  {
+    id: 119,
+    name: 'نمین',
+    slug: 'نمین',
+    province_id: 3
+  },
+  {
+    id: 120,
+    name: 'نیر',
+    slug: 'اردبیل-نیر',
+    province_id: 3
+  },
+  {
+    id: 121,
+    name: 'هشتجین',
+    slug: 'هشتجین',
+    province_id: 3
+  },
+  {
+    id: 122,
+    name: 'هیر',
+    slug: 'هیر',
+    province_id: 3
+  },
+  {
+    id: 123,
+    name: 'ابریشم',
+    slug: 'ابریشم',
+    province_id: 4
+  },
+  {
+    id: 124,
+    name: 'ابوزیدآباد',
+    slug: 'ابوزیدآباد',
+    province_id: 4
+  },
+  {
+    id: 125,
+    name: 'اردستان',
+    slug: 'اردستان',
+    province_id: 4
+  },
+  {
+    id: 126,
+    name: 'اژیه',
+    slug: 'اژیه',
+    province_id: 4
+  },
+  {
+    id: 127,
+    name: 'اصفهان',
+    slug: 'اصفهان',
+    province_id: 4
+  },
+  {
+    id: 128,
+    name: 'افوس',
+    slug: 'افوس',
+    province_id: 4
+  },
+  {
+    id: 129,
+    name: 'انارک',
+    slug: 'انارک',
+    province_id: 4
+  },
+  {
+    id: 130,
+    name: 'ایمانشهر',
+    slug: 'ایمانشهر',
+    province_id: 4
+  },
+  {
+    id: 131,
+    name: 'آران وبیدگل',
+    slug: 'آران-وبیدگل',
+    province_id: 4
+  },
+  {
+    id: 132,
+    name: 'بادرود',
+    slug: 'بادرود',
+    province_id: 4
+  },
+  {
+    id: 133,
+    name: 'باغ بهادران',
+    slug: 'باغ-بهادران',
+    province_id: 4
+  },
+  {
+    id: 134,
+    name: 'بافران',
+    slug: 'بافران',
+    province_id: 4
+  },
+  {
+    id: 135,
+    name: 'برزک',
+    slug: 'برزک',
+    province_id: 4
+  },
+  {
+    id: 136,
+    name: 'برف انبار',
+    slug: 'برف-انبار',
+    province_id: 4
+  },
+  {
+    id: 137,
+    name: 'بهاران شهر',
+    slug: 'بهاران-شهر',
+    province_id: 4
+  },
+  {
+    id: 138,
+    name: 'بهارستان',
+    slug: 'بهارستان',
+    province_id: 4
+  },
+  {
+    id: 139,
+    name: 'بوئین و میاندشت',
+    slug: 'بوئین-میاندشت',
+    province_id: 4
+  },
+  {
+    id: 140,
+    name: 'پیربکران',
+    slug: 'پیربکران',
+    province_id: 4
+  },
+  {
+    id: 141,
+    name: 'تودشک',
+    slug: 'تودشک',
+    province_id: 4
+  },
+  {
+    id: 142,
+    name: 'تیران',
+    slug: 'تیران',
+    province_id: 4
+  },
+  {
+    id: 143,
+    name: 'جندق',
+    slug: 'جندق',
+    province_id: 4
+  },
+  {
+    id: 144,
+    name: 'جوزدان',
+    slug: 'جوزدان',
+    province_id: 4
+  },
+  {
+    id: 145,
+    name: 'جوشقان و کامو',
+    slug: 'جوشقان-کامو',
+    province_id: 4
+  },
+  {
+    id: 146,
+    name: 'چادگان',
+    slug: 'چادگان',
+    province_id: 4
+  },
+  {
+    id: 147,
+    name: 'چرمهین',
+    slug: 'چرمهین',
+    province_id: 4
+  },
+  {
+    id: 148,
+    name: 'چمگردان',
+    slug: 'چمگردان',
+    province_id: 4
+  },
+  {
+    id: 149,
+    name: 'حبیب آباد',
+    slug: 'حبیب-آباد',
+    province_id: 4
+  },
+  {
+    id: 150,
+    name: 'حسن آباد',
+    slug: 'اصفهان-حسن-آباد',
+    province_id: 4
+  },
+  {
+    id: 151,
+    name: 'حنا',
+    slug: 'حنا',
+    province_id: 4
+  },
+  {
+    id: 152,
+    name: 'خالدآباد',
+    slug: 'خالدآباد',
+    province_id: 4
+  },
+  {
+    id: 153,
+    name: 'خمینی شهر',
+    slug: 'خمینی-شهر',
+    province_id: 4
+  },
+  {
+    id: 154,
+    name: 'خوانسار',
+    slug: 'خوانسار',
+    province_id: 4
+  },
+  {
+    id: 155,
+    name: 'خور',
+    slug: 'اصفهان-خور',
+    province_id: 4
+  },
+  {
+    id: 157,
+    name: 'خورزوق',
+    slug: 'خورزوق',
+    province_id: 4
+  },
+  {
+    id: 158,
+    name: 'داران',
+    slug: 'داران',
+    province_id: 4
+  },
+  {
+    id: 159,
+    name: 'دامنه',
+    slug: 'دامنه',
+    province_id: 4
+  },
+  {
+    id: 160,
+    name: 'درچه',
+    slug: 'درچه',
+    province_id: 4
+  },
+  {
+    id: 161,
+    name: 'دستگرد',
+    slug: 'دستگرد',
+    province_id: 4
+  },
+  {
+    id: 162,
+    name: 'دهاقان',
+    slug: 'دهاقان',
+    province_id: 4
+  },
+  {
+    id: 163,
+    name: 'دهق',
+    slug: 'دهق',
+    province_id: 4
+  },
+  {
+    id: 164,
+    name: 'دولت آباد',
+    slug: 'اصفهان-دولت-آباد',
+    province_id: 4
+  },
+  {
+    id: 165,
+    name: 'دیزیچه',
+    slug: 'دیزیچه',
+    province_id: 4
+  },
+  {
+    id: 166,
+    name: 'رزوه',
+    slug: 'رزوه',
+    province_id: 4
+  },
+  {
+    id: 167,
+    name: 'رضوانشهر',
+    slug: 'اصفهان-رضوانشهر',
+    province_id: 4
+  },
+  {
+    id: 168,
+    name: 'زاینده رود',
+    slug: 'زاینده-رود',
+    province_id: 4
+  },
+  {
+    id: 169,
+    name: 'زرین شهر',
+    slug: 'زرین-شهر',
+    province_id: 4
+  },
+  {
+    id: 170,
+    name: 'زواره',
+    slug: 'زواره',
+    province_id: 4
+  },
+  {
+    id: 171,
+    name: 'زیباشهر',
+    slug: 'زیباشهر',
+    province_id: 4
+  },
+  {
+    id: 172,
+    name: 'سده لنجان',
+    slug: 'سده-لنجان',
+    province_id: 4
+  },
+  {
+    id: 173,
+    name: 'سفیدشهر',
+    slug: 'سفیدشهر',
+    province_id: 4
+  },
+  {
+    id: 174,
+    name: 'سگزی',
+    slug: 'سگزی',
+    province_id: 4
+  },
+  {
+    id: 175,
+    name: 'سمیرم',
+    slug: 'سمیرم',
+    province_id: 4
+  },
+  {
+    id: 176,
+    name: 'شاهین شهر',
+    slug: 'شاهین-شهر',
+    province_id: 4
+  },
+  {
+    id: 177,
+    name: 'شهرضا',
+    slug: 'شهرضا',
+    province_id: 4
+  },
+  {
+    id: 178,
+    name: 'طالخونچه',
+    slug: 'طالخونچه',
+    province_id: 4
+  },
+  {
+    id: 179,
+    name: 'عسگران',
+    slug: 'عسگران',
+    province_id: 4
+  },
+  {
+    id: 180,
+    name: 'علویجه',
+    slug: 'علویجه',
+    province_id: 4
+  },
+  {
+    id: 181,
+    name: 'فرخی',
+    slug: 'فرخی',
+    province_id: 4
+  },
+  {
+    id: 182,
+    name: 'فریدونشهر',
+    slug: 'فریدونشهر',
+    province_id: 4
+  },
+  {
+    id: 183,
+    name: 'فلاورجان',
+    slug: 'فلاورجان',
+    province_id: 4
+  },
+  {
+    id: 184,
+    name: 'فولادشهر',
+    slug: 'فولادشهر',
+    province_id: 4
+  },
+  {
+    id: 185,
+    name: 'قمصر',
+    slug: 'قمصر',
+    province_id: 4
+  },
+  {
+    id: 186,
+    name: 'قهجاورستان',
+    slug: 'قهجاورستان',
+    province_id: 4
+  },
+  {
+    id: 187,
+    name: 'قهدریجان',
+    slug: 'قهدریجان',
+    province_id: 4
+  },
+  {
+    id: 188,
+    name: 'کاشان',
+    slug: 'کاشان',
+    province_id: 4
+  },
+  {
+    id: 189,
+    name: 'کرکوند',
+    slug: 'کرکوند',
+    province_id: 4
+  },
+  {
+    id: 190,
+    name: 'کلیشاد و سودرجان',
+    slug: 'کلیشاد-سودرجان',
+    province_id: 4
+  },
+  {
+    id: 191,
+    name: 'کمشچه',
+    slug: 'کمشچه',
+    province_id: 4
+  },
+  {
+    id: 192,
+    name: 'کمه',
+    slug: 'کمه',
+    province_id: 4
+  },
+  {
+    id: 193,
+    name: 'کهریزسنگ',
+    slug: 'کهریزسنگ',
+    province_id: 4
+  },
+  {
+    id: 194,
+    name: 'کوشک',
+    slug: 'کوشک',
+    province_id: 4
+  },
+  {
+    id: 195,
+    name: 'کوهپایه',
+    slug: 'کوهپایه',
+    province_id: 4
+  },
+  {
+    id: 196,
+    name: 'گرگاب',
+    slug: 'گرگاب',
+    province_id: 4
+  },
+  {
+    id: 197,
+    name: 'گزبرخوار',
+    slug: 'گزبرخوار',
+    province_id: 4
+  },
+  {
+    id: 198,
+    name: 'گلپایگان',
+    slug: 'گلپایگان',
+    province_id: 4
+  },
+  {
+    id: 199,
+    name: 'گلدشت',
+    slug: 'گلدشت',
+    province_id: 4
+  },
+  {
+    id: 200,
+    name: 'گلشهر',
+    slug: 'گلشهر',
+    province_id: 4
+  },
+  {
+    id: 201,
+    name: 'گوگد',
+    slug: 'گوگد',
+    province_id: 4
+  },
+  {
+    id: 202,
+    name: 'لای بید',
+    slug: 'لای-بید',
+    province_id: 4
+  },
+  {
+    id: 203,
+    name: 'مبارکه',
+    slug: 'مبارکه',
+    province_id: 4
+  },
+  {
+    id: 204,
+    name: 'مجلسی',
+    slug: 'مجلسی',
+    province_id: 4
+  },
+  {
+    id: 205,
+    name: 'محمدآباد',
+    slug: 'اصفهان-محمدآباد',
+    province_id: 4
+  },
+  {
+    id: 206,
+    name: 'مشکات',
+    slug: 'مشکات',
+    province_id: 4
+  },
+  {
+    id: 207,
+    name: 'منظریه',
+    slug: 'منظریه',
+    province_id: 4
+  },
+  {
+    id: 208,
+    name: 'مهاباد',
+    slug: 'اصفهان-مهاباد',
+    province_id: 4
+  },
+  {
+    id: 209,
+    name: 'میمه',
+    slug: 'اصفهان-میمه',
+    province_id: 4
+  },
+  {
+    id: 210,
+    name: 'نائین',
+    slug: 'نائین',
+    province_id: 4
+  },
+  {
+    id: 211,
+    name: 'نجف آباد',
+    slug: 'نجف-آباد',
+    province_id: 4
+  },
+  {
+    id: 212,
+    name: 'نصرآباد',
+    slug: 'اصفهان-نصرآباد',
+    province_id: 4
+  },
+  {
+    id: 213,
+    name: 'نطنز',
+    slug: 'نطنز',
+    province_id: 4
+  },
+  {
+    id: 214,
+    name: 'نوش آباد',
+    slug: 'نوش-آباد',
+    province_id: 4
+  },
+  {
+    id: 215,
+    name: 'نیاسر',
+    slug: 'نیاسر',
+    province_id: 4
+  },
+  {
+    id: 216,
+    name: 'نیک آباد',
+    slug: 'نیک-آباد',
+    province_id: 4
+  },
+  {
+    id: 217,
+    name: 'هرند',
+    slug: 'هرند',
+    province_id: 4
+  },
+  {
+    id: 218,
+    name: 'ورزنه',
+    slug: 'ورزنه',
+    province_id: 4
+  },
+  {
+    id: 219,
+    name: 'ورنامخواست',
+    slug: 'ورنامخواست',
+    province_id: 4
+  },
+  {
+    id: 220,
+    name: 'وزوان',
+    slug: 'وزوان',
+    province_id: 4
+  },
+  {
+    id: 221,
+    name: 'ونک',
+    slug: 'ونک',
+    province_id: 4
+  },
+  {
+    id: 222,
+    name: 'اسارا',
+    slug: 'اسارا',
+    province_id: 5
+  },
+  {
+    id: 223,
+    name: 'اشتهارد',
+    slug: 'اشتهارد',
+    province_id: 5
+  },
+  {
+    id: 224,
+    name: 'تنکمان',
+    slug: 'تنکمان',
+    province_id: 5
+  },
+  {
+    id: 225,
+    name: 'چهارباغ',
+    slug: 'چهارباغ',
+    province_id: 5
+  },
+  {
+    id: 226,
+    name: 'سعید آباد',
+    slug: 'سعید-آباد',
+    province_id: 5
+  },
+  {
+    id: 227,
+    name: 'شهر جدید هشتگرد',
+    slug: 'شهر-جدید-هشتگرد',
+    province_id: 5
+  },
+  {
+    id: 228,
+    name: 'طالقان',
+    slug: 'طالقان',
+    province_id: 5
+  },
+  {
+    id: 229,
+    name: 'کرج',
+    slug: 'کرج',
+    province_id: 5
+  },
+  {
+    id: 230,
+    name: 'کمال شهر',
+    slug: 'کمال-شهر',
+    province_id: 5
+  },
+  {
+    id: 231,
+    name: 'کوهسار',
+    slug: 'کوهسار',
+    province_id: 5
+  },
+  {
+    id: 232,
+    name: 'گرمدره',
+    slug: 'گرمدره',
+    province_id: 5
+  },
+  {
+    id: 233,
+    name: 'ماهدشت',
+    slug: 'ماهدشت',
+    province_id: 5
+  },
+  {
+    id: 234,
+    name: 'محمدشهر',
+    slug: 'البرز-محمدشهر',
+    province_id: 5
+  },
+  {
+    id: 235,
+    name: 'مشکین دشت',
+    slug: 'مشکین-دشت',
+    province_id: 5
+  },
+  {
+    id: 236,
+    name: 'نظرآباد',
+    slug: 'نظرآباد',
+    province_id: 5
+  },
+  {
+    id: 237,
+    name: 'هشتگرد',
+    slug: 'هشتگرد',
+    province_id: 5
+  },
+  {
+    id: 238,
+    name: 'ارکواز',
+    slug: 'ارکواز',
+    province_id: 6
+  },
+  {
+    id: 239,
+    name: 'ایلام',
+    slug: 'ایلام',
+    province_id: 6
+  },
+  {
+    id: 240,
+    name: 'ایوان',
+    slug: 'ایوان',
+    province_id: 6
+  },
+  {
+    id: 241,
+    name: 'آبدانان',
+    slug: 'آبدانان',
+    province_id: 6
+  },
+  {
+    id: 242,
+    name: 'آسمان آباد',
+    slug: 'آسمان-آباد',
+    province_id: 6
+  },
+  {
+    id: 243,
+    name: 'بدره',
+    slug: 'بدره',
+    province_id: 6
+  },
+  {
+    id: 244,
+    name: 'پهله',
+    slug: 'پهله',
+    province_id: 6
+  },
+  {
+    id: 245,
+    name: 'توحید',
+    slug: 'توحید',
+    province_id: 6
+  },
+  {
+    id: 246,
+    name: 'چوار',
+    slug: 'چوار',
+    province_id: 6
+  },
+  {
+    id: 247,
+    name: 'دره شهر',
+    slug: 'دره-شهر',
+    province_id: 6
+  },
+  {
+    id: 248,
+    name: 'دلگشا',
+    slug: 'دلگشا',
+    province_id: 6
+  },
+  {
+    id: 249,
+    name: 'دهلران',
+    slug: 'دهلران',
+    province_id: 6
+  },
+  {
+    id: 250,
+    name: 'زرنه',
+    slug: 'زرنه',
+    province_id: 6
+  },
+  {
+    id: 251,
+    name: 'سراب باغ',
+    slug: 'سراب-باغ',
+    province_id: 6
+  },
+  {
+    id: 252,
+    name: 'سرابله',
+    slug: 'سرابله',
+    province_id: 6
+  },
+  {
+    id: 253,
+    name: 'صالح آباد',
+    slug: 'ایلام-صالح-آباد',
+    province_id: 6
+  },
+  {
+    id: 254,
+    name: 'لومار',
+    slug: 'لومار',
+    province_id: 6
+  },
+  {
+    id: 255,
+    name: 'مهران',
+    slug: 'مهران',
+    province_id: 6
+  },
+  {
+    id: 256,
+    name: 'مورموری',
+    slug: 'مورموری',
+    province_id: 6
+  },
+  {
+    id: 257,
+    name: 'موسیان',
+    slug: 'موسیان',
+    province_id: 6
+  },
+  {
+    id: 258,
+    name: 'میمه',
+    slug: 'ایلام-میمه',
+    province_id: 6
+  },
+  {
+    id: 259,
+    name: 'امام حسن',
+    slug: 'امام-حسن',
+    province_id: 7
+  },
+  {
+    id: 260,
+    name: 'انارستان',
+    slug: 'انارستان',
+    province_id: 7
+  },
+  {
+    id: 261,
+    name: 'اهرم',
+    slug: 'اهرم',
+    province_id: 7
+  },
+  {
+    id: 262,
+    name: 'آب پخش',
+    slug: 'آب-پخش',
+    province_id: 7
+  },
+  {
+    id: 263,
+    name: 'آبدان',
+    slug: 'آبدان',
+    province_id: 7
+  },
+  {
+    id: 264,
+    name: 'برازجان',
+    slug: 'برازجان',
+    province_id: 7
+  },
+  {
+    id: 265,
+    name: 'بردخون',
+    slug: 'بردخون',
+    province_id: 7
+  },
+  {
+    id: 266,
+    name: 'بندردیر',
+    slug: 'بندردیر',
+    province_id: 7
+  },
+  {
+    id: 267,
+    name: 'بندردیلم',
+    slug: 'بندردیلم',
+    province_id: 7
+  },
+  {
+    id: 268,
+    name: 'بندرریگ',
+    slug: 'بندرریگ',
+    province_id: 7
+  },
+  {
+    id: 269,
+    name: 'بندرکنگان',
+    slug: 'بندرکنگان',
+    province_id: 7
+  },
+  {
+    id: 270,
+    name: 'بندرگناوه',
+    slug: 'بندرگناوه',
+    province_id: 7
+  },
+  {
+    id: 271,
+    name: 'بنک',
+    slug: 'بنک',
+    province_id: 7
+  },
+  {
+    id: 272,
+    name: 'بوشهر',
+    slug: 'بوشهر',
+    province_id: 7
+  },
+  {
+    id: 273,
+    name: 'تنگ ارم',
+    slug: 'تنگ-ارم',
+    province_id: 7
+  },
+  {
+    id: 274,
+    name: 'جم',
+    slug: 'جم',
+    province_id: 7
+  },
+  {
+    id: 275,
+    name: 'چغادک',
+    slug: 'چغادک',
+    province_id: 7
+  },
+  {
+    id: 276,
+    name: 'خارک',
+    slug: 'خارک',
+    province_id: 7
+  },
+  {
+    id: 277,
+    name: 'خورموج',
+    slug: 'خورموج',
+    province_id: 7
+  },
+  {
+    id: 278,
+    name: 'دالکی',
+    slug: 'دالکی',
+    province_id: 7
+  },
+  {
+    id: 279,
+    name: 'دلوار',
+    slug: 'دلوار',
+    province_id: 7
+  },
+  {
+    id: 280,
+    name: 'ریز',
+    slug: 'ریز',
+    province_id: 7
+  },
+  {
+    id: 281,
+    name: 'سعدآباد',
+    slug: 'سعدآباد',
+    province_id: 7
+  },
+  {
+    id: 282,
+    name: 'سیراف',
+    slug: 'سیراف',
+    province_id: 7
+  },
+  {
+    id: 283,
+    name: 'شبانکاره',
+    slug: 'شبانکاره',
+    province_id: 7
+  },
+  {
+    id: 284,
+    name: 'شنبه',
+    slug: 'شنبه',
+    province_id: 7
+  },
+  {
+    id: 285,
+    name: 'عسلویه',
+    slug: 'عسلویه',
+    province_id: 7
+  },
+  {
+    id: 286,
+    name: 'کاکی',
+    slug: 'کاکی',
+    province_id: 7
+  },
+  {
+    id: 287,
+    name: 'کلمه',
+    slug: 'کلمه',
+    province_id: 7
+  },
+  {
+    id: 288,
+    name: 'نخل تقی',
+    slug: 'نخل-تقی',
+    province_id: 7
+  },
+  {
+    id: 289,
+    name: 'وحدتیه',
+    slug: 'وحدتیه',
+    province_id: 7
+  },
+  {
+    id: 290,
+    name: 'ارجمند',
+    slug: 'ارجمند',
+    province_id: 8
+  },
+  {
+    id: 291,
+    name: 'اسلامشهر',
+    slug: 'اسلامشهر',
+    province_id: 8
+  },
+  {
+    id: 292,
+    name: 'اندیشه',
+    slug: 'اندیشه',
+    province_id: 8
+  },
+  {
+    id: 293,
+    name: 'آبسرد',
+    slug: 'آبسرد',
+    province_id: 8
+  },
+  {
+    id: 294,
+    name: 'آبعلی',
+    slug: 'آبعلی',
+    province_id: 8
+  },
+  {
+    id: 295,
+    name: 'باغستان',
+    slug: 'باغستان',
+    province_id: 8
+  },
+  {
+    id: 296,
+    name: 'باقرشهر',
+    slug: 'باقرشهر',
+    province_id: 8
+  },
+  {
+    id: 297,
+    name: 'بومهن',
+    slug: 'بومهن',
+    province_id: 8
+  },
+  {
+    id: 298,
+    name: 'پاکدشت',
+    slug: 'پاکدشت',
+    province_id: 8
+  },
+  {
+    id: 299,
+    name: 'پردیس',
+    slug: 'پردیس',
+    province_id: 8
+  },
+  {
+    id: 300,
+    name: 'پیشوا',
+    slug: 'پیشوا',
+    province_id: 8
+  },
+  {
+    id: 301,
+    name: 'تهران',
+    slug: 'تهران',
+    province_id: 8
+  },
+  {
+    id: 302,
+    name: 'جوادآباد',
+    slug: 'جوادآباد',
+    province_id: 8
+  },
+  {
+    id: 303,
+    name: 'چهاردانگه',
+    slug: 'چهاردانگه',
+    province_id: 8
+  },
+  {
+    id: 304,
+    name: 'حسن آباد',
+    slug: 'تهران-حسن-آباد',
+    province_id: 8
+  },
+  {
+    id: 305,
+    name: 'دماوند',
+    slug: 'دماوند',
+    province_id: 8
+  },
+  {
+    id: 306,
+    name: 'دیزین',
+    slug: 'دیزین',
+    province_id: 8
+  },
+  {
+    id: 307,
+    name: 'شهر ری',
+    slug: 'شهر-ری',
+    province_id: 8
+  },
+  {
+    id: 308,
+    name: 'رباط کریم',
+    slug: 'رباط-کریم',
+    province_id: 8
+  },
+  {
+    id: 309,
+    name: 'رودهن',
+    slug: 'رودهن',
+    province_id: 8
+  },
+  {
+    id: 310,
+    name: 'شاهدشهر',
+    slug: 'شاهدشهر',
+    province_id: 8
+  },
+  {
+    id: 311,
+    name: 'شریف آباد',
+    slug: 'شریف-آباد',
+    province_id: 8
+  },
+  {
+    id: 312,
+    name: 'شمشک',
+    slug: 'شمشک',
+    province_id: 8
+  },
+  {
+    id: 313,
+    name: 'شهریار',
+    slug: 'شهریار',
+    province_id: 8
+  },
+  {
+    id: 314,
+    name: 'صالح آباد',
+    slug: 'تهران-صالح-آباد',
+    province_id: 8
+  },
+  {
+    id: 315,
+    name: 'صباشهر',
+    slug: 'صباشهر',
+    province_id: 8
+  },
+  {
+    id: 316,
+    name: 'صفادشت',
+    slug: 'صفادشت',
+    province_id: 8
+  },
+  {
+    id: 317,
+    name: 'فردوسیه',
+    slug: 'فردوسیه',
+    province_id: 8
+  },
+  {
+    id: 318,
+    name: 'فشم',
+    slug: 'فشم',
+    province_id: 8
+  },
+  {
+    id: 319,
+    name: 'فیروزکوه',
+    slug: 'فیروزکوه',
+    province_id: 8
+  },
+  {
+    id: 320,
+    name: 'قدس',
+    slug: 'قدس',
+    province_id: 8
+  },
+  {
+    id: 321,
+    name: 'قرچک',
+    slug: 'قرچک',
+    province_id: 8
+  },
+  {
+    id: 322,
+    name: 'کهریزک',
+    slug: 'کهریزک',
+    province_id: 8
+  },
+  {
+    id: 323,
+    name: 'کیلان',
+    slug: 'کیلان',
+    province_id: 8
+  },
+  {
+    id: 324,
+    name: 'گلستان',
+    slug: 'شهر-گلستان',
+    province_id: 8
+  },
+  {
+    id: 325,
+    name: 'لواسان',
+    slug: 'لواسان',
+    province_id: 8
+  },
+  {
+    id: 326,
+    name: 'ملارد',
+    slug: 'ملارد',
+    province_id: 8
+  },
+  {
+    id: 327,
+    name: 'میگون',
+    slug: 'میگون',
+    province_id: 8
+  },
+  {
+    id: 328,
+    name: 'نسیم شهر',
+    slug: 'نسیم-شهر',
+    province_id: 8
+  },
+  {
+    id: 329,
+    name: 'نصیرآباد',
+    slug: 'نصیرآباد',
+    province_id: 8
+  },
+  {
+    id: 330,
+    name: 'وحیدیه',
+    slug: 'وحیدیه',
+    province_id: 8
+  },
+  {
+    id: 331,
+    name: 'ورامین',
+    slug: 'ورامین',
+    province_id: 8
+  },
+  {
+    id: 332,
+    name: 'اردل',
+    slug: 'اردل',
+    province_id: 9
+  },
+  {
+    id: 333,
+    name: 'آلونی',
+    slug: 'آلونی',
+    province_id: 9
+  },
+  {
+    id: 334,
+    name: 'باباحیدر',
+    slug: 'باباحیدر',
+    province_id: 9
+  },
+  {
+    id: 335,
+    name: 'بروجن',
+    slug: 'بروجن',
+    province_id: 9
+  },
+  {
+    id: 336,
+    name: 'بلداجی',
+    slug: 'بلداجی',
+    province_id: 9
+  },
+  {
+    id: 337,
+    name: 'بن',
+    slug: 'بن',
+    province_id: 9
+  },
+  {
+    id: 338,
+    name: 'جونقان',
+    slug: 'جونقان',
+    province_id: 9
+  },
+  {
+    id: 339,
+    name: 'چلگرد',
+    slug: 'چلگرد',
+    province_id: 9
+  },
+  {
+    id: 340,
+    name: 'سامان',
+    slug: 'سامان',
+    province_id: 9
+  },
+  {
+    id: 341,
+    name: 'سفیددشت',
+    slug: 'سفیددشت',
+    province_id: 9
+  },
+  {
+    id: 342,
+    name: 'سودجان',
+    slug: 'سودجان',
+    province_id: 9
+  },
+  {
+    id: 343,
+    name: 'سورشجان',
+    slug: 'سورشجان',
+    province_id: 9
+  },
+  {
+    id: 344,
+    name: 'شلمزار',
+    slug: 'شلمزار',
+    province_id: 9
+  },
+  {
+    id: 345,
+    name: 'شهرکرد',
+    slug: 'شهرکرد',
+    province_id: 9
+  },
+  {
+    id: 346,
+    name: 'طاقانک',
+    slug: 'طاقانک',
+    province_id: 9
+  },
+  {
+    id: 347,
+    name: 'فارسان',
+    slug: 'فارسان',
+    province_id: 9
+  },
+  {
+    id: 348,
+    name: 'فرادنبه',
+    slug: 'فرادبنه',
+    province_id: 9
+  },
+  {
+    id: 349,
+    name: 'فرخ شهر',
+    slug: 'فرخ-شهر',
+    province_id: 9
+  },
+  {
+    id: 350,
+    name: 'کیان',
+    slug: 'کیان',
+    province_id: 9
+  },
+  {
+    id: 351,
+    name: 'گندمان',
+    slug: 'گندمان',
+    province_id: 9
+  },
+  {
+    id: 352,
+    name: 'گهرو',
+    slug: 'گهرو',
+    province_id: 9
+  },
+  {
+    id: 353,
+    name: 'لردگان',
+    slug: 'لردگان',
+    province_id: 9
+  },
+  {
+    id: 354,
+    name: 'مال خلیفه',
+    slug: 'مال-خلیفه',
+    province_id: 9
+  },
+  {
+    id: 355,
+    name: 'ناغان',
+    slug: 'ناغان',
+    province_id: 9
+  },
+  {
+    id: 356,
+    name: 'نافچ',
+    slug: 'نافچ',
+    province_id: 9
+  },
+  {
+    id: 357,
+    name: 'نقنه',
+    slug: 'نقنه',
+    province_id: 9
+  },
+  {
+    id: 358,
+    name: 'هفشجان',
+    slug: 'هفشجان',
+    province_id: 9
+  },
+  {
+    id: 359,
+    name: 'ارسک',
+    slug: 'ارسک',
+    province_id: 10
+  },
+  {
+    id: 360,
+    name: 'اسدیه',
+    slug: 'اسدیه',
+    province_id: 10
+  },
+  {
+    id: 361,
+    name: 'اسفدن',
+    slug: 'اسفدن',
+    province_id: 10
+  },
+  {
+    id: 362,
+    name: 'اسلامیه',
+    slug: 'اسلامیه',
+    province_id: 10
+  },
+  {
+    id: 363,
+    name: 'آرین شهر',
+    slug: 'آرین-شهر',
+    province_id: 10
+  },
+  {
+    id: 364,
+    name: 'آیسک',
+    slug: 'آیسک',
+    province_id: 10
+  },
+  {
+    id: 365,
+    name: 'بشرویه',
+    slug: 'بشرویه',
+    province_id: 10
+  },
+  {
+    id: 366,
+    name: 'بیرجند',
+    slug: 'بیرجند',
+    province_id: 10
+  },
+  {
+    id: 367,
+    name: 'حاجی آباد',
+    slug: 'خراسان-جنوبی-حاجی-آباد',
+    province_id: 10
+  },
+  {
+    id: 368,
+    name: 'خضری دشت بیاض',
+    slug: 'خضری-دشت-بیاض',
+    province_id: 10
+  },
+  {
+    id: 369,
+    name: 'خوسف',
+    slug: 'خوسف',
+    province_id: 10
+  },
+  {
+    id: 370,
+    name: 'زهان',
+    slug: 'زهان',
+    province_id: 10
+  },
+  {
+    id: 371,
+    name: 'سرایان',
+    slug: 'سرایان',
+    province_id: 10
+  },
+  {
+    id: 372,
+    name: 'سربیشه',
+    slug: 'سربیشه',
+    province_id: 10
+  },
+  {
+    id: 373,
+    name: 'سه قلعه',
+    slug: 'سه-قلعه',
+    province_id: 10
+  },
+  {
+    id: 374,
+    name: 'شوسف',
+    slug: 'شوسف',
+    province_id: 10
+  },
+  {
+    id: 375,
+    name: 'طبس ',
+    slug: 'خراسان-جنوبی-طبس-',
+    province_id: 10
+  },
+  {
+    id: 376,
+    name: 'فردوس',
+    slug: 'فردوس',
+    province_id: 10
+  },
+  {
+    id: 377,
+    name: 'قاین',
+    slug: 'قاین',
+    province_id: 10
+  },
+  {
+    id: 378,
+    name: 'قهستان',
+    slug: 'قهستان',
+    province_id: 10
+  },
+  {
+    id: 379,
+    name: 'محمدشهر',
+    slug: 'خراسان-جنوبی-محمدشهر',
+    province_id: 10
+  },
+  {
+    id: 380,
+    name: 'مود',
+    slug: 'مود',
+    province_id: 10
+  },
+  {
+    id: 381,
+    name: 'نهبندان',
+    slug: 'نهبندان',
+    province_id: 10
+  },
+  {
+    id: 382,
+    name: 'نیمبلوک',
+    slug: 'نیمبلوک',
+    province_id: 10
+  },
+  {
+    id: 383,
+    name: 'احمدآباد صولت',
+    slug: 'احمدآباد-صولت',
+    province_id: 11
+  },
+  {
+    id: 384,
+    name: 'انابد',
+    slug: 'انابد',
+    province_id: 11
+  },
+  {
+    id: 385,
+    name: 'باجگیران',
+    slug: 'باجگیران',
+    province_id: 11
+  },
+  {
+    id: 386,
+    name: 'باخرز',
+    slug: 'باخرز',
+    province_id: 11
+  },
+  {
+    id: 387,
+    name: 'بار',
+    slug: 'بار',
+    province_id: 11
+  },
+  {
+    id: 388,
+    name: 'بایگ',
+    slug: 'بایگ',
+    province_id: 11
+  },
+  {
+    id: 389,
+    name: 'بجستان',
+    slug: 'بجستان',
+    province_id: 11
+  },
+  {
+    id: 390,
+    name: 'بردسکن',
+    slug: 'بردسکن',
+    province_id: 11
+  },
+  {
+    id: 391,
+    name: 'بیدخت',
+    slug: 'بیدخت',
+    province_id: 11
+  },
+  {
+    id: 392,
+    name: 'تایباد',
+    slug: 'تایباد',
+    province_id: 11
+  },
+  {
+    id: 393,
+    name: 'تربت جام',
+    slug: 'تربت-جام',
+    province_id: 11
+  },
+  {
+    id: 394,
+    name: 'تربت حیدریه',
+    slug: 'تربت-حیدریه',
+    province_id: 11
+  },
+  {
+    id: 395,
+    name: 'جغتای',
+    slug: 'جغتای',
+    province_id: 11
+  },
+  {
+    id: 396,
+    name: 'جنگل',
+    slug: 'جنگل',
+    province_id: 11
+  },
+  {
+    id: 397,
+    name: 'چاپشلو',
+    slug: 'چاپشلو',
+    province_id: 11
+  },
+  {
+    id: 398,
+    name: 'چکنه',
+    slug: 'چکنه',
+    province_id: 11
+  },
+  {
+    id: 399,
+    name: 'چناران',
+    slug: 'چناران',
+    province_id: 11
+  },
+  {
+    id: 400,
+    name: 'خرو',
+    slug: 'خرو',
+    province_id: 11
+  },
+  {
+    id: 401,
+    name: 'خلیل آباد',
+    slug: 'خلیل-آباد',
+    province_id: 11
+  },
+  {
+    id: 402,
+    name: 'خواف',
+    slug: 'خواف',
+    province_id: 11
+  },
+  {
+    id: 403,
+    name: 'داورزن',
+    slug: 'داورزن',
+    province_id: 11
+  },
+  {
+    id: 404,
+    name: 'درگز',
+    slug: 'درگز',
+    province_id: 11
+  },
+  {
+    id: 405,
+    name: 'در رود',
+    slug: 'در-رود',
+    province_id: 11
+  },
+  {
+    id: 406,
+    name: 'دولت آباد',
+    slug: 'خراسان-رضوی-دولت-آباد',
+    province_id: 11
+  },
+  {
+    id: 407,
+    name: 'رباط سنگ',
+    slug: 'رباط-سنگ',
+    province_id: 11
+  },
+  {
+    id: 408,
+    name: 'رشتخوار',
+    slug: 'رشتخوار',
+    province_id: 11
+  },
+  {
+    id: 409,
+    name: 'رضویه',
+    slug: 'رضویه',
+    province_id: 11
+  },
+  {
+    id: 410,
+    name: 'روداب',
+    slug: 'روداب',
+    province_id: 11
+  },
+  {
+    id: 411,
+    name: 'ریوش',
+    slug: 'ریوش',
+    province_id: 11
+  },
+  {
+    id: 412,
+    name: 'سبزوار',
+    slug: 'سبزوار',
+    province_id: 11
+  },
+  {
+    id: 413,
+    name: 'سرخس',
+    slug: 'سرخس',
+    province_id: 11
+  },
+  {
+    id: 414,
+    name: 'سفیدسنگ',
+    slug: 'سفیدسنگ',
+    province_id: 11
+  },
+  {
+    id: 415,
+    name: 'سلامی',
+    slug: 'سلامی',
+    province_id: 11
+  },
+  {
+    id: 416,
+    name: 'سلطان آباد',
+    slug: 'سلطان-آباد',
+    province_id: 11
+  },
+  {
+    id: 417,
+    name: 'سنگان',
+    slug: 'سنگان',
+    province_id: 11
+  },
+  {
+    id: 418,
+    name: 'شادمهر',
+    slug: 'شادمهر',
+    province_id: 11
+  },
+  {
+    id: 419,
+    name: 'شاندیز',
+    slug: 'شاندیز',
+    province_id: 11
+  },
+  {
+    id: 420,
+    name: 'ششتمد',
+    slug: 'ششتمد',
+    province_id: 11
+  },
+  {
+    id: 421,
+    name: 'شهرآباد',
+    slug: 'شهرآباد',
+    province_id: 11
+  },
+  {
+    id: 422,
+    name: 'شهرزو',
+    slug: 'شهرزو',
+    province_id: 11
+  },
+  {
+    id: 423,
+    name: 'صالح آباد',
+    slug: 'خراسان-رضوی-صالح-آباد',
+    province_id: 11
+  },
+  {
+    id: 424,
+    name: 'طرقبه',
+    slug: 'طرقبه',
+    province_id: 11
+  },
+  {
+    id: 425,
+    name: 'عشق آباد',
+    slug: 'خراسان-رضوی-عشق-آباد',
+    province_id: 11
+  },
+  {
+    id: 426,
+    name: 'فرهادگرد',
+    slug: 'فرهادگرد',
+    province_id: 11
+  },
+  {
+    id: 427,
+    name: 'فریمان',
+    slug: 'فریمان',
+    province_id: 11
+  },
+  {
+    id: 428,
+    name: 'فیروزه',
+    slug: 'فیروزه',
+    province_id: 11
+  },
+  {
+    id: 429,
+    name: 'فیض آباد',
+    slug: 'فیض-آباد',
+    province_id: 11
+  },
+  {
+    id: 430,
+    name: 'قاسم آباد',
+    slug: 'قاسم-آباد',
+    province_id: 11
+  },
+  {
+    id: 431,
+    name: 'قدمگاه',
+    slug: 'قدمگاه',
+    province_id: 11
+  },
+  {
+    id: 432,
+    name: 'قلندرآباد',
+    slug: 'قلندرآباد',
+    province_id: 11
+  },
+  {
+    id: 433,
+    name: 'قوچان',
+    slug: 'قوچان',
+    province_id: 11
+  },
+  {
+    id: 434,
+    name: 'کاخک',
+    slug: 'کاخک',
+    province_id: 11
+  },
+  {
+    id: 435,
+    name: 'کاریز',
+    slug: 'کاریز',
+    province_id: 11
+  },
+  {
+    id: 436,
+    name: 'کاشمر',
+    slug: 'کاشمر',
+    province_id: 11
+  },
+  {
+    id: 437,
+    name: 'کدکن',
+    slug: 'کدکن',
+    province_id: 11
+  },
+  {
+    id: 438,
+    name: 'کلات',
+    slug: 'کلات',
+    province_id: 11
+  },
+  {
+    id: 439,
+    name: 'کندر',
+    slug: 'کندر',
+    province_id: 11
+  },
+  {
+    id: 440,
+    name: 'گلمکان',
+    slug: 'گلمکان',
+    province_id: 11
+  },
+  {
+    id: 441,
+    name: 'گناباد',
+    slug: 'گناباد',
+    province_id: 11
+  },
+  {
+    id: 442,
+    name: 'لطف آباد',
+    slug: 'لطف-آباد',
+    province_id: 11
+  },
+  {
+    id: 443,
+    name: 'مزدآوند',
+    slug: 'مزدآوند',
+    province_id: 11
+  },
+  {
+    id: 444,
+    name: 'مشهد',
+    slug: 'مشهد',
+    province_id: 11
+  },
+  {
+    id: 445,
+    name: 'ملک آباد',
+    slug: 'ملک-آباد',
+    province_id: 11
+  },
+  {
+    id: 446,
+    name: 'نشتیفان',
+    slug: 'نشتیفان',
+    province_id: 11
+  },
+  {
+    id: 447,
+    name: 'نصرآباد',
+    slug: 'خراسان-رضوی-نصرآباد',
+    province_id: 11
+  },
+  {
+    id: 448,
+    name: 'نقاب',
+    slug: 'نقاب',
+    province_id: 11
+  },
+  {
+    id: 449,
+    name: 'نوخندان',
+    slug: 'نوخندان',
+    province_id: 11
+  },
+  {
+    id: 450,
+    name: 'نیشابور',
+    slug: 'نیشابور',
+    province_id: 11
+  },
+  {
+    id: 451,
+    name: 'نیل شهر',
+    slug: 'نیل-شهر',
+    province_id: 11
+  },
+  {
+    id: 452,
+    name: 'همت آباد',
+    slug: 'همت-آباد',
+    province_id: 11
+  },
+  {
+    id: 453,
+    name: 'یونسی',
+    slug: 'یونسی',
+    province_id: 11
+  },
+  {
+    id: 454,
+    name: 'اسفراین',
+    slug: 'اسفراین',
+    province_id: 12
+  },
+  {
+    id: 455,
+    name: 'ایور',
+    slug: 'ایور',
+    province_id: 12
+  },
+  {
+    id: 456,
+    name: 'آشخانه',
+    slug: 'آشخانه',
+    province_id: 12
+  },
+  {
+    id: 457,
+    name: 'بجنورد',
+    slug: 'بجنورد',
+    province_id: 12
+  },
+  {
+    id: 458,
+    name: 'پیش قلعه',
+    slug: 'پیش-قلعه',
+    province_id: 12
+  },
+  {
+    id: 459,
+    name: 'تیتکانلو',
+    slug: 'تیتکانلو',
+    province_id: 12
+  },
+  {
+    id: 460,
+    name: 'جاجرم',
+    slug: 'جاجرم',
+    province_id: 12
+  },
+  {
+    id: 461,
+    name: 'حصارگرمخان',
+    slug: 'حصارگرمخان',
+    province_id: 12
+  },
+  {
+    id: 462,
+    name: 'درق',
+    slug: 'درق',
+    province_id: 12
+  },
+  {
+    id: 463,
+    name: 'راز',
+    slug: 'راز',
+    province_id: 12
+  },
+  {
+    id: 464,
+    name: 'سنخواست',
+    slug: 'سنخواست',
+    province_id: 12
+  },
+  {
+    id: 465,
+    name: 'شوقان',
+    slug: 'شوقان',
+    province_id: 12
+  },
+  {
+    id: 466,
+    name: 'شیروان',
+    slug: 'شیروان',
+    province_id: 12
+  },
+  {
+    id: 467,
+    name: 'صفی آباد',
+    slug: 'خراسان-شمالی-صفی-آباد',
+    province_id: 12
+  },
+  {
+    id: 468,
+    name: 'فاروج',
+    slug: 'فاروج',
+    province_id: 12
+  },
+  {
+    id: 469,
+    name: 'قاضی',
+    slug: 'قاضی',
+    province_id: 12
+  },
+  {
+    id: 470,
+    name: 'گرمه',
+    slug: 'گرمه',
+    province_id: 12
+  },
+  {
+    id: 471,
+    name: 'لوجلی',
+    slug: 'لوجلی',
+    province_id: 12
+  },
+  {
+    id: 472,
+    name: 'اروندکنار',
+    slug: 'اروندکنار',
+    province_id: 13
+  },
+  {
+    id: 473,
+    name: 'الوان',
+    slug: 'الوان',
+    province_id: 13
+  },
+  {
+    id: 474,
+    name: 'امیدیه',
+    slug: 'امیدیه',
+    province_id: 13
+  },
+  {
+    id: 475,
+    name: 'اندیمشک',
+    slug: 'اندیمشک',
+    province_id: 13
+  },
+  {
+    id: 476,
+    name: 'اهواز',
+    slug: 'اهواز',
+    province_id: 13
+  },
+  {
+    id: 477,
+    name: 'ایذه',
+    slug: 'ایذه',
+    province_id: 13
+  },
+  {
+    id: 478,
+    name: 'آبادان',
+    slug: 'آبادان',
+    province_id: 13
+  },
+  {
+    id: 479,
+    name: 'آغاجاری',
+    slug: 'آغاجاری',
+    province_id: 13
+  },
+  {
+    id: 480,
+    name: 'باغ ملک',
+    slug: 'باغ-ملک',
+    province_id: 13
+  },
+  {
+    id: 481,
+    name: 'بستان',
+    slug: 'بستان',
+    province_id: 13
+  },
+  {
+    id: 482,
+    name: 'بندرامام خمینی',
+    slug: 'بندرامام-خمینی',
+    province_id: 13
+  },
+  {
+    id: 483,
+    name: 'بندرماهشهر',
+    slug: 'بندرماهشهر',
+    province_id: 13
+  },
+  {
+    id: 484,
+    name: 'بهبهان',
+    slug: 'بهبهان',
+    province_id: 13
+  },
+  {
+    id: 485,
+    name: 'ترکالکی',
+    slug: 'ترکالکی',
+    province_id: 13
+  },
+  {
+    id: 486,
+    name: 'جایزان',
+    slug: 'جایزان',
+    province_id: 13
+  },
+  {
+    id: 487,
+    name: 'چمران',
+    slug: 'چمران',
+    province_id: 13
+  },
+  {
+    id: 488,
+    name: 'چویبده',
+    slug: 'چویبده',
+    province_id: 13
+  },
+  {
+    id: 489,
+    name: 'حر',
+    slug: 'حر',
+    province_id: 13
+  },
+  {
+    id: 490,
+    name: 'حسینیه',
+    slug: 'حسینیه',
+    province_id: 13
+  },
+  {
+    id: 491,
+    name: 'حمزه',
+    slug: 'حمزه',
+    province_id: 13
+  },
+  {
+    id: 492,
+    name: 'حمیدیه',
+    slug: 'حمیدیه',
+    province_id: 13
+  },
+  {
+    id: 493,
+    name: 'خرمشهر',
+    slug: 'خرمشهر',
+    province_id: 13
+  },
+  {
+    id: 494,
+    name: 'دارخوین',
+    slug: 'دارخوین',
+    province_id: 13
+  },
+  {
+    id: 495,
+    name: 'دزآب',
+    slug: 'دزآب',
+    province_id: 13
+  },
+  {
+    id: 496,
+    name: 'دزفول',
+    slug: 'دزفول',
+    province_id: 13
+  },
+  {
+    id: 497,
+    name: 'دهدز',
+    slug: 'دهدز',
+    province_id: 13
+  },
+  {
+    id: 498,
+    name: 'رامشیر',
+    slug: 'رامشیر',
+    province_id: 13
+  },
+  {
+    id: 499,
+    name: 'رامهرمز',
+    slug: 'رامهرمز',
+    province_id: 13
+  },
+  {
+    id: 500,
+    name: 'رفیع',
+    slug: 'رفیع',
+    province_id: 13
+  },
+  {
+    id: 501,
+    name: 'زهره',
+    slug: 'زهره',
+    province_id: 13
+  },
+  {
+    id: 502,
+    name: 'سالند',
+    slug: 'سالند',
+    province_id: 13
+  },
+  {
+    id: 503,
+    name: 'سردشت',
+    slug: 'خوزستان-سردشت',
+    province_id: 13
+  },
+  {
+    id: 504,
+    name: 'سوسنگرد',
+    slug: 'سوسنگرد',
+    province_id: 13
+  },
+  {
+    id: 505,
+    name: 'شادگان',
+    slug: 'شادگان',
+    province_id: 13
+  },
+  {
+    id: 506,
+    name: 'شاوور',
+    slug: 'شاوور',
+    province_id: 13
+  },
+  {
+    id: 507,
+    name: 'شرافت',
+    slug: 'شرافت',
+    province_id: 13
+  },
+  {
+    id: 508,
+    name: 'شوش',
+    slug: 'شوش',
+    province_id: 13
+  },
+  {
+    id: 509,
+    name: 'شوشتر',
+    slug: 'شوشتر',
+    province_id: 13
+  },
+  {
+    id: 510,
+    name: 'شیبان',
+    slug: 'شیبان',
+    province_id: 13
+  },
+  {
+    id: 511,
+    name: 'صالح شهر',
+    slug: 'صالح-شهر',
+    province_id: 13
+  },
+  {
+    id: 512,
+    name: 'صفی آباد',
+    slug: 'خوزستان-صفی-آباد',
+    province_id: 13
+  },
+  {
+    id: 513,
+    name: 'صیدون',
+    slug: 'صیدون',
+    province_id: 13
+  },
+  {
+    id: 514,
+    name: 'قلعه تل',
+    slug: 'قلعه-تل',
+    province_id: 13
+  },
+  {
+    id: 515,
+    name: 'قلعه خواجه',
+    slug: 'قلعه-خواجه',
+    province_id: 13
+  },
+  {
+    id: 516,
+    name: 'گتوند',
+    slug: 'گتوند',
+    province_id: 13
+  },
+  {
+    id: 517,
+    name: 'لالی',
+    slug: 'لالی',
+    province_id: 13
+  },
+  {
+    id: 518,
+    name: 'مسجدسلیمان',
+    slug: 'مسجدسلیمان',
+    province_id: 13
+  },
+  {
+    id: 520,
+    name: 'ملاثانی',
+    slug: 'ملاثانی',
+    province_id: 13
+  },
+  {
+    id: 521,
+    name: 'میانرود',
+    slug: 'میانرود',
+    province_id: 13
+  },
+  {
+    id: 522,
+    name: 'مینوشهر',
+    slug: 'مینوشهر',
+    province_id: 13
+  },
+  {
+    id: 523,
+    name: 'هفتگل',
+    slug: 'هفتگل',
+    province_id: 13
+  },
+  {
+    id: 524,
+    name: 'هندیجان',
+    slug: 'هندیجان',
+    province_id: 13
+  },
+  {
+    id: 525,
+    name: 'هویزه',
+    slug: 'هویزه',
+    province_id: 13
+  },
+  {
+    id: 526,
+    name: 'ویس',
+    slug: 'ویس',
+    province_id: 13
+  },
+  {
+    id: 527,
+    name: 'ابهر',
+    slug: 'ابهر',
+    province_id: 14
+  },
+  {
+    id: 528,
+    name: 'ارمغان خانه',
+    slug: 'ارمغان-خانه',
+    province_id: 14
+  },
+  {
+    id: 529,
+    name: 'آب بر',
+    slug: 'آب-بر',
+    province_id: 14
+  },
+  {
+    id: 530,
+    name: 'چورزق',
+    slug: 'چورزق',
+    province_id: 14
+  },
+  {
+    id: 531,
+    name: 'حلب',
+    slug: 'حلب',
+    province_id: 14
+  },
+  {
+    id: 532,
+    name: 'خرمدره',
+    slug: 'خرمدره',
+    province_id: 14
+  },
+  {
+    id: 533,
+    name: 'دندی',
+    slug: 'دندی',
+    province_id: 14
+  },
+  {
+    id: 534,
+    name: 'زرین آباد',
+    slug: 'زرین-آباد',
+    province_id: 14
+  },
+  {
+    id: 535,
+    name: 'زرین رود',
+    slug: 'زرین-رود',
+    province_id: 14
+  },
+  {
+    id: 536,
+    name: 'زنجان',
+    slug: 'زنجان',
+    province_id: 14
+  },
+  {
+    id: 537,
+    name: 'سجاس',
+    slug: 'سجاس',
+    province_id: 14
+  },
+  {
+    id: 538,
+    name: 'سلطانیه',
+    slug: 'سلطانیه',
+    province_id: 14
+  },
+  {
+    id: 539,
+    name: 'سهرورد',
+    slug: 'سهرورد',
+    province_id: 14
+  },
+  {
+    id: 540,
+    name: 'صائین قلعه',
+    slug: 'صائین-قلعه',
+    province_id: 14
+  },
+  {
+    id: 541,
+    name: 'قیدار',
+    slug: 'قیدار',
+    province_id: 14
+  },
+  {
+    id: 542,
+    name: 'گرماب',
+    slug: 'گرماب',
+    province_id: 14
+  },
+  {
+    id: 543,
+    name: 'ماه نشان',
+    slug: 'ماه-نشان',
+    province_id: 14
+  },
+  {
+    id: 544,
+    name: 'هیدج',
+    slug: 'هیدج',
+    province_id: 14
+  },
+  {
+    id: 545,
+    name: 'امیریه',
+    slug: 'امیریه',
+    province_id: 15
+  },
+  {
+    id: 546,
+    name: 'ایوانکی',
+    slug: 'ایوانکی',
+    province_id: 15
+  },
+  {
+    id: 547,
+    name: 'آرادان',
+    slug: 'آرادان',
+    province_id: 15
+  },
+  {
+    id: 548,
+    name: 'بسطام',
+    slug: 'بسطام',
+    province_id: 15
+  },
+  {
+    id: 549,
+    name: 'بیارجمند',
+    slug: 'بیارجمند',
+    province_id: 15
+  },
+  {
+    id: 550,
+    name: 'دامغان',
+    slug: 'دامغان',
+    province_id: 15
+  },
+  {
+    id: 551,
+    name: 'درجزین',
+    slug: 'درجزین',
+    province_id: 15
+  },
+  {
+    id: 552,
+    name: 'دیباج',
+    slug: 'دیباج',
+    province_id: 15
+  },
+  {
+    id: 553,
+    name: 'سرخه',
+    slug: 'سرخه',
+    province_id: 15
+  },
+  {
+    id: 554,
+    name: 'سمنان',
+    slug: 'سمنان',
+    province_id: 15
+  },
+  {
+    id: 555,
+    name: 'شاهرود',
+    slug: 'شاهرود',
+    province_id: 15
+  },
+  {
+    id: 556,
+    name: 'شهمیرزاد',
+    slug: 'شهمیرزاد',
+    province_id: 15
+  },
+  {
+    id: 557,
+    name: 'کلاته خیج',
+    slug: 'کلاته-خیج',
+    province_id: 15
+  },
+  {
+    id: 558,
+    name: 'گرمسار',
+    slug: 'گرمسار',
+    province_id: 15
+  },
+  {
+    id: 559,
+    name: 'مجن',
+    slug: 'مجن',
+    province_id: 15
+  },
+  {
+    id: 560,
+    name: 'مهدی شهر',
+    slug: 'مهدی-شهر',
+    province_id: 15
+  },
+  {
+    id: 561,
+    name: 'میامی',
+    slug: 'میامی',
+    province_id: 15
+  },
+  {
+    id: 562,
+    name: 'ادیمی',
+    slug: 'ادیمی',
+    province_id: 16
+  },
+  {
+    id: 563,
+    name: 'اسپکه',
+    slug: 'اسپکه',
+    province_id: 16
+  },
+  {
+    id: 564,
+    name: 'ایرانشهر',
+    slug: 'ایرانشهر',
+    province_id: 16
+  },
+  {
+    id: 565,
+    name: 'بزمان',
+    slug: 'بزمان',
+    province_id: 16
+  },
+  {
+    id: 566,
+    name: 'بمپور',
+    slug: 'بمپور',
+    province_id: 16
+  },
+  {
+    id: 567,
+    name: 'بنت',
+    slug: 'بنت',
+    province_id: 16
+  },
+  {
+    id: 568,
+    name: 'بنجار',
+    slug: 'بنجار',
+    province_id: 16
+  },
+  {
+    id: 569,
+    name: 'پیشین',
+    slug: 'پیشین',
+    province_id: 16
+  },
+  {
+    id: 570,
+    name: 'جالق',
+    slug: 'جالق',
+    province_id: 16
+  },
+  {
+    id: 571,
+    name: 'چابهار',
+    slug: 'چابهار',
+    province_id: 16
+  },
+  {
+    id: 572,
+    name: 'خاش',
+    slug: 'خاش',
+    province_id: 16
+  },
+  {
+    id: 573,
+    name: 'دوست محمد',
+    slug: 'دوست-محمد',
+    province_id: 16
+  },
+  {
+    id: 574,
+    name: 'راسک',
+    slug: 'راسک',
+    province_id: 16
+  },
+  {
+    id: 575,
+    name: 'زابل',
+    slug: 'زابل',
+    province_id: 16
+  },
+  {
+    id: 576,
+    name: 'زابلی',
+    slug: 'زابلی',
+    province_id: 16
+  },
+  {
+    id: 577,
+    name: 'زاهدان',
+    slug: 'زاهدان',
+    province_id: 16
+  },
+  {
+    id: 578,
+    name: 'زهک',
+    slug: 'زهک',
+    province_id: 16
+  },
+  {
+    id: 579,
+    name: 'سراوان',
+    slug: 'سراوان',
+    province_id: 16
+  },
+  {
+    id: 580,
+    name: 'سرباز',
+    slug: 'سرباز',
+    province_id: 16
+  },
+  {
+    id: 581,
+    name: 'سوران',
+    slug: 'سوران',
+    province_id: 16
+  },
+  {
+    id: 582,
+    name: 'سیرکان',
+    slug: 'سیرکان',
+    province_id: 16
+  },
+  {
+    id: 583,
+    name: 'علی اکبر',
+    slug: 'علی-اکبر',
+    province_id: 16
+  },
+  {
+    id: 584,
+    name: 'فنوج',
+    slug: 'فنوج',
+    province_id: 16
+  },
+  {
+    id: 585,
+    name: 'قصرقند',
+    slug: 'قصرقند',
+    province_id: 16
+  },
+  {
+    id: 586,
+    name: 'کنارک',
+    slug: 'کنارک',
+    province_id: 16
+  },
+  {
+    id: 587,
+    name: 'گشت',
+    slug: 'گشت',
+    province_id: 16
+  },
+  {
+    id: 588,
+    name: 'گلمورتی',
+    slug: 'گلمورتی',
+    province_id: 16
+  },
+  {
+    id: 589,
+    name: 'محمدان',
+    slug: 'محمدان',
+    province_id: 16
+  },
+  {
+    id: 590,
+    name: 'محمدآباد',
+    slug: 'سیستان-و-بلوچستان-محمدآباد',
+    province_id: 16
+  },
+  {
+    id: 591,
+    name: 'محمدی',
+    slug: 'محمدی',
+    province_id: 16
+  },
+  {
+    id: 592,
+    name: 'میرجاوه',
+    slug: 'میرجاوه',
+    province_id: 16
+  },
+  {
+    id: 593,
+    name: 'نصرت آباد',
+    slug: 'نصرت-آباد',
+    province_id: 16
+  },
+  {
+    id: 594,
+    name: 'نگور',
+    slug: 'نگور',
+    province_id: 16
+  },
+  {
+    id: 595,
+    name: 'نوک آباد',
+    slug: 'نوک-آباد',
+    province_id: 16
+  },
+  {
+    id: 596,
+    name: 'نیک شهر',
+    slug: 'نیک-شهر',
+    province_id: 16
+  },
+  {
+    id: 597,
+    name: 'هیدوچ',
+    slug: 'هیدوچ',
+    province_id: 16
+  },
+  {
+    id: 598,
+    name: 'اردکان',
+    slug: 'فارس-اردکان',
+    province_id: 17
+  },
+  {
+    id: 599,
+    name: 'ارسنجان',
+    slug: 'ارسنجان',
+    province_id: 17
+  },
+  {
+    id: 600,
+    name: 'استهبان',
+    slug: 'استهبان',
+    province_id: 17
+  },
+  {
+    id: 601,
+    name: 'اشکنان',
+    slug: 'اشکنان',
+    province_id: 17
+  },
+  {
+    id: 602,
+    name: 'افزر',
+    slug: 'افزر',
+    province_id: 17
+  },
+  {
+    id: 603,
+    name: 'اقلید',
+    slug: 'اقلید',
+    province_id: 17
+  },
+  {
+    id: 604,
+    name: 'امام شهر',
+    slug: 'امام-شهر',
+    province_id: 17
+  },
+  {
+    id: 605,
+    name: 'اهل',
+    slug: 'اهل',
+    province_id: 17
+  },
+  {
+    id: 606,
+    name: 'اوز',
+    slug: 'اوز',
+    province_id: 17
+  },
+  {
+    id: 607,
+    name: 'ایج',
+    slug: 'ایج',
+    province_id: 17
+  },
+  {
+    id: 608,
+    name: 'ایزدخواست',
+    slug: 'ایزدخواست',
+    province_id: 17
+  },
+  {
+    id: 609,
+    name: 'آباده',
+    slug: 'آباده',
+    province_id: 17
+  },
+  {
+    id: 610,
+    name: 'آباده طشک',
+    slug: 'آباده-طشک',
+    province_id: 17
+  },
+  {
+    id: 611,
+    name: 'باب انار',
+    slug: 'باب-انار',
+    province_id: 17
+  },
+  {
+    id: 612,
+    name: 'بالاده',
+    slug: 'فارس-بالاده',
+    province_id: 17
+  },
+  {
+    id: 613,
+    name: 'بنارویه',
+    slug: 'بنارویه',
+    province_id: 17
+  },
+  {
+    id: 614,
+    name: 'بهمن',
+    slug: 'بهمن',
+    province_id: 17
+  },
+  {
+    id: 615,
+    name: 'بوانات',
+    slug: 'بوانات',
+    province_id: 17
+  },
+  {
+    id: 616,
+    name: 'بیرم',
+    slug: 'بیرم',
+    province_id: 17
+  },
+  {
+    id: 617,
+    name: 'بیضا',
+    slug: 'بیضا',
+    province_id: 17
+  },
+  {
+    id: 618,
+    name: 'جنت شهر',
+    slug: 'جنت-شهر',
+    province_id: 17
+  },
+  {
+    id: 619,
+    name: 'جهرم',
+    slug: 'جهرم',
+    province_id: 17
+  },
+  {
+    id: 620,
+    name: 'جویم',
+    slug: 'جویم',
+    province_id: 17
+  },
+  {
+    id: 621,
+    name: 'زرین دشت',
+    slug: 'زرین-دشت',
+    province_id: 17
+  },
+  {
+    id: 622,
+    name: 'حسن آباد',
+    slug: 'فارس-حسن-آباد',
+    province_id: 17
+  },
+  {
+    id: 623,
+    name: 'خان زنیان',
+    slug: 'خان-زنیان',
+    province_id: 17
+  },
+  {
+    id: 624,
+    name: 'خاوران',
+    slug: 'خاوران',
+    province_id: 17
+  },
+  {
+    id: 625,
+    name: 'خرامه',
+    slug: 'خرامه',
+    province_id: 17
+  },
+  {
+    id: 626,
+    name: 'خشت',
+    slug: 'خشت',
+    province_id: 17
+  },
+  {
+    id: 627,
+    name: 'خنج',
+    slug: 'خنج',
+    province_id: 17
+  },
+  {
+    id: 628,
+    name: 'خور',
+    slug: 'فارس-خور',
+    province_id: 17
+  },
+  {
+    id: 629,
+    name: 'داراب',
+    slug: 'داراب',
+    province_id: 17
+  },
+  {
+    id: 630,
+    name: 'داریان',
+    slug: 'داریان',
+    province_id: 17
+  },
+  {
+    id: 631,
+    name: 'دبیران',
+    slug: 'دبیران',
+    province_id: 17
+  },
+  {
+    id: 632,
+    name: 'دژکرد',
+    slug: 'دژکرد',
+    province_id: 17
+  },
+  {
+    id: 633,
+    name: 'دهرم',
+    slug: 'دهرم',
+    province_id: 17
+  },
+  {
+    id: 634,
+    name: 'دوبرجی',
+    slug: 'دوبرجی',
+    province_id: 17
+  },
+  {
+    id: 635,
+    name: 'رامجرد',
+    slug: 'رامجرد',
+    province_id: 17
+  },
+  {
+    id: 636,
+    name: 'رونیز',
+    slug: 'رونیز',
+    province_id: 17
+  },
+  {
+    id: 637,
+    name: 'زاهدشهر',
+    slug: 'زاهدشهر',
+    province_id: 17
+  },
+  {
+    id: 638,
+    name: 'زرقان',
+    slug: 'زرقان',
+    province_id: 17
+  },
+  {
+    id: 639,
+    name: 'سده',
+    slug: 'سده',
+    province_id: 17
+  },
+  {
+    id: 640,
+    name: 'سروستان',
+    slug: 'سروستان',
+    province_id: 17
+  },
+  {
+    id: 641,
+    name: 'سعادت شهر',
+    slug: 'سعادت-شهر',
+    province_id: 17
+  },
+  {
+    id: 642,
+    name: 'سورمق',
+    slug: 'سورمق',
+    province_id: 17
+  },
+  {
+    id: 643,
+    name: 'سیدان',
+    slug: 'سیدان',
+    province_id: 17
+  },
+  {
+    id: 644,
+    name: 'ششده',
+    slug: 'ششده',
+    province_id: 17
+  },
+  {
+    id: 645,
+    name: 'شهرپیر',
+    slug: 'شهرپیر',
+    province_id: 17
+  },
+  {
+    id: 646,
+    name: 'شهرصدرا',
+    slug: 'شهرصدرا',
+    province_id: 17
+  },
+  {
+    id: 647,
+    name: 'شیراز',
+    slug: 'شیراز',
+    province_id: 17
+  },
+  {
+    id: 648,
+    name: 'صغاد',
+    slug: 'صغاد',
+    province_id: 17
+  },
+  {
+    id: 649,
+    name: 'صفاشهر',
+    slug: 'صفاشهر',
+    province_id: 17
+  },
+  {
+    id: 650,
+    name: 'علامرودشت',
+    slug: 'علامرودشت',
+    province_id: 17
+  },
+  {
+    id: 651,
+    name: 'فدامی',
+    slug: 'فدامی',
+    province_id: 17
+  },
+  {
+    id: 652,
+    name: 'فراشبند',
+    slug: 'فراشبند',
+    province_id: 17
+  },
+  {
+    id: 653,
+    name: 'فسا',
+    slug: 'فسا',
+    province_id: 17
+  },
+  {
+    id: 654,
+    name: 'فیروزآباد',
+    slug: 'فارس-فیروزآباد',
+    province_id: 17
+  },
+  {
+    id: 655,
+    name: 'قائمیه',
+    slug: 'قائمیه',
+    province_id: 17
+  },
+  {
+    id: 656,
+    name: 'قادرآباد',
+    slug: 'قادرآباد',
+    province_id: 17
+  },
+  {
+    id: 657,
+    name: 'قطب آباد',
+    slug: 'قطب-آباد',
+    province_id: 17
+  },
+  {
+    id: 658,
+    name: 'قطرویه',
+    slug: 'قطرویه',
+    province_id: 17
+  },
+  {
+    id: 659,
+    name: 'قیر',
+    slug: 'قیر',
+    province_id: 17
+  },
+  {
+    id: 660,
+    name: 'کارزین (فتح آباد)',
+    slug: 'کارزین-فتح-آباد',
+    province_id: 17
+  },
+  {
+    id: 661,
+    name: 'کازرون',
+    slug: 'کازرون',
+    province_id: 17
+  },
+  {
+    id: 662,
+    name: 'کامفیروز',
+    slug: 'کامفیروز',
+    province_id: 17
+  },
+  {
+    id: 663,
+    name: 'کره ای',
+    slug: 'کره-ای',
+    province_id: 17
+  },
+  {
+    id: 664,
+    name: 'کنارتخته',
+    slug: 'کنارتخته',
+    province_id: 17
+  },
+  {
+    id: 665,
+    name: 'کوار',
+    slug: 'کوار',
+    province_id: 17
+  },
+  {
+    id: 666,
+    name: 'گراش',
+    slug: 'گراش',
+    province_id: 17
+  },
+  {
+    id: 667,
+    name: 'گله دار',
+    slug: 'گله-دار',
+    province_id: 17
+  },
+  {
+    id: 668,
+    name: 'لار',
+    slug: 'لار',
+    province_id: 17
+  },
+  {
+    id: 669,
+    name: 'لامرد',
+    slug: 'لامرد',
+    province_id: 17
+  },
+  {
+    id: 670,
+    name: 'لپویی',
+    slug: 'لپویی',
+    province_id: 17
+  },
+  {
+    id: 671,
+    name: 'لطیفی',
+    slug: 'لطیفی',
+    province_id: 17
+  },
+  {
+    id: 672,
+    name: 'مبارک آباددیز',
+    slug: 'مبارک-آباددیز',
+    province_id: 17
+  },
+  {
+    id: 673,
+    name: 'مرودشت',
+    slug: 'مرودشت',
+    province_id: 17
+  },
+  {
+    id: 674,
+    name: 'مشکان',
+    slug: 'مشکان',
+    province_id: 17
+  },
+  {
+    id: 675,
+    name: 'مصیری',
+    slug: 'مصیری',
+    province_id: 17
+  },
+  {
+    id: 676,
+    name: 'مهر',
+    slug: 'مهر',
+    province_id: 17
+  },
+  {
+    id: 677,
+    name: 'میمند',
+    slug: 'میمند',
+    province_id: 17
+  },
+  {
+    id: 678,
+    name: 'نوبندگان',
+    slug: 'نوبندگان',
+    province_id: 17
+  },
+  {
+    id: 679,
+    name: 'نوجین',
+    slug: 'نوجین',
+    province_id: 17
+  },
+  {
+    id: 680,
+    name: 'نودان',
+    slug: 'نودان',
+    province_id: 17
+  },
+  {
+    id: 681,
+    name: 'نورآباد',
+    slug: 'فارس-نورآباد',
+    province_id: 17
+  },
+  {
+    id: 682,
+    name: 'نی ریز',
+    slug: 'نی-ریز',
+    province_id: 17
+  },
+  {
+    id: 683,
+    name: 'وراوی',
+    slug: 'وراوی',
+    province_id: 17
+  },
+  {
+    id: 684,
+    name: 'ارداق',
+    slug: 'ارداق',
+    province_id: 18
+  },
+  {
+    id: 685,
+    name: 'اسفرورین',
+    slug: 'اسفرورین',
+    province_id: 18
+  },
+  {
+    id: 686,
+    name: 'اقبالیه',
+    slug: 'اقبالیه',
+    province_id: 18
+  },
+  {
+    id: 687,
+    name: 'الوند',
+    slug: 'الوند',
+    province_id: 18
+  },
+  {
+    id: 688,
+    name: 'آبگرم',
+    slug: 'آبگرم',
+    province_id: 18
+  },
+  {
+    id: 689,
+    name: 'آبیک',
+    slug: 'آبیک',
+    province_id: 18
+  },
+  {
+    id: 690,
+    name: 'آوج',
+    slug: 'آوج',
+    province_id: 18
+  },
+  {
+    id: 691,
+    name: 'بوئین زهرا',
+    slug: 'بوئین-زهرا',
+    province_id: 18
+  },
+  {
+    id: 692,
+    name: 'بیدستان',
+    slug: 'بیدستان',
+    province_id: 18
+  },
+  {
+    id: 693,
+    name: 'تاکستان',
+    slug: 'تاکستان',
+    province_id: 18
+  },
+  {
+    id: 694,
+    name: 'خاکعلی',
+    slug: 'خاکعلی',
+    province_id: 18
+  },
+  {
+    id: 695,
+    name: 'خرمدشت',
+    slug: 'خرمدشت',
+    province_id: 18
+  },
+  {
+    id: 696,
+    name: 'دانسفهان',
+    slug: 'دانسفهان',
+    province_id: 18
+  },
+  {
+    id: 697,
+    name: 'رازمیان',
+    slug: 'رازمیان',
+    province_id: 18
+  },
+  {
+    id: 698,
+    name: 'سگزآباد',
+    slug: 'سگزآباد',
+    province_id: 18
+  },
+  {
+    id: 699,
+    name: 'سیردان',
+    slug: 'سیردان',
+    province_id: 18
+  },
+  {
+    id: 700,
+    name: 'شال',
+    slug: 'شال',
+    province_id: 18
+  },
+  {
+    id: 701,
+    name: 'شریفیه',
+    slug: 'شریفیه',
+    province_id: 18
+  },
+  {
+    id: 702,
+    name: 'ضیاآباد',
+    slug: 'ضیاآباد',
+    province_id: 18
+  },
+  {
+    id: 703,
+    name: 'قزوین',
+    slug: 'قزوین',
+    province_id: 18
+  },
+  {
+    id: 704,
+    name: 'کوهین',
+    slug: 'کوهین',
+    province_id: 18
+  },
+  {
+    id: 705,
+    name: 'محمدیه',
+    slug: 'محمدیه',
+    province_id: 18
+  },
+  {
+    id: 706,
+    name: 'محمودآباد نمونه',
+    slug: 'محمودآباد-نمونه',
+    province_id: 18
+  },
+  {
+    id: 707,
+    name: 'معلم کلایه',
+    slug: 'معلم-کلایه',
+    province_id: 18
+  },
+  {
+    id: 708,
+    name: 'نرجه',
+    slug: 'نرجه',
+    province_id: 18
+  },
+  {
+    id: 709,
+    name: 'جعفریه',
+    slug: 'جعفریه',
+    province_id: 19
+  },
+  {
+    id: 710,
+    name: 'دستجرد',
+    slug: 'دستجرد',
+    province_id: 19
+  },
+  {
+    id: 711,
+    name: 'سلفچگان',
+    slug: 'سلفچگان',
+    province_id: 19
+  },
+  {
+    id: 712,
+    name: 'قم',
+    slug: 'قم',
+    province_id: 19
+  },
+  {
+    id: 713,
+    name: 'قنوات',
+    slug: 'قنوات',
+    province_id: 19
+  },
+  {
+    id: 714,
+    name: 'کهک',
+    slug: 'کهک',
+    province_id: 19
+  },
+  {
+    id: 715,
+    name: 'آرمرده',
+    slug: 'آرمرده',
+    province_id: 20
+  },
+  {
+    id: 716,
+    name: 'بابارشانی',
+    slug: 'بابارشانی',
+    province_id: 20
+  },
+  {
+    id: 717,
+    name: 'بانه',
+    slug: 'بانه',
+    province_id: 20
+  },
+  {
+    id: 718,
+    name: 'بلبان آباد',
+    slug: 'بلبان-آباد',
+    province_id: 20
+  },
+  {
+    id: 719,
+    name: 'بوئین سفلی',
+    slug: 'بوئین-سفلی',
+    province_id: 20
+  },
+  {
+    id: 720,
+    name: 'بیجار',
+    slug: 'بیجار',
+    province_id: 20
+  },
+  {
+    id: 721,
+    name: 'چناره',
+    slug: 'چناره',
+    province_id: 20
+  },
+  {
+    id: 722,
+    name: 'دزج',
+    slug: 'دزج',
+    province_id: 20
+  },
+  {
+    id: 723,
+    name: 'دلبران',
+    slug: 'دلبران',
+    province_id: 20
+  },
+  {
+    id: 724,
+    name: 'دهگلان',
+    slug: 'دهگلان',
+    province_id: 20
+  },
+  {
+    id: 725,
+    name: 'دیواندره',
+    slug: 'دیواندره',
+    province_id: 20
+  },
+  {
+    id: 726,
+    name: 'زرینه',
+    slug: 'زرینه',
+    province_id: 20
+  },
+  {
+    id: 727,
+    name: 'سروآباد',
+    slug: 'سروآباد',
+    province_id: 20
+  },
+  {
+    id: 728,
+    name: 'سریش آباد',
+    slug: 'سریش-آباد',
+    province_id: 20
+  },
+  {
+    id: 729,
+    name: 'سقز',
+    slug: 'سقز',
+    province_id: 20
+  },
+  {
+    id: 730,
+    name: 'سنندج',
+    slug: 'سنندج',
+    province_id: 20
+  },
+  {
+    id: 731,
+    name: 'شویشه',
+    slug: 'شویشه',
+    province_id: 20
+  },
+  {
+    id: 732,
+    name: 'صاحب',
+    slug: 'صاحب',
+    province_id: 20
+  },
+  {
+    id: 733,
+    name: 'قروه',
+    slug: 'قروه',
+    province_id: 20
+  },
+  {
+    id: 734,
+    name: 'کامیاران',
+    slug: 'کامیاران',
+    province_id: 20
+  },
+  {
+    id: 735,
+    name: 'کانی دینار',
+    slug: 'کانی-دینار',
+    province_id: 20
+  },
+  {
+    id: 736,
+    name: 'کانی سور',
+    slug: 'کانی-سور',
+    province_id: 20
+  },
+  {
+    id: 737,
+    name: 'مریوان',
+    slug: 'مریوان',
+    province_id: 20
+  },
+  {
+    id: 738,
+    name: 'موچش',
+    slug: 'موچش',
+    province_id: 20
+  },
+  {
+    id: 739,
+    name: 'یاسوکند',
+    slug: 'یاسوکند',
+    province_id: 20
+  },
+  {
+    id: 740,
+    name: 'اختیارآباد',
+    slug: 'اختیارآباد',
+    province_id: 21
+  },
+  {
+    id: 741,
+    name: 'ارزوئیه',
+    slug: 'ارزوئیه',
+    province_id: 21
+  },
+  {
+    id: 742,
+    name: 'امین شهر',
+    slug: 'امین-شهر',
+    province_id: 21
+  },
+  {
+    id: 743,
+    name: 'انار',
+    slug: 'انار',
+    province_id: 21
+  },
+  {
+    id: 744,
+    name: 'اندوهجرد',
+    slug: 'اندوهجرد',
+    province_id: 21
+  },
+  {
+    id: 745,
+    name: 'باغین',
+    slug: 'باغین',
+    province_id: 21
+  },
+  {
+    id: 746,
+    name: 'بافت',
+    slug: 'بافت',
+    province_id: 21
+  },
+  {
+    id: 747,
+    name: 'بردسیر',
+    slug: 'بردسیر',
+    province_id: 21
+  },
+  {
+    id: 748,
+    name: 'بروات',
+    slug: 'بروات',
+    province_id: 21
+  },
+  {
+    id: 749,
+    name: 'بزنجان',
+    slug: 'بزنجان',
+    province_id: 21
+  },
+  {
+    id: 750,
+    name: 'بم',
+    slug: 'بم',
+    province_id: 21
+  },
+  {
+    id: 751,
+    name: 'بهرمان',
+    slug: 'بهرمان',
+    province_id: 21
+  },
+  {
+    id: 752,
+    name: 'پاریز',
+    slug: 'پاریز',
+    province_id: 21
+  },
+  {
+    id: 753,
+    name: 'جبالبارز',
+    slug: 'جبالبارز',
+    province_id: 21
+  },
+  {
+    id: 754,
+    name: 'جوپار',
+    slug: 'جوپار',
+    province_id: 21
+  },
+  {
+    id: 755,
+    name: 'جوزم',
+    slug: 'جوزم',
+    province_id: 21
+  },
+  {
+    id: 756,
+    name: 'جیرفت',
+    slug: 'جیرفت',
+    province_id: 21
+  },
+  {
+    id: 757,
+    name: 'چترود',
+    slug: 'چترود',
+    province_id: 21
+  },
+  {
+    id: 758,
+    name: 'خاتون آباد',
+    slug: 'خاتون-آباد',
+    province_id: 21
+  },
+  {
+    id: 759,
+    name: 'خانوک',
+    slug: 'خانوک',
+    province_id: 21
+  },
+  {
+    id: 760,
+    name: 'خورسند',
+    slug: 'خورسند',
+    province_id: 21
+  },
+  {
+    id: 761,
+    name: 'درب بهشت',
+    slug: 'درب-بهشت',
+    province_id: 21
+  },
+  {
+    id: 762,
+    name: 'دهج',
+    slug: 'دهج',
+    province_id: 21
+  },
+  {
+    id: 763,
+    name: 'رابر',
+    slug: 'رابر',
+    province_id: 21
+  },
+  {
+    id: 764,
+    name: 'راور',
+    slug: 'راور',
+    province_id: 21
+  },
+  {
+    id: 765,
+    name: 'راین',
+    slug: 'راین',
+    province_id: 21
+  },
+  {
+    id: 766,
+    name: 'رفسنجان',
+    slug: 'رفسنجان',
+    province_id: 21
+  },
+  {
+    id: 767,
+    name: 'رودبار',
+    slug: 'کرمان-رودبار',
+    province_id: 21
+  },
+  {
+    id: 768,
+    name: 'ریحان شهر',
+    slug: 'ریحان-شهر',
+    province_id: 21
+  },
+  {
+    id: 769,
+    name: 'زرند',
+    slug: 'زرند',
+    province_id: 21
+  },
+  {
+    id: 770,
+    name: 'زنگی آباد',
+    slug: 'زنگی-آباد',
+    province_id: 21
+  },
+  {
+    id: 771,
+    name: 'زیدآباد',
+    slug: 'زیدآباد',
+    province_id: 21
+  },
+  {
+    id: 772,
+    name: 'سیرجان',
+    slug: 'سیرجان',
+    province_id: 21
+  },
+  {
+    id: 773,
+    name: 'شهداد',
+    slug: 'شهداد',
+    province_id: 21
+  },
+  {
+    id: 774,
+    name: 'شهربابک',
+    slug: 'شهربابک',
+    province_id: 21
+  },
+  {
+    id: 775,
+    name: 'صفائیه',
+    slug: 'صفائیه',
+    province_id: 21
+  },
+  {
+    id: 776,
+    name: 'عنبرآباد',
+    slug: 'عنبرآباد',
+    province_id: 21
+  },
+  {
+    id: 777,
+    name: 'فاریاب',
+    slug: 'فاریاب',
+    province_id: 21
+  },
+  {
+    id: 778,
+    name: 'فهرج',
+    slug: 'فهرج',
+    province_id: 21
+  },
+  {
+    id: 779,
+    name: 'قلعه گنج',
+    slug: 'قلعه-گنج',
+    province_id: 21
+  },
+  {
+    id: 780,
+    name: 'کاظم آباد',
+    slug: 'کاظم-آباد',
+    province_id: 21
+  },
+  {
+    id: 781,
+    name: 'کرمان',
+    slug: 'کرمان',
+    province_id: 21
+  },
+  {
+    id: 782,
+    name: 'کشکوئیه',
+    slug: 'کشکوئیه',
+    province_id: 21
+  },
+  {
+    id: 783,
+    name: 'کهنوج',
+    slug: 'کهنوج',
+    province_id: 21
+  },
+  {
+    id: 784,
+    name: 'کوهبنان',
+    slug: 'کوهبنان',
+    province_id: 21
+  },
+  {
+    id: 785,
+    name: 'کیانشهر',
+    slug: 'کیانشهر',
+    province_id: 21
+  },
+  {
+    id: 786,
+    name: 'گلباف',
+    slug: 'گلباف',
+    province_id: 21
+  },
+  {
+    id: 787,
+    name: 'گلزار',
+    slug: 'گلزار',
+    province_id: 21
+  },
+  {
+    id: 788,
+    name: 'لاله زار',
+    slug: 'لاله-زار',
+    province_id: 21
+  },
+  {
+    id: 789,
+    name: 'ماهان',
+    slug: 'ماهان',
+    province_id: 21
+  },
+  {
+    id: 790,
+    name: 'محمدآباد',
+    slug: 'کرمان-محمدآباد',
+    province_id: 21
+  },
+  {
+    id: 791,
+    name: 'محی آباد',
+    slug: 'محی-آباد',
+    province_id: 21
+  },
+  {
+    id: 792,
+    name: 'مردهک',
+    slug: 'مردهک',
+    province_id: 21
+  },
+  {
+    id: 793,
+    name: 'مس سرچشمه',
+    slug: 'مس-سرچشمه',
+    province_id: 21
+  },
+  {
+    id: 794,
+    name: 'منوجان',
+    slug: 'منوجان',
+    province_id: 21
+  },
+  {
+    id: 795,
+    name: 'نجف شهر',
+    slug: 'نجف-شهر',
+    province_id: 21
+  },
+  {
+    id: 796,
+    name: 'نرماشیر',
+    slug: 'نرماشیر',
+    province_id: 21
+  },
+  {
+    id: 797,
+    name: 'نظام شهر',
+    slug: 'نظام-شهر',
+    province_id: 21
+  },
+  {
+    id: 798,
+    name: 'نگار',
+    slug: 'نگار',
+    province_id: 21
+  },
+  {
+    id: 799,
+    name: 'نودژ',
+    slug: 'نودژ',
+    province_id: 21
+  },
+  {
+    id: 800,
+    name: 'هجدک',
+    slug: 'هجدک',
+    province_id: 21
+  },
+  {
+    id: 801,
+    name: 'یزدان شهر',
+    slug: 'یزدان-شهر',
+    province_id: 21
+  },
+  {
+    id: 802,
+    name: 'ازگله',
+    slug: 'ازگله',
+    province_id: 22
+  },
+  {
+    id: 803,
+    name: 'اسلام آباد غرب',
+    slug: 'اسلام-آباد-غرب',
+    province_id: 22
+  },
+  {
+    id: 804,
+    name: 'باینگان',
+    slug: 'باینگان',
+    province_id: 22
+  },
+  {
+    id: 805,
+    name: 'بیستون',
+    slug: 'بیستون',
+    province_id: 22
+  },
+  {
+    id: 806,
+    name: 'پاوه',
+    slug: 'پاوه',
+    province_id: 22
+  },
+  {
+    id: 807,
+    name: 'تازه آباد',
+    slug: 'تازه-آباد',
+    province_id: 22
+  },
+  {
+    id: 808,
+    name: 'جوان رود',
+    slug: 'جوان-رود',
+    province_id: 22
+  },
+  {
+    id: 809,
+    name: 'حمیل',
+    slug: 'حمیل',
+    province_id: 22
+  },
+  {
+    id: 810,
+    name: 'ماهیدشت',
+    slug: 'ماهیدشت',
+    province_id: 22
+  },
+  {
+    id: 811,
+    name: 'روانسر',
+    slug: 'روانسر',
+    province_id: 22
+  },
+  {
+    id: 812,
+    name: 'سرپل ذهاب',
+    slug: 'سرپل-ذهاب',
+    province_id: 22
+  },
+  {
+    id: 813,
+    name: 'سرمست',
+    slug: 'سرمست',
+    province_id: 22
+  },
+  {
+    id: 814,
+    name: 'سطر',
+    slug: 'سطر',
+    province_id: 22
+  },
+  {
+    id: 815,
+    name: 'سنقر',
+    slug: 'سنقر',
+    province_id: 22
+  },
+  {
+    id: 816,
+    name: 'سومار',
+    slug: 'سومار',
+    province_id: 22
+  },
+  {
+    id: 817,
+    name: 'شاهو',
+    slug: 'شاهو',
+    province_id: 22
+  },
+  {
+    id: 818,
+    name: 'صحنه',
+    slug: 'صحنه',
+    province_id: 22
+  },
+  {
+    id: 819,
+    name: 'قصرشیرین',
+    slug: 'قصرشیرین',
+    province_id: 22
+  },
+  {
+    id: 820,
+    name: 'کرمانشاه',
+    slug: 'کرمانشاه',
+    province_id: 22
+  },
+  {
+    id: 821,
+    name: 'کرندغرب',
+    slug: 'کرندغرب',
+    province_id: 22
+  },
+  {
+    id: 822,
+    name: 'کنگاور',
+    slug: 'کنگاور',
+    province_id: 22
+  },
+  {
+    id: 823,
+    name: 'کوزران',
+    slug: 'کوزران',
+    province_id: 22
+  },
+  {
+    id: 824,
+    name: 'گهواره',
+    slug: 'گهواره',
+    province_id: 22
+  },
+  {
+    id: 825,
+    name: 'گیلانغرب',
+    slug: 'گیلانغرب',
+    province_id: 22
+  },
+  {
+    id: 826,
+    name: 'میان راهان',
+    slug: 'میان-راهان',
+    province_id: 22
+  },
+  {
+    id: 827,
+    name: 'نودشه',
+    slug: 'نودشه',
+    province_id: 22
+  },
+  {
+    id: 828,
+    name: 'نوسود',
+    slug: 'نوسود',
+    province_id: 22
+  },
+  {
+    id: 829,
+    name: 'هرسین',
+    slug: 'هرسین',
+    province_id: 22
+  },
+  {
+    id: 830,
+    name: 'هلشی',
+    slug: 'هلشی',
+    province_id: 22
+  },
+  {
+    id: 831,
+    name: 'باشت',
+    slug: 'باشت',
+    province_id: 23
+  },
+  {
+    id: 832,
+    name: 'پاتاوه',
+    slug: 'پاتاوه',
+    province_id: 23
+  },
+  {
+    id: 833,
+    name: 'چرام',
+    slug: 'چرام',
+    province_id: 23
+  },
+  {
+    id: 834,
+    name: 'چیتاب',
+    slug: 'چیتاب',
+    province_id: 23
+  },
+  {
+    id: 835,
+    name: 'دهدشت',
+    slug: 'دهدشت',
+    province_id: 23
+  },
+  {
+    id: 836,
+    name: 'دوگنبدان',
+    slug: 'دوگنبدان',
+    province_id: 23
+  },
+  {
+    id: 837,
+    name: 'دیشموک',
+    slug: 'دیشموک',
+    province_id: 23
+  },
+  {
+    id: 838,
+    name: 'سوق',
+    slug: 'سوق',
+    province_id: 23
+  },
+  {
+    id: 839,
+    name: 'سی سخت',
+    slug: 'سی-سخت',
+    province_id: 23
+  },
+  {
+    id: 840,
+    name: 'قلعه رئیسی',
+    slug: 'قلعه-رئیسی',
+    province_id: 23
+  },
+  {
+    id: 841,
+    name: 'گراب سفلی',
+    slug: 'گراب-سفلی',
+    province_id: 23
+  },
+  {
+    id: 842,
+    name: 'لنده',
+    slug: 'لنده',
+    province_id: 23
+  },
+  {
+    id: 843,
+    name: 'لیکک',
+    slug: 'لیکک',
+    province_id: 23
+  },
+  {
+    id: 844,
+    name: 'مادوان',
+    slug: 'مادوان',
+    province_id: 23
+  },
+  {
+    id: 845,
+    name: 'مارگون',
+    slug: 'مارگون',
+    province_id: 23
+  },
+  {
+    id: 846,
+    name: 'یاسوج',
+    slug: 'یاسوج',
+    province_id: 23
+  },
+  {
+    id: 847,
+    name: 'انبارآلوم',
+    slug: 'انبارآلوم',
+    province_id: 24
+  },
+  {
+    id: 848,
+    name: 'اینچه برون',
+    slug: 'اینچه-برون',
+    province_id: 24
+  },
+  {
+    id: 849,
+    name: 'آزادشهر',
+    slug: 'آزادشهر',
+    province_id: 24
+  },
+  {
+    id: 850,
+    name: 'آق قلا',
+    slug: 'آق-قلا',
+    province_id: 24
+  },
+  {
+    id: 851,
+    name: 'بندرترکمن',
+    slug: 'بندرترکمن',
+    province_id: 24
+  },
+  {
+    id: 852,
+    name: 'بندرگز',
+    slug: 'بندرگز',
+    province_id: 24
+  },
+  {
+    id: 853,
+    name: 'جلین',
+    slug: 'جلین',
+    province_id: 24
+  },
+  {
+    id: 854,
+    name: 'خان ببین',
+    slug: 'خان-ببین',
+    province_id: 24
+  },
+  {
+    id: 855,
+    name: 'دلند',
+    slug: 'دلند',
+    province_id: 24
+  },
+  {
+    id: 856,
+    name: 'رامیان',
+    slug: 'رامیان',
+    province_id: 24
+  },
+  {
+    id: 857,
+    name: 'سرخنکلاته',
+    slug: 'سرخنکلاته',
+    province_id: 24
+  },
+  {
+    id: 858,
+    name: 'سیمین شهر',
+    slug: 'سیمین-شهر',
+    province_id: 24
+  },
+  {
+    id: 859,
+    name: 'علی آباد کتول',
+    slug: 'علی-آباد-کتول',
+    province_id: 24
+  },
+  {
+    id: 860,
+    name: 'فاضل آباد',
+    slug: 'فاضل-آباد',
+    province_id: 24
+  },
+  {
+    id: 861,
+    name: 'کردکوی',
+    slug: 'کردکوی',
+    province_id: 24
+  },
+  {
+    id: 862,
+    name: 'کلاله',
+    slug: 'کلاله',
+    province_id: 24
+  },
+  {
+    id: 863,
+    name: 'گالیکش',
+    slug: 'گالیکش',
+    province_id: 24
+  },
+  {
+    id: 864,
+    name: 'گرگان',
+    slug: 'گرگان',
+    province_id: 24
+  },
+  {
+    id: 865,
+    name: 'گمیش تپه',
+    slug: 'گمیش-تپه',
+    province_id: 24
+  },
+  {
+    id: 866,
+    name: 'گنبدکاووس',
+    slug: 'گنبدکاووس',
+    province_id: 24
+  },
+  {
+    id: 867,
+    name: 'مراوه',
+    slug: 'مراوه',
+    province_id: 24
+  },
+  {
+    id: 868,
+    name: 'مینودشت',
+    slug: 'مینودشت',
+    province_id: 24
+  },
+  {
+    id: 869,
+    name: 'نگین شهر',
+    slug: 'نگین-شهر',
+    province_id: 24
+  },
+  {
+    id: 870,
+    name: 'نوده خاندوز',
+    slug: 'نوده-خاندوز',
+    province_id: 24
+  },
+  {
+    id: 871,
+    name: 'نوکنده',
+    slug: 'نوکنده',
+    province_id: 24
+  },
+  {
+    id: 872,
+    name: 'ازنا',
+    slug: 'ازنا',
+    province_id: 25
+  },
+  {
+    id: 873,
+    name: 'اشترینان',
+    slug: 'اشترینان',
+    province_id: 25
+  },
+  {
+    id: 874,
+    name: 'الشتر',
+    slug: 'الشتر',
+    province_id: 25
+  },
+  {
+    id: 875,
+    name: 'الیگودرز',
+    slug: 'الیگودرز',
+    province_id: 25
+  },
+  {
+    id: 876,
+    name: 'بروجرد',
+    slug: 'بروجرد',
+    province_id: 25
+  },
+  {
+    id: 877,
+    name: 'پلدختر',
+    slug: 'پلدختر',
+    province_id: 25
+  },
+  {
+    id: 878,
+    name: 'چالانچولان',
+    slug: 'چالانچولان',
+    province_id: 25
+  },
+  {
+    id: 879,
+    name: 'چغلوندی',
+    slug: 'چغلوندی',
+    province_id: 25
+  },
+  {
+    id: 880,
+    name: 'چقابل',
+    slug: 'چقابل',
+    province_id: 25
+  },
+  {
+    id: 881,
+    name: 'خرم آباد',
+    slug: 'لرستان-خرم-آباد',
+    province_id: 25
+  },
+  {
+    id: 882,
+    name: 'درب گنبد',
+    slug: 'درب-گنبد',
+    province_id: 25
+  },
+  {
+    id: 883,
+    name: 'دورود',
+    slug: 'دورود',
+    province_id: 25
+  },
+  {
+    id: 884,
+    name: 'زاغه',
+    slug: 'زاغه',
+    province_id: 25
+  },
+  {
+    id: 885,
+    name: 'سپیددشت',
+    slug: 'سپیددشت',
+    province_id: 25
+  },
+  {
+    id: 886,
+    name: 'سراب دوره',
+    slug: 'سراب-دوره',
+    province_id: 25
+  },
+  {
+    id: 887,
+    name: 'فیروزآباد',
+    slug: 'لرستان-فیروزآباد',
+    province_id: 25
+  },
+  {
+    id: 888,
+    name: 'کونانی',
+    slug: 'کونانی',
+    province_id: 25
+  },
+  {
+    id: 889,
+    name: 'کوهدشت',
+    slug: 'کوهدشت',
+    province_id: 25
+  },
+  {
+    id: 890,
+    name: 'گراب',
+    slug: 'گراب',
+    province_id: 25
+  },
+  {
+    id: 891,
+    name: 'معمولان',
+    slug: 'معمولان',
+    province_id: 25
+  },
+  {
+    id: 892,
+    name: 'مومن آباد',
+    slug: 'مومن-آباد',
+    province_id: 25
+  },
+  {
+    id: 893,
+    name: 'نورآباد',
+    slug: 'لرستان-نورآباد',
+    province_id: 25
+  },
+  {
+    id: 894,
+    name: 'ویسیان',
+    slug: 'ویسیان',
+    province_id: 25
+  },
+  {
+    id: 895,
+    name: 'احمدسرگوراب',
+    slug: 'احمدسرگوراب',
+    province_id: 26
+  },
+  {
+    id: 896,
+    name: 'اسالم',
+    slug: 'اسالم',
+    province_id: 26
+  },
+  {
+    id: 897,
+    name: 'اطاقور',
+    slug: 'اطاقور',
+    province_id: 26
+  },
+  {
+    id: 898,
+    name: 'املش',
+    slug: 'املش',
+    province_id: 26
+  },
+  {
+    id: 899,
+    name: 'آستارا',
+    slug: 'آستارا',
+    province_id: 26
+  },
+  {
+    id: 900,
+    name: 'آستانه اشرفیه',
+    slug: 'آستانه-اشرفیه',
+    province_id: 26
+  },
+  {
+    id: 901,
+    name: 'بازار جمعه',
+    slug: 'بازار-جمعه',
+    province_id: 26
+  },
+  {
+    id: 902,
+    name: 'بره سر',
+    slug: 'بره-سر',
+    province_id: 26
+  },
+  {
+    id: 903,
+    name: 'بندرانزلی',
+    slug: 'بندرانزلی',
+    province_id: 26
+  },
+  {
+    id: 906,
+    name: 'پره سر',
+    slug: 'پره-سر',
+    province_id: 26
+  },
+  {
+    id: 907,
+    name: 'تالش',
+    slug: 'تالش',
+    province_id: 26
+  },
+  {
+    id: 908,
+    name: 'توتکابن',
+    slug: 'توتکابن',
+    province_id: 26
+  },
+  {
+    id: 909,
+    name: 'جیرنده',
+    slug: 'جیرنده',
+    province_id: 26
+  },
+  {
+    id: 910,
+    name: 'چابکسر',
+    slug: 'چابکسر',
+    province_id: 26
+  },
+  {
+    id: 911,
+    name: 'چاف و چمخاله',
+    slug: 'چاف-و-چمخاله',
+    province_id: 26
+  },
+  {
+    id: 912,
+    name: 'چوبر',
+    slug: 'چوبر',
+    province_id: 26
+  },
+  {
+    id: 913,
+    name: 'حویق',
+    slug: 'حویق',
+    province_id: 26
+  },
+  {
+    id: 914,
+    name: 'خشکبیجار',
+    slug: 'خشکبیجار',
+    province_id: 26
+  },
+  {
+    id: 915,
+    name: 'خمام',
+    slug: 'خمام',
+    province_id: 26
+  },
+  {
+    id: 916,
+    name: 'دیلمان',
+    slug: 'دیلمان',
+    province_id: 26
+  },
+  {
+    id: 917,
+    name: 'رانکوه',
+    slug: 'رانکوه',
+    province_id: 26
+  },
+  {
+    id: 918,
+    name: 'رحیم آباد',
+    slug: 'رحیم-آباد',
+    province_id: 26
+  },
+  {
+    id: 919,
+    name: 'رستم آباد',
+    slug: 'رستم-آباد',
+    province_id: 26
+  },
+  {
+    id: 920,
+    name: 'رشت',
+    slug: 'رشت',
+    province_id: 26
+  },
+  {
+    id: 921,
+    name: 'رضوانشهر',
+    slug: 'گیلان-رضوانشهر',
+    province_id: 26
+  },
+  {
+    id: 922,
+    name: 'رودبار',
+    slug: 'گیلان-رودبار',
+    province_id: 26
+  },
+  {
+    id: 923,
+    name: 'رودبنه',
+    slug: 'رودبنه',
+    province_id: 26
+  },
+  {
+    id: 924,
+    name: 'رودسر',
+    slug: 'رودسر',
+    province_id: 26
+  },
+  {
+    id: 925,
+    name: 'سنگر',
+    slug: 'سنگر',
+    province_id: 26
+  },
+  {
+    id: 926,
+    name: 'سیاهکل',
+    slug: 'سیاهکل',
+    province_id: 26
+  },
+  {
+    id: 927,
+    name: 'شفت',
+    slug: 'شفت',
+    province_id: 26
+  },
+  {
+    id: 928,
+    name: 'شلمان',
+    slug: 'شلمان',
+    province_id: 26
+  },
+  {
+    id: 929,
+    name: 'صومعه سرا',
+    slug: 'صومعه-سرا',
+    province_id: 26
+  },
+  {
+    id: 930,
+    name: 'فومن',
+    slug: 'فومن',
+    province_id: 26
+  },
+  {
+    id: 931,
+    name: 'کلاچای',
+    slug: 'کلاچای',
+    province_id: 26
+  },
+  {
+    id: 932,
+    name: 'کوچصفهان',
+    slug: 'کوچصفهان',
+    province_id: 26
+  },
+  {
+    id: 933,
+    name: 'کومله',
+    slug: 'کومله',
+    province_id: 26
+  },
+  {
+    id: 934,
+    name: 'کیاشهر',
+    slug: 'کیاشهر',
+    province_id: 26
+  },
+  {
+    id: 935,
+    name: 'گوراب زرمیخ',
+    slug: 'گوراب-زرمیخ',
+    province_id: 26
+  },
+  {
+    id: 936,
+    name: 'لاهیجان',
+    slug: 'لاهیجان',
+    province_id: 26
+  },
+  {
+    id: 937,
+    name: 'لشت نشا',
+    slug: 'لشت-نشا',
+    province_id: 26
+  },
+  {
+    id: 938,
+    name: 'لنگرود',
+    slug: 'لنگرود',
+    province_id: 26
+  },
+  {
+    id: 939,
+    name: 'لوشان',
+    slug: 'لوشان',
+    province_id: 26
+  },
+  {
+    id: 940,
+    name: 'لولمان',
+    slug: 'لولمان',
+    province_id: 26
+  },
+  {
+    id: 941,
+    name: 'لوندویل',
+    slug: 'لوندویل',
+    province_id: 26
+  },
+  {
+    id: 942,
+    name: 'لیسار',
+    slug: 'لیسار',
+    province_id: 26
+  },
+  {
+    id: 943,
+    name: 'ماسال',
+    slug: 'ماسال',
+    province_id: 26
+  },
+  {
+    id: 944,
+    name: 'ماسوله',
+    slug: 'ماسوله',
+    province_id: 26
+  },
+  {
+    id: 945,
+    name: 'مرجقل',
+    slug: 'مرجقل',
+    province_id: 26
+  },
+  {
+    id: 946,
+    name: 'منجیل',
+    slug: 'منجیل',
+    province_id: 26
+  },
+  {
+    id: 947,
+    name: 'واجارگاه',
+    slug: 'واجارگاه',
+    province_id: 26
+  },
+  {
+    id: 948,
+    name: 'امیرکلا',
+    slug: 'امیرکلا',
+    province_id: 27
+  },
+  {
+    id: 949,
+    name: 'ایزدشهر',
+    slug: 'ایزدشهر',
+    province_id: 27
+  },
+  {
+    id: 950,
+    name: 'آلاشت',
+    slug: 'آلاشت',
+    province_id: 27
+  },
+  {
+    id: 951,
+    name: 'آمل',
+    slug: 'آمل',
+    province_id: 27
+  },
+  {
+    id: 952,
+    name: 'بابل',
+    slug: 'بابل',
+    province_id: 27
+  },
+  {
+    id: 953,
+    name: 'بابلسر',
+    slug: 'بابلسر',
+    province_id: 27
+  },
+  {
+    id: 954,
+    name: 'بلده',
+    slug: 'مازندران-بلده',
+    province_id: 27
+  },
+  {
+    id: 955,
+    name: 'بهشهر',
+    slug: 'بهشهر',
+    province_id: 27
+  },
+  {
+    id: 956,
+    name: 'بهنمیر',
+    slug: 'بهنمیر',
+    province_id: 27
+  },
+  {
+    id: 957,
+    name: 'پل سفید',
+    slug: 'پل-سفید',
+    province_id: 27
+  },
+  {
+    id: 958,
+    name: 'تنکابن',
+    slug: 'تنکابن',
+    province_id: 27
+  },
+  {
+    id: 959,
+    name: 'جویبار',
+    slug: 'جویبار',
+    province_id: 27
+  },
+  {
+    id: 960,
+    name: 'چالوس',
+    slug: 'چالوس',
+    province_id: 27
+  },
+  {
+    id: 961,
+    name: 'چمستان',
+    slug: 'چمستان',
+    province_id: 27
+  },
+  {
+    id: 962,
+    name: 'خرم آباد',
+    slug: 'مازندران-خرم-آباد',
+    province_id: 27
+  },
+  {
+    id: 963,
+    name: 'خلیل شهر',
+    slug: 'خلیل-شهر',
+    province_id: 27
+  },
+  {
+    id: 964,
+    name: 'خوش رودپی',
+    slug: 'خوش-رودپی',
+    province_id: 27
+  },
+  {
+    id: 965,
+    name: 'دابودشت',
+    slug: 'دابودشت',
+    province_id: 27
+  },
+  {
+    id: 966,
+    name: 'رامسر',
+    slug: 'رامسر',
+    province_id: 27
+  },
+  {
+    id: 967,
+    name: 'رستمکلا',
+    slug: 'رستمکلا',
+    province_id: 27
+  },
+  {
+    id: 968,
+    name: 'رویان',
+    slug: 'رویان',
+    province_id: 27
+  },
+  {
+    id: 969,
+    name: 'رینه',
+    slug: 'رینه',
+    province_id: 27
+  },
+  {
+    id: 970,
+    name: 'زرگرمحله',
+    slug: 'زرگرمحله',
+    province_id: 27
+  },
+  {
+    id: 971,
+    name: 'زیرآب',
+    slug: 'زیرآب',
+    province_id: 27
+  },
+  {
+    id: 972,
+    name: 'ساری',
+    slug: 'ساری',
+    province_id: 27
+  },
+  {
+    id: 973,
+    name: 'سرخرود',
+    slug: 'سرخرود',
+    province_id: 27
+  },
+  {
+    id: 974,
+    name: 'سلمان شهر',
+    slug: 'سلمان-شهر',
+    province_id: 27
+  },
+  {
+    id: 975,
+    name: 'سورک',
+    slug: 'سورک',
+    province_id: 27
+  },
+  {
+    id: 976,
+    name: 'شیرگاه',
+    slug: 'شیرگاه',
+    province_id: 27
+  },
+  {
+    id: 977,
+    name: 'شیرود',
+    slug: 'شیرود',
+    province_id: 27
+  },
+  {
+    id: 978,
+    name: 'عباس آباد',
+    slug: 'عباس-آباد',
+    province_id: 27
+  },
+  {
+    id: 979,
+    name: 'فریدونکنار',
+    slug: 'فریدونکنار',
+    province_id: 27
+  },
+  {
+    id: 980,
+    name: 'فریم',
+    slug: 'فریم',
+    province_id: 27
+  },
+  {
+    id: 981,
+    name: 'قائم شهر',
+    slug: 'قائم-شهر',
+    province_id: 27
+  },
+  {
+    id: 982,
+    name: 'کتالم',
+    slug: 'کتالم',
+    province_id: 27
+  },
+  {
+    id: 983,
+    name: 'کلارآباد',
+    slug: 'کلارآباد',
+    province_id: 27
+  },
+  {
+    id: 984,
+    name: 'کلاردشت',
+    slug: 'کلاردشت',
+    province_id: 27
+  },
+  {
+    id: 985,
+    name: 'کله بست',
+    slug: 'کله-بست',
+    province_id: 27
+  },
+  {
+    id: 986,
+    name: 'کوهی خیل',
+    slug: 'کوهی-خیل',
+    province_id: 27
+  },
+  {
+    id: 987,
+    name: 'کیاسر',
+    slug: 'کیاسر',
+    province_id: 27
+  },
+  {
+    id: 988,
+    name: 'کیاکلا',
+    slug: 'کیاکلا',
+    province_id: 27
+  },
+  {
+    id: 989,
+    name: 'گتاب',
+    slug: 'گتاب',
+    province_id: 27
+  },
+  {
+    id: 990,
+    name: 'گزنک',
+    slug: 'گزنک',
+    province_id: 27
+  },
+  {
+    id: 991,
+    name: 'گلوگاه',
+    slug: 'گلوگاه',
+    province_id: 27
+  },
+  {
+    id: 992,
+    name: 'محمودآباد',
+    slug: 'مازندران-محمودآباد',
+    province_id: 27
+  },
+  {
+    id: 993,
+    name: 'مرزن آباد',
+    slug: 'مرزن-آباد',
+    province_id: 27
+  },
+  {
+    id: 994,
+    name: 'مرزیکلا',
+    slug: 'مرزیکلا',
+    province_id: 27
+  },
+  {
+    id: 995,
+    name: 'نشتارود',
+    slug: 'نشتارود',
+    province_id: 27
+  },
+  {
+    id: 996,
+    name: 'نکا',
+    slug: 'نکا',
+    province_id: 27
+  },
+  {
+    id: 997,
+    name: 'نور',
+    slug: 'نور',
+    province_id: 27
+  },
+  {
+    id: 998,
+    name: 'نوشهر',
+    slug: 'نوشهر',
+    province_id: 27
+  },
+  {
+    id: 999,
+    name: 'اراک',
+    slug: 'اراک',
+    province_id: 28
+  },
+  {
+    id: 1000,
+    name: 'آستانه',
+    slug: 'آستانه',
+    province_id: 28
+  },
+  {
+    id: 1001,
+    name: 'آشتیان',
+    slug: 'آشتیان',
+    province_id: 28
+  },
+  {
+    id: 1002,
+    name: 'پرندک',
+    slug: 'پرندک',
+    province_id: 28
+  },
+  {
+    id: 1003,
+    name: 'تفرش',
+    slug: 'تفرش',
+    province_id: 28
+  },
+  {
+    id: 1004,
+    name: 'توره',
+    slug: 'توره',
+    province_id: 28
+  },
+  {
+    id: 1005,
+    name: 'جاورسیان',
+    slug: 'جاورسیان',
+    province_id: 28
+  },
+  {
+    id: 1006,
+    name: 'خشکرود',
+    slug: 'خشکرود',
+    province_id: 28
+  },
+  {
+    id: 1007,
+    name: 'خمین',
+    slug: 'خمین',
+    province_id: 28
+  },
+  {
+    id: 1008,
+    name: 'خنداب',
+    slug: 'خنداب',
+    province_id: 28
+  },
+  {
+    id: 1009,
+    name: 'داودآباد',
+    slug: 'داودآباد',
+    province_id: 28
+  },
+  {
+    id: 1010,
+    name: 'دلیجان',
+    slug: 'دلیجان',
+    province_id: 28
+  },
+  {
+    id: 1011,
+    name: 'رازقان',
+    slug: 'رازقان',
+    province_id: 28
+  },
+  {
+    id: 1012,
+    name: 'زاویه',
+    slug: 'زاویه',
+    province_id: 28
+  },
+  {
+    id: 1013,
+    name: 'ساروق',
+    slug: 'ساروق',
+    province_id: 28
+  },
+  {
+    id: 1014,
+    name: 'ساوه',
+    slug: 'ساوه',
+    province_id: 28
+  },
+  {
+    id: 1015,
+    name: 'سنجان',
+    slug: 'سنجان',
+    province_id: 28
+  },
+  {
+    id: 1016,
+    name: 'شازند',
+    slug: 'شازند',
+    province_id: 28
+  },
+  {
+    id: 1017,
+    name: 'غرق آباد',
+    slug: 'غرق-آباد',
+    province_id: 28
+  },
+  {
+    id: 1018,
+    name: 'فرمهین',
+    slug: 'فرمهین',
+    province_id: 28
+  },
+  {
+    id: 1019,
+    name: 'قورچی باشی',
+    slug: 'قورچی-باشی',
+    province_id: 28
+  },
+  {
+    id: 1020,
+    name: 'کرهرود',
+    slug: 'کرهرود',
+    province_id: 28
+  },
+  {
+    id: 1021,
+    name: 'کمیجان',
+    slug: 'کمیجان',
+    province_id: 28
+  },
+  {
+    id: 1022,
+    name: 'مامونیه',
+    slug: 'مامونیه',
+    province_id: 28
+  },
+  {
+    id: 1023,
+    name: 'محلات',
+    slug: 'محلات',
+    province_id: 28
+  },
+  {
+    id: 1024,
+    name: 'مهاجران',
+    slug: 'مهاجران',
+    province_id: 28
+  },
+  {
+    id: 1025,
+    name: 'میلاجرد',
+    slug: 'میلاجرد',
+    province_id: 28
+  },
+  {
+    id: 1026,
+    name: 'نراق',
+    slug: 'نراق',
+    province_id: 28
+  },
+  {
+    id: 1027,
+    name: 'نوبران',
+    slug: 'نوبران',
+    province_id: 28
+  },
+  {
+    id: 1028,
+    name: 'نیمور',
+    slug: 'نیمور',
+    province_id: 28
+  },
+  {
+    id: 1029,
+    name: 'هندودر',
+    slug: 'هندودر',
+    province_id: 28
+  },
+  {
+    id: 1030,
+    name: 'ابوموسی',
+    slug: 'ابوموسی',
+    province_id: 29
+  },
+  {
+    id: 1031,
+    name: 'بستک',
+    slug: 'بستک',
+    province_id: 29
+  },
+  {
+    id: 1032,
+    name: 'بندرجاسک',
+    slug: 'بندرجاسک',
+    province_id: 29
+  },
+  {
+    id: 1033,
+    name: 'بندرچارک',
+    slug: 'بندرچارک',
+    province_id: 29
+  },
+  {
+    id: 1034,
+    name: 'بندرخمیر',
+    slug: 'بندرخمیر',
+    province_id: 29
+  },
+  {
+    id: 1035,
+    name: 'بندرعباس',
+    slug: 'بندرعباس',
+    province_id: 29
+  },
+  {
+    id: 1036,
+    name: 'بندرلنگه',
+    slug: 'بندرلنگه',
+    province_id: 29
+  },
+  {
+    id: 1037,
+    name: 'بیکا',
+    slug: 'بیکا',
+    province_id: 29
+  },
+  {
+    id: 1038,
+    name: 'پارسیان',
+    slug: 'پارسیان',
+    province_id: 29
+  },
+  {
+    id: 1039,
+    name: 'تخت',
+    slug: 'تخت',
+    province_id: 29
+  },
+  {
+    id: 1040,
+    name: 'جناح',
+    slug: 'جناح',
+    province_id: 29
+  },
+  {
+    id: 1041,
+    name: 'حاجی آباد',
+    slug: 'هرمزگان-حاجی-آباد',
+    province_id: 29
+  },
+  {
+    id: 1042,
+    name: 'درگهان',
+    slug: 'درگهان',
+    province_id: 29
+  },
+  {
+    id: 1043,
+    name: 'دهبارز',
+    slug: 'دهبارز',
+    province_id: 29
+  },
+  {
+    id: 1044,
+    name: 'رویدر',
+    slug: 'رویدر',
+    province_id: 29
+  },
+  {
+    id: 1045,
+    name: 'زیارتعلی',
+    slug: 'زیارتعلی',
+    province_id: 29
+  },
+  {
+    id: 1046,
+    name: 'سردشت',
+    slug: 'هرمزگان-سردشت',
+    province_id: 29
+  },
+  {
+    id: 1047,
+    name: 'سندرک',
+    slug: 'سندرک',
+    province_id: 29
+  },
+  {
+    id: 1048,
+    name: 'سوزا',
+    slug: 'سوزا',
+    province_id: 29
+  },
+  {
+    id: 1049,
+    name: 'سیریک',
+    slug: 'سیریک',
+    province_id: 29
+  },
+  {
+    id: 1050,
+    name: 'فارغان',
+    slug: 'فارغان',
+    province_id: 29
+  },
+  {
+    id: 1051,
+    name: 'فین',
+    slug: 'فین',
+    province_id: 29
+  },
+  {
+    id: 1052,
+    name: 'قشم',
+    slug: 'قشم',
+    province_id: 29
+  },
+  {
+    id: 1053,
+    name: 'قلعه قاضی',
+    slug: 'قلعه-قاضی',
+    province_id: 29
+  },
+  {
+    id: 1054,
+    name: 'کنگ',
+    slug: 'کنگ',
+    province_id: 29
+  },
+  {
+    id: 1055,
+    name: 'کوشکنار',
+    slug: 'کوشکنار',
+    province_id: 29
+  },
+  {
+    id: 1056,
+    name: 'کیش',
+    slug: 'کیش',
+    province_id: 29
+  },
+  {
+    id: 1057,
+    name: 'گوهران',
+    slug: 'گوهران',
+    province_id: 29
+  },
+  {
+    id: 1058,
+    name: 'میناب',
+    slug: 'میناب',
+    province_id: 29
+  },
+  {
+    id: 1059,
+    name: 'هرمز',
+    slug: 'هرمز',
+    province_id: 29
+  },
+  {
+    id: 1060,
+    name: 'هشتبندی',
+    slug: 'هشتبندی',
+    province_id: 29
+  },
+  {
+    id: 1061,
+    name: 'ازندریان',
+    slug: 'ازندریان',
+    province_id: 30
+  },
+  {
+    id: 1062,
+    name: 'اسدآباد',
+    slug: 'اسدآباد',
+    province_id: 30
+  },
+  {
+    id: 1063,
+    name: 'برزول',
+    slug: 'برزول',
+    province_id: 30
+  },
+  {
+    id: 1064,
+    name: 'بهار',
+    slug: 'بهار',
+    province_id: 30
+  },
+  {
+    id: 1065,
+    name: 'تویسرکان',
+    slug: 'تویسرکان',
+    province_id: 30
+  },
+  {
+    id: 1066,
+    name: 'جورقان',
+    slug: 'جورقان',
+    province_id: 30
+  },
+  {
+    id: 1067,
+    name: 'جوکار',
+    slug: 'جوکار',
+    province_id: 30
+  },
+  {
+    id: 1068,
+    name: 'دمق',
+    slug: 'دمق',
+    province_id: 30
+  },
+  {
+    id: 1069,
+    name: 'رزن',
+    slug: 'رزن',
+    province_id: 30
+  },
+  {
+    id: 1070,
+    name: 'زنگنه',
+    slug: 'زنگنه',
+    province_id: 30
+  },
+  {
+    id: 1071,
+    name: 'سامن',
+    slug: 'سامن',
+    province_id: 30
+  },
+  {
+    id: 1072,
+    name: 'سرکان',
+    slug: 'سرکان',
+    province_id: 30
+  },
+  {
+    id: 1073,
+    name: 'شیرین سو',
+    slug: 'شیرین-سو',
+    province_id: 30
+  },
+  {
+    id: 1074,
+    name: 'صالح آباد',
+    slug: 'همدان-صالح-آباد',
+    province_id: 30
+  },
+  {
+    id: 1075,
+    name: 'فامنین',
+    slug: 'فامنین',
+    province_id: 30
+  },
+  {
+    id: 1076,
+    name: 'فرسفج',
+    slug: 'فرسفج',
+    province_id: 30
+  },
+  {
+    id: 1077,
+    name: 'فیروزان',
+    slug: 'فیروزان',
+    province_id: 30
+  },
+  {
+    id: 1078,
+    name: 'قروه درجزین',
+    slug: 'قروه-درجزین',
+    province_id: 30
+  },
+  {
+    id: 1079,
+    name: 'قهاوند',
+    slug: 'قهاوند',
+    province_id: 30
+  },
+  {
+    id: 1080,
+    name: 'کبودر آهنگ',
+    slug: 'کبودر-آهنگ',
+    province_id: 30
+  },
+  {
+    id: 1081,
+    name: 'گل تپه',
+    slug: 'گل-تپه',
+    province_id: 30
+  },
+  {
+    id: 1082,
+    name: 'گیان',
+    slug: 'گیان',
+    province_id: 30
+  },
+  {
+    id: 1083,
+    name: 'لالجین',
+    slug: 'لالجین',
+    province_id: 30
+  },
+  {
+    id: 1084,
+    name: 'مریانج',
+    slug: 'مریانج',
+    province_id: 30
+  },
+  {
+    id: 1085,
+    name: 'ملایر',
+    slug: 'ملایر',
+    province_id: 30
+  },
+  {
+    id: 1086,
+    name: 'نهاوند',
+    slug: 'نهاوند',
+    province_id: 30
+  },
+  {
+    id: 1087,
+    name: 'همدان',
+    slug: 'همدان',
+    province_id: 30
+  },
+  {
+    id: 1088,
+    name: 'ابرکوه',
+    slug: 'ابرکوه',
+    province_id: 31
+  },
+  {
+    id: 1089,
+    name: 'احمدآباد',
+    slug: 'احمدآباد',
+    province_id: 31
+  },
+  {
+    id: 1090,
+    name: 'اردکان',
+    slug: 'یزد-اردکان',
+    province_id: 31
+  },
+  {
+    id: 1091,
+    name: 'اشکذر',
+    slug: 'اشکذر',
+    province_id: 31
+  },
+  {
+    id: 1092,
+    name: 'بافق',
+    slug: 'بافق',
+    province_id: 31
+  },
+  {
+    id: 1093,
+    name: 'بفروئیه',
+    slug: 'بفروئیه',
+    province_id: 31
+  },
+  {
+    id: 1094,
+    name: 'بهاباد',
+    slug: 'بهاباد',
+    province_id: 31
+  },
+  {
+    id: 1095,
+    name: 'تفت',
+    slug: 'تفت',
+    province_id: 31
+  },
+  {
+    id: 1096,
+    name: 'حمیدیا',
+    slug: 'حمیدیا',
+    province_id: 31
+  },
+  {
+    id: 1097,
+    name: 'خضرآباد',
+    slug: 'خضرآباد',
+    province_id: 31
+  },
+  {
+    id: 1098,
+    name: 'دیهوک',
+    slug: 'دیهوک',
+    province_id: 31
+  },
+  {
+    id: 1099,
+    name: 'زارچ',
+    slug: 'زارچ',
+    province_id: 31
+  },
+  {
+    id: 1100,
+    name: 'شاهدیه',
+    slug: 'شاهدیه',
+    province_id: 31
+  },
+  {
+    id: 1101,
+    name: 'طبس',
+    slug: 'یزد-طبس',
+    province_id: 31
+  },
+  {
+    id: 1103,
+    name: 'عقدا',
+    slug: 'عقدا',
+    province_id: 31
+  },
+  {
+    id: 1104,
+    name: 'مروست',
+    slug: 'مروست',
+    province_id: 31
+  },
+  {
+    id: 1105,
+    name: 'مهردشت',
+    slug: 'مهردشت',
+    province_id: 31
+  },
+  {
+    id: 1106,
+    name: 'مهریز',
+    slug: 'مهریز',
+    province_id: 31
+  },
+  {
+    id: 1107,
+    name: 'میبد',
+    slug: 'میبد',
+    province_id: 31
+  },
+  {
+    id: 1108,
+    name: 'ندوشن',
+    slug: 'ندوشن',
+    province_id: 31
+  },
+  {
+    id: 1109,
+    name: 'نیر',
+    slug: 'یزد-نیر',
+    province_id: 31
+  },
+  {
+    id: 1110,
+    name: 'هرات',
+    slug: 'هرات',
+    province_id: 31
+  },
+  {
+    id: 1111,
+    name: 'یزد',
+    slug: 'یزد',
+    province_id: 31
+  },
+  {
+    id: 1116,
+    name: 'پرند',
+    slug: 'پرند',
+    province_id: 8
+  },
+  {
+    id: 1117,
+    name: 'فردیس',
+    slug: 'فردیس',
+    province_id: 5
+  },
+  {
+    id: 1118,
+    name: 'مارلیک',
+    slug: 'مارلیک',
+    province_id: 5
+  },
+  {
+    id: 1119,
+    name: 'سادات شهر',
+    slug: 'سادات-شهر',
+    province_id: 27
+  },
+  {
+    id: 1121,
+    name: 'زیباکنار',
+    slug: 'زیباکنار',
+    province_id: 26
+  },
+  {
+    id: 1135,
+    name: 'کردان',
+    slug: 'کردان',
+    province_id: 5
+  },
+  {
+    id: 1137,
+    name: 'ساوجبلاغ',
+    slug: 'ساوجبلاغ',
+    province_id: 5
+  },
+  {
+    id: 1138,
+    name: 'تهران دشت',
+    slug: 'تهران-دشت',
+    province_id: 5
+  },
+  {
+    id: 1150,
+    name: 'گلبهار',
+    slug: 'گلبهار',
+    province_id: 11
+  },
+  {
+    id: 1153,
+    name: 'قیامدشت',
+    slug: 'قیامدشت',
+    province_id: 8
+  },
+  {
+    id: 1155,
+    name: 'بینالود',
+    slug: 'بینالود',
+    province_id: 11
+  },
+  {
+    id: 1159,
+    name: 'پیربازار',
+    slug: 'پیربازار',
+    province_id: 26
+  },
+  {
+    id: 1160,
+    name: 'رضوانشهر',
+    slug: 'رضوانشهر',
+    province_id: 31
+  }
+]
+
+export const Provinces = [
+  {
+    id: 1,
+    name: 'آذربایجان شرقی',
+    slug: 'آذربایجان-شرقی',
+    tel_prefix: '041'
+  },
+  {
+    id: 2,
+    name: 'آذربایجان غربی',
+    slug: 'آذربایجان-غربی',
+    tel_prefix: '044'
+  },
+  {
+    id: 3,
+    name: 'اردبیل',
+    slug: 'اردبیل',
+    tel_prefix: '045'
+  },
+  {
+    id: 4,
+    name: 'اصفهان',
+    slug: 'اصفهان',
+    tel_prefix: '031'
+  },
+  {
+    id: 5,
+    name: 'البرز',
+    slug: 'البرز',
+    tel_prefix: '026'
+  },
+  {
+    id: 6,
+    name: 'ایلام',
+    slug: 'ایلام',
+    tel_prefix: '084'
+  },
+  {
+    id: 7,
+    name: 'بوشهر',
+    slug: 'بوشهر',
+    tel_prefix: '077'
+  },
+  {
+    id: 8,
+    name: 'تهران',
+    slug: 'تهران',
+    tel_prefix: '021'
+  },
+  {
+    id: 9,
+    name: 'چهارمحال و بختیاری',
+    slug: 'چهارمحال-بختیاری',
+    tel_prefix: '038'
+  },
+  {
+    id: 10,
+    name: 'خراسان جنوبی',
+    slug: 'خراسان-جنوبی',
+    tel_prefix: '056'
+  },
+  {
+    id: 11,
+    name: 'خراسان رضوی',
+    slug: 'خراسان-رضوی',
+    tel_prefix: '051'
+  },
+  {
+    id: 12,
+    name: 'خراسان شمالی',
+    slug: 'خراسان-شمالی',
+    tel_prefix: '058'
+  },
+  {
+    id: 13,
+    name: 'خوزستان',
+    slug: 'خوزستان',
+    tel_prefix: '061'
+  },
+  {
+    id: 14,
+    name: 'زنجان',
+    slug: 'زنجان',
+    tel_prefix: '024'
+  },
+  {
+    id: 15,
+    name: 'سمنان',
+    slug: 'سمنان',
+    tel_prefix: '023'
+  },
+  {
+    id: 16,
+    name: 'سیستان و بلوچستان',
+    slug: 'سیستان-بلوچستان',
+    tel_prefix: '054'
+  },
+  {
+    id: 17,
+    name: 'فارس',
+    slug: 'فارس',
+    tel_prefix: '071'
+  },
+  {
+    id: 18,
+    name: 'قزوین',
+    slug: 'قزوین',
+    tel_prefix: '028'
+  },
+  {
+    id: 19,
+    name: 'قم',
+    slug: 'قم',
+    tel_prefix: '025'
+  },
+  {
+    id: 20,
+    name: 'کردستان',
+    slug: 'کردستان',
+    tel_prefix: '087'
+  },
+  {
+    id: 21,
+    name: 'کرمان',
+    slug: 'کرمان',
+    tel_prefix: '034'
+  },
+  {
+    id: 22,
+    name: 'کرمانشاه',
+    slug: 'کرمانشاه',
+    tel_prefix: '083'
+  },
+  {
+    id: 23,
+    name: 'کهگیلویه و بویراحمد',
+    slug: 'کهگیلویه-بویراحمد',
+    tel_prefix: '074'
+  },
+  {
+    id: 24,
+    name: 'گلستان',
+    slug: 'گلستان',
+    tel_prefix: '017'
+  },
+  {
+    id: 25,
+    name: 'لرستان',
+    slug: 'لرستان',
+    tel_prefix: '066'
+  },
+  {
+    id: 26,
+    name: 'گیلان',
+    slug: 'گیلان',
+    tel_prefix: '013'
+  },
+  {
+    id: 27,
+    name: 'مازندران',
+    slug: 'مازندران',
+    tel_prefix: '011'
+  },
+  {
+    id: 28,
+    name: 'مرکزی',
+    slug: 'مرکزی',
+    tel_prefix: '086'
+  },
+  {
+    id: 29,
+    name: 'هرمزگان',
+    slug: 'هرمزگان',
+    tel_prefix: '076'
+  },
+  {
+    id: 30,
+    name: 'همدان',
+    slug: 'همدان',
+    tel_prefix: '081'
+  },
+  {
+    id: 31,
+    name: 'یزد',
+    slug: 'یزد',
+    tel_prefix: '035'
+  }
+]
+
+export function getCitiesOfProvince (provinceName) {
+  const targetProvince = Provinces.find(province => province.name === provinceName)
+  if (!targetProvince) {
+    return targetProvince
+  }
+  return Cities.filter(city => city.province_id === targetProvince.id)
+}

+ 5 - 1
src/components/FormBuilderCustumComponents/ContentCategorySelector.vue

@@ -88,9 +88,13 @@ export default {
     }
   },
   watch: {
-    value () {
+    value (newValue, oldValue) {
+      if (newValue && oldValue && newValue.toString() === oldValue.toString()) {
+        return
+      }
       if (this.value?.id) {
         this.getCategories()
+        // this.$emit('update:value', this.value.id)
       } else if (this.value) {
         this.inputData = this.value
       }

+ 15 - 14
src/components/FormBuilderCustumComponents/ContentMedias/ContentMedias.vue

@@ -171,21 +171,22 @@ export default {
         if (!source) {
           return
         }
+        window.open(source, '_blank')
 
-        const fileName = (source.match(/(?<=\/)[^/?#]+(?=[^/]*$)/gm) || [media.title])[0]
-        this.fetchLoading = true
-        fetch(source)
-          .then(response => response.blob())
-          .then(blob => {
-            const link = document.createElement('a')
-            link.href = URL.createObjectURL(blob)
-            link.download = fileName
-            link.click()
-            this.fetchLoading = false
-          })
-          .catch(() => {
-            this.fetchLoading = false
-          })
+        // const fileName = (source.match(/(?<=\/)[^/?#]+(?=[^/]*$)/gm) || [media.title])[0]
+        // this.fetchLoading = true
+        // fetch(source)
+        //   .then(response => response.blob())
+        //   .then(blob => {
+        //     const link = document.createElement('a')
+        //     link.href = URL.createObjectURL(blob)
+        //     link.download = fileName
+        //     link.click()
+        //     this.fetchLoading = false
+        //   })
+        //   .catch(() => {
+        //     this.fetchLoading = false
+        //   })
       }
     },
     onDelete (media) {

+ 36 - 9
src/components/FormBuilderCustumComponents/ProductSelector.vue

@@ -82,11 +82,18 @@ export default {
             field: row => (new Product(row)).is_physical_info.label
           },
           {
-            name: 'content_category',
+            name: 'store_category',
             required: true,
             label: 'دسته فروشگاه',
             align: 'left',
-            field: row => row.store_category_info?.parent?.parent?.title + '، ' + row.store_category_info?.parent?.title + '، ' + row.store_category_info?.title
+            field: row => (row.store_category_info?.parent?.parent?.title || '-') + '، ' + (row.store_category_info?.parent?.title || '-') + '، ' + (row.store_category_info?.title || '-')
+          },
+          {
+            name: 'store_category1',
+            required: true,
+            label: 'دسته محتوا',
+            align: 'left',
+            field: row => (row.content_category_info?.parent?.parent?.title || '-') + '، ' + (row.content_category_info?.parent?.title || '-') + '، ' + (row.content_category_info?.title || '-')
           },
           {
             name: 'unit_price',
@@ -105,15 +112,31 @@ export default {
         ]
       },
       itemIdentifyKey: 'id',
-      itemIndicatorKey: (product) => product ? product.title + '(' + product.id + ')' : '-'
+      itemIndicatorKey: (product) => {
+        return product ? product.title + '(' + product.id + ')' : '-'
+      }
     }
   },
   watch: {
     value: {
-      handler (newValue) {
-        if (newValue) {
-          this.selected = newValue
+      handler (newValue, oldValue) {
+        if (!newValue || (newValue && oldValue && newValue.toString() === oldValue.toString())) {
+          return
         }
+        if (this.selectionMode === 'multiple') {
+          if (isNaN(newValue[0])) {
+            this.selected = newValue
+          } else {
+            this.inputData = newValue
+          }
+        } else {
+          if (isNaN(newValue)) {
+            this.selected = newValue
+          } else {
+            this.inputData = newValue
+          }
+        }
+        this.updateFormValue()
       },
       immediate: true
     }
@@ -132,11 +155,15 @@ export default {
         }
       })
       this.selected = selected
-      this.inputData = selected
+      this.updateFormValue()
+    },
+    updateFormValue () {
       if (this.selectionMode === 'multiple') {
-        this.$emit('update:value', selected.map(product => product.id))
+        this.inputData = this.selected.map(product => product.id)
+        this.$emit('update:value', this.selected.map(product => product.id))
       } else {
-        this.$emit('update:value', selected.id)
+        this.inputData = this.selected.id
+        this.$emit('update:value', this.selected.id)
       }
     }
   }

+ 1 - 1
src/components/InvoicePaymentCard/InvoicePaymentCard.vue

@@ -98,7 +98,7 @@
       <div>
         <instalments :wallet="wallet"
                      :invoice="invoice"
-                     :can-action="canAction"
+                     :can-action="true"
                      @onInstalmentPayed="onInstalmentPayed" />
       </div>
     </div>

+ 2 - 2
src/components/Template/Header/MainHeader.vue

@@ -56,7 +56,7 @@
                     <q-item-section>کتب سطح یک</q-item-section>
                   </q-item>
                   <q-item v-close-popup
-                          href="https://formafzar.com/form/zmvhh"
+                          href="https://formafzar.com/form/c73vr"
                           target="_blank"
                           clickable>
                     <q-item-section>کتب سطح دو</q-item-section>
@@ -86,7 +86,7 @@
                     <q-item-section>کتب جریان‌شناسی سطح یک</q-item-section>
                   </q-item>
                   <q-item v-close-popup
-                          href="https://formafzar.com/form/xskmo"
+                          href="https://formafzar.com/form/w1m0i"
                           target="_blank"
                           clickable>
                     <q-item-section>کتب جریان‌شناسی سطح دو</q-item-section>

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

@@ -252,6 +252,20 @@ export default {
               routeName: 'Admin.Store.Discount.List',
               show: true,
               active: false
+            },
+            {
+              title: 'هزینه ارسال',
+              roles: ['superuser'],
+              routeName: 'Admin.Store.Basket.DeliveryCost',
+              show: true,
+              active: false
+            },
+            {
+              title: 'آیین نامه خرید',
+              roles: ['superuser'],
+              routeName: 'Admin.Store.Basket.PurchaseRegulations',
+              show: true,
+              active: false
             }
             // {
             //   title: 'تنظیمات',

+ 2 - 2
src/components/Template/SideBar/Main.vue

@@ -85,7 +85,7 @@ export default {
             },
             {
               title: 'کتب سطح دو',
-              externalLink: 'https://formafzar.com/form/zmvhh',
+              externalLink: 'https://formafzar.com/form/c73vr',
               show: true
             },
             {
@@ -110,7 +110,7 @@ export default {
             },
             {
               title: 'کتب جریان‌شناسی سطح دو',
-              externalLink: 'https://formafzar.com/form/xskmo',
+              externalLink: 'https://formafzar.com/form/w1m0i',
               show: true
             },
             {

+ 1 - 0
src/components/Widgets/Content/AdminContentCreate/AdminContentCreate.vue

@@ -53,6 +53,7 @@ export default {
       inputs: [
         { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-md-6 col-12' },
         { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'checkbox', name: 'is_hidden', responseKey: 'is_hidden', label: 'پنهان باشد؟', placeholder: ' ', value: true, col: 'col-12' },
         { type: ContentCategorySelectorComp, name: 'category', responseKey: 'category', col: 'col-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' },
         { type: ContentMediasComp, name: 'medias', responseKey: 'medias_info', col: 'col-12' },

+ 9 - 0
src/components/Widgets/Content/AdminContentList/AdminContentList.vue

@@ -48,6 +48,7 @@
 <script>
 import { shallowRef } from 'vue'
 import { EntityIndex } from 'quasar-crud'
+import { Content } from 'src/models/Content.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import BtnControl from 'src/components/Control/btn.vue'
@@ -75,6 +76,7 @@ export default {
       inputs: [
         { type: ContentCategorySelectorComp, name: 'category', responseKey: 'category', col: 'col-12' },
         { type: 'input', name: 'search', value: null, col: 'col-md-3 col-12', label: 'جست و جو', placeholder: ' ' },
+        { type: 'select', name: 'is_hidden', label: 'وضعیت نمایش', options: (new Content()).is_hiddenEnums, placeholder: ' ', col: 'col-md-3 col-12' },
         { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: this.search, col: 'col-md-2 col-12' }
       ],
       table: {
@@ -107,6 +109,13 @@ export default {
             align: 'left',
             field: row => row.category_info?.title
           },
+          {
+            name: 'is_hidden',
+            required: true,
+            label: 'نمایش',
+            align: 'left',
+            field: row => (new Content(row)).is_hidden_info.label
+          },
           {
             name: 'action',
             required: true,

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

@@ -87,6 +87,7 @@ export default {
       inputs: [
         { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-md-6 col-12' },
         { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'checkbox', name: 'is_hidden', responseKey: 'is_hidden', label: 'پنهان باشد؟', placeholder: ' ', col: 'col-12' },
         { type: ContentCategorySelectorComp, name: 'category', responseKey: 'category_info', col: 'col-md-12 col-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-md-12 col-12' },
         { type: ContentMediasComp, name: 'medias', responseKey: 'medias_info', col: 'col-md-12 col-12' },

+ 158 - 0
src/components/Widgets/Content/ContentCarousel/ContentCarousel.vue

@@ -0,0 +1,158 @@
+<template>
+  <div class="contentCarousel">
+    <div class="contentCarousel-title">
+      <div>
+        <q-banner class="banner">
+          {{ getCategoryTitle }}
+        </q-banner>
+      </div>
+      <div>
+        <q-btn flat
+               :to="{name: 'Public.ContentCategoryParentParent.List'}">
+          مشاهده همه
+          <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">
+      <div v-if="!contents.loading && contents.list.length > 0"
+           ref="slider"
+           class="splide"
+           role="group"
+           aria-label="Splide Basic HTML Example">
+        <div class="splide__track">
+          <ul class="splide__list">
+            <li v-for="(content, contentIndex) in contents.list"
+                :key="contentIndex"
+                class="splide__slide">
+              <content-item :content="content" />
+            </li>
+          </ul>
+        </div>
+      </div>
+      <div v-else-if="!contents.loading && contents.list.length === 0">
+        رویدادی یافت نشد.
+      </div>
+      <div v-else-if="contents.loading">
+        کمی صبر کنید ...
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Splide from '@splidejs/splide'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { ContentList } from 'src/models/Content.js'
+import '@splidejs/splide/dist/css/splide-core.min.css'
+import ContentItem from './componetns/ContentItem.vue'
+import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'EventCarousel',
+  components: {
+    ContentItem
+  },
+  mixins: [mixinPrefetchServerData, mixinWidget],
+  data () {
+    return {
+      loading: false,
+      slider: null,
+      contents: new ContentList(),
+      breakpoints: {
+        1920: {
+          perPage: 3
+        },
+        900: {
+          perPage: 2
+        },
+        600: {
+          perPage: 1
+        }
+      },
+      maximizedToggle: true
+    }
+  },
+  computed: {
+    getCategoryTitle () {
+      return this.localOptions.categoryTitle
+    }
+  },
+  mounted () {
+    this.loadSlider()
+  },
+  methods: {
+    loadSlider () {
+      this.$nextTick(() => {
+        if (!this.$refs.slider) {
+          return
+        }
+        new Splide(this.$refs.slider, {
+          direction: 'rtl',
+          paginationDirection: 'rtl',
+          // type: 'loop',
+          focus: 0,
+          gap: 24,
+          snap: true,
+          // focus: 'center',
+          breakpoints: this.breakpoints
+        }).mount()
+      })
+    },
+    prefetchServerDataPromise () {
+      return this.getContents()
+    },
+    prefetchServerDataPromiseThen (contentList) {
+      this.contents = new ContentList(contentList.list)
+      this.contents.loading = false
+      this.loadSlider()
+    },
+    prefetchServerDataPromiseCatch () {
+      this.contents.loading = false
+    },
+
+    getContents () {
+      const params = {}
+
+      if (this.localOptions.categoryCategoryCategoryId) {
+        params.category__parent__parent = this.localOptions.categoryCategoryCategoryId
+      }
+
+      if (this.localOptions.categoryCategoryId) {
+        params.category__parent = this.localOptions.categoryCategoryId
+      }
+
+      if (this.localOptions.categoryId) {
+        params.category = this.localOptions.categoryId
+      }
+
+      this.contents.loading = true
+      return APIGateway.content.index(params)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.contentCarousel {
+  padding: 60px 0;
+  .contentCarousel-title {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    padding-bottom: 33px;
+  }
+  .carousel-section {
+  }
+}
+</style>

+ 74 - 0
src/components/Widgets/Content/ContentCarousel/OptionPanel.vue

@@ -0,0 +1,74 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div class="q-mt-md">
+        عنوان
+      </div>
+      <div class="row">
+        <div class="col-md-8 col-12">
+          <q-input v-model="localOptions.categoryTitle" />
+        </div>
+      </div>
+      <div class="q-mt-md">
+        کد دسته پدربزرگ
+      </div>
+      <div class="row">
+        <div class="col-md-8 col-12">
+          <q-input v-model="localOptions.categoryCategoryCategoryId" />
+        </div>
+      </div>
+      <div>
+        کد دسته پدر
+      </div>
+      <div class="row">
+        <div class="col-md-4 col-12">
+          <q-checkbox v-model="localOptions.categoryCategoryFromQueryParams"
+                      label="استفاده از کوری پارام صفحه" />
+        </div>
+        <div v-if="!localOptions.categoryCategoryFromQueryParams"
+             class="col-md-8 col-12">
+          <q-input v-model="localOptions.categoryCategoryId" />
+        </div>
+      </div>
+      <div>
+        کد دسته فرزند
+      </div>
+      <div class="row">
+        <div class="col-md-4 col-12">
+          <q-checkbox v-model="localOptions.categoryFromQueryParams"
+                      label="استفاده از کوری پارام صفحه" />
+        </div>
+        <div v-if="!localOptions.categoryFromQueryParams"
+             class="col-md-8 col-12">
+          <q-input v-model="localOptions.categoryId" />
+        </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 {
+      defaultOptions: {
+        categoryTitle: 'عنوان دلخواه برای دسته محتوای مشخص شده',
+        categoryId: null,
+        categoryCategoryId: null,
+        categoryCategoryCategoryId: null,
+        categoryFromQueryParams: false,
+        categoryCategoryFromQueryParams: false
+      }
+    }
+  }
+})
+</script>

+ 128 - 0
src/components/Widgets/Content/ContentCarousel/componetns/ContentItem.vue

@@ -0,0 +1,128 @@
+<template>
+  <q-card flat
+          class="content-item">
+    <div class="thumbnail">
+      <img class="thumbnail-img"
+           :src="content.thumbnail"
+           @click="goToPage">
+    </div>
+    <div class="infos">
+      <div class="title ellipsis-2-lines">
+        <router-link :to="{name: 'Public.Content.Show', params: {id: content.id}}">
+          {{ content.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-content"
+             :to="{name: 'Public.Content.Show', params: {id: content.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 Assist from 'src/assets/js/Assist.js'
+import { Content } from 'src/models/Content.js'
+
+export default defineComponent({
+  name: 'ContentItem',
+  props: {
+    content: {
+      type: Content,
+      default: new Content()
+    }
+  },
+  data() {
+    return {
+    }
+  },
+  methods: {
+    goToPage () {
+      this.$router.push({ name: 'Public.Content.Show', params: { id: this.content.id } })
+    },
+    miladiToShamsi () {
+      return Assist.miladiToShamsi(this.content.creation_time, 'jD jMMMM jYYYY ساعت HH:mm')
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.content-item {
+  width: 100%;
+  background-color: #F6F6F6;
+  border-radius: 8px;
+  position: relative;
+  z-index: 1;
+
+  .thumbnail {
+    padding: 0;
+    cursor: pointer;
+    .thumbnail-img {
+      width: 100%;
+      height: auto;
+    }
+  }
+  .infos {
+    position: absolute;
+    left: 0;
+    padding-top: 12px;
+    bottom: 60px;
+    width: 100%;
+    z-index: 2;
+    -webkit-box-shadow: inset 0px -200px 55px -90px rgba(0,0,0,0.32);
+    -moz-box-shadow: inset 0px -200px 55px -90px rgba(0,0,0,0.32);
+    box-shadow: inset 0px -200px 55px -90px rgba(0,0,0,0.32);
+    .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-content {
+      width: 100%;
+      height: 60px;
+      border-top-right-radius: 0;
+      border-top-left-radius: 0;
+    }
+  }
+}
+</style>

+ 1 - 1
src/components/Widgets/Content/ContentShow/components/productItem.vue

@@ -30,7 +30,7 @@
         </div>
         <div class="product-item__price-info-value">
           {{ product.getFinalPrice() }}
-          تومان
+          ریال
         </div>
       </div>
       <q-btn color="primary"

+ 2 - 2
src/components/Widgets/Content/ContentTopicList/components/packageItem.vue

@@ -19,11 +19,11 @@
     <div class="package-item__price">
       <div class="package-item__price-base">
         {{ parseInt(packageItem.unit_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
       <div class="package-item__price-final">
         {{ packageItem.getFinalPrice() }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="package-item__actions">

+ 2 - 2
src/components/Widgets/Content/ContentTopicList/components/productItem.vue

@@ -27,11 +27,11 @@
     <div class="product-item__price">
       <div class="product-item__price-base">
         {{ parseInt(product.unit_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
       <div class="product-item__price-final">
         {{ product.getFinalPrice() }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="product-item__actions">

+ 90 - 0
src/components/Widgets/Content/LastContentTopics/LastContentTopics.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="ContentTopic"
+       :style="localOptions.style">
+    <div class="ContentTopic-title">
+      <div>
+        <q-banner class="banner">
+          دسته‌بندی موضوعی
+        </q-banner>
+      </div>
+      <div>
+        <q-btn flat
+               :to="{name: 'Public.PostTopic.List'}">
+          مشاهده همه
+          <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="ContentTopic-list">
+      <div class="row q-col-gutter-md">
+        <div v-for="post in contentTopicList.list"
+             :key="post.id"
+             class="col-md-6 col-12">
+          <topic-item :content-topic="post" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import TopicItem from './TopicItem.vue'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { ContentTopicList } from 'src/models/ContentTopic.js'
+import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'LastPosts',
+  components: {
+    TopicItem
+  },
+  mixins: [mixinWidget, mixinPrefetchServerData],
+  data () {
+    return {
+      mounted: false,
+      contentTopicList: new ContentTopicList()
+    }
+  },
+  mounted() {
+    this.mounted = true
+  },
+  methods: {
+    prefetchServerDataPromise () {
+      return this.getApiRequest()
+    },
+    prefetchServerDataPromiseThen ({ list }) {
+      this.contentTopicList = new ContentTopicList(list)
+      this.contentTopicList.loading = false
+    },
+    prefetchServerDataPromiseCatch () {
+      this.contentTopicList.loading = false
+    },
+
+    getApiRequest() {
+      this.contentTopicList.loading = true
+      return APIGateway.contentTopic.index()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.ContentTopic {
+  .ContentTopic-title {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    padding-bottom: 33px;
+  }
+}
+</style>

+ 0 - 0
src/components/Widgets/Content/LastContents/OptionPanel.vue → src/components/Widgets/Content/LastContentTopics/OptionPanel.vue


+ 84 - 0
src/components/Widgets/Content/LastContentTopics/TopicItem.vue

@@ -0,0 +1,84 @@
+<template>
+  <q-card class="ContentTopicItem"
+          flat>
+    <div class="thumbnail">
+      <router-link :to="{name: 'Public.Content.Show', params: {id: contentTopic.content}}">
+        <q-img :src="contentTopic.thumbnail" />
+      </router-link>
+    </div>
+    <div class="texts">
+      <div class="title ellipsis-2-lines">
+        <router-link :to="{name: 'Public.Content.Show', params: {id: contentTopic.content}}">
+          {{ contentTopic.title }}
+        </router-link>
+      </div>
+      <div v-if="contentTopic.content_info"
+           class="content"
+           v-html="contentTopic.content_info.description" />
+    </div>
+  </q-card>
+</template>
+
+<script>
+import Assist from 'src/assets/js/Assist.js'
+import { ContentTopic } from 'src/models/ContentTopic.js'
+
+export default {
+  name: 'ContentTopicItem',
+  props: {
+    contentTopic: {
+      type: ContentTopic,
+      default: new ContentTopic()
+    }
+  },
+  methods: {
+    miladiToShamsi (date) {
+      return Assist.miladiToShamsi(date, 'jDD jMMM jYYYY')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ContentTopicItem {
+  display: flex;
+  flex-flow: row;
+  gap: 24px;
+  justify-content: flex-start;
+  border-radius: 20px;
+  padding: 32px 28px;
+  background: #FBF4EA;
+  $thumbnail-width: 250px;
+  .thumbnail {
+    width: $thumbnail-width;
+    border-radius: 10px;
+    .q-img {
+      width: 100%;
+      border-radius: 10px;
+    }
+  }
+  .texts {
+    width: calc(100% - #{$thumbnail-width});
+    padding-top: 24px;
+    .title {
+      color: #212121;
+      font-size: 16px;
+      font-weight: bold;
+      margin-bottom: 28px;
+    }
+    .content {
+      color: #212121;
+      font-size: 16px;
+    }
+  }
+  @media screen and (max-width: 600px) {
+    flex-flow: column;
+    .thumbnail {
+      width: 100%;
+    }
+    .texts {
+      width: 100%;
+    }
+  }
+}
+</style>

+ 0 - 61
src/components/Widgets/Content/LastContents/LastContents.vue

@@ -1,61 +0,0 @@
-<template>
-  <div class="LastBlogs row q-col-gutter-md"
-       :style="localOptions.style">
-    <div v-for="post in postList.list"
-         :key="post.id"
-         class="col-md-6 col-12">
-      <post-item :post="post" />
-    </div>
-  </div>
-</template>
-
-<script>
-import PostItem from './PostItem.vue'
-import { PostList } from 'src/models/Post.js'
-import { APIGateway } from 'src/api/APIGateway.js'
-import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
-
-export default {
-  name: 'LastPosts',
-  components: {
-    PostItem
-  },
-  mixins: [mixinWidget, mixinPrefetchServerData],
-  data () {
-    return {
-      mounted: false,
-      defaultOptions: {
-        categoryId: null,
-        categoryParentId: null,
-        categoryParentParentId: 1
-      },
-      postList: new PostList()
-    }
-  },
-  mounted() {
-    this.mounted = true
-  },
-  methods: {
-    prefetchServerDataPromise () {
-      return this.getApiRequest()
-    },
-    prefetchServerDataPromiseThen ({ list }) {
-      this.postList = new PostList(list)
-      this.postList.loading = false
-    },
-    prefetchServerDataPromiseCatch () {
-      this.postList.loading = false
-    },
-
-    getApiRequest() {
-      this.postList.loading = true
-      return APIGateway.post.index({ category__parent__parent: this.localOptions.categoryParentParentId, category__parent: this.localOptions.categoryParentId, category: this.localOptions.categoryId })
-    }
-  }
-}
-</script>
-
-<style scoped lang="scss">
-.LastBlogs {
-}
-</style>

+ 0 - 98
src/components/Widgets/Content/LastContents/PostItem.vue

@@ -1,98 +0,0 @@
-<template>
-  <q-card class="PostItem"
-          flat>
-    <div class="thumbnail">
-      <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
-        <q-img :src="post.thumbnail" />
-      </router-link>
-    </div>
-    <div class="texts">
-      <div class="category-title">
-        <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
-          {{ post.category_info.title }}
-        </router-link>
-      </div>
-      <div class="title ellipsis-2-lines">
-        <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
-          {{ post.title }}
-        </router-link>
-      </div>
-      <div class="published-date">
-        تاریخ انتشار
-        {{ miladiToShamsi(post.creation_time) }}
-      </div>
-    </div>
-  </q-card>
-</template>
-
-<script>
-import { Post } from 'src/models/Post.js'
-import Assist from 'src/assets/js/Assist.js'
-
-export default {
-  name: 'PostItem',
-  props: {
-    post: {
-      type: Post,
-      default: new Post()
-    }
-  },
-  methods: {
-    miladiToShamsi (date) {
-      return Assist.miladiToShamsi(date, 'jDD jMMM jYYYY')
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.PostItem {
-  display: flex;
-  flex-flow: row;
-  justify-content: flex-start;
-  border: 2px solid transparent;
-  padding: 16px;
-  &:hover {
-    border-radius: 14px;
-    border-color: #0B6AB1;
-  }
-  .thumbnail {
-    width: 148px;
-    .q-img {
-      width: 100%;
-    }
-  }
-  .texts {
-    width: calc(100% - 148px);
-    position: relative;
-    padding-left: 14px;
-    .category-title {
-      width: max-content;
-      border-radius: 3px;
-      background: #E3E7F4;
-      padding: 4px 8px;
-      color: #666;
-      font-size: 14px;
-      font-weight: 500;
-      line-height: 100%;
-      margin-bottom: 8px;
-    }
-    .title {
-      width: 100%;
-      color: #000;
-      text-align: left;
-      font-size: 18px;
-      font-weight: 700;
-    }
-    .published-date {
-      color: #000;
-      text-align: right;
-      font-size: 14px;
-      font-weight: 200;
-      position: absolute;
-      left: 14px;
-      bottom: 0;
-    }
-  }
-}
-</style>

+ 152 - 0
src/components/Widgets/Donation/DonationIntro/DonationIntro.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="DonationIntro">
+    <div class="DonationIntro-title">
+      <q-banner class="banner">
+        صدقات و خیریه
+      </q-banner>
+    </div>
+    <div class="content">
+      <div class="text-and-action">
+        <div class="text"
+             v-html="localOptions.text" />
+        <q-btn color="primary"
+               :to="{ name: 'Public.DonationCategory.List' }">
+          پرداخت صدقات و خیریه
+        </q-btn>
+      </div>
+      <div class="carousel-section">
+        <div v-if="localOptions.slides.length > 0"
+             ref="slider"
+             class="splide"
+             role="group"
+             aria-label="Splide Basic HTML Example">
+          <div class="splide__track">
+            <ul class="splide__list">
+              <li v-for="(slide, slideIndex) in localOptions.slides"
+                  :key="slideIndex"
+                  class="splide__slide">
+                <q-img :src="slide" />
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Splide from '@splidejs/splide'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import '@splidejs/splide/dist/css/splide-core.min.css'
+
+export default {
+  name: 'EventCarousel',
+  mixins: [mixinWidget],
+  data () {
+    return {
+      loading: false,
+      slider: null,
+      breakpoints: {
+        1920: {
+          perPage: 1
+        },
+        900: {
+          perPage: 1
+        },
+        600: {
+          perPage: 1
+        }
+      },
+      maximizedToggle: true,
+      defaultOptions: {
+        text: null,
+        slides: []
+      }
+    }
+  },
+  mounted () {
+    this.loadSlider()
+  },
+  methods: {
+    loadSlider () {
+      this.$nextTick(() => {
+        if (!this.$refs.slider) {
+          return
+        }
+        new Splide(this.$refs.slider, {
+          direction: 'rtl',
+          paginationDirection: 'rtl',
+          // type: 'loop',
+          focus: 0,
+          gap: 24,
+          snap: true,
+          // focus: 'center',
+          breakpoints: this.breakpoints
+        }).mount()
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.DonationIntro {
+  padding: 60px 0;
+  .DonationIntro-title {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    padding-bottom: 33px;
+  }
+  .content {
+    display: flex;
+    flex-flow: row;
+    gap: 48px;
+    justify-content: space-between;
+    align-items: stretch;
+    .text-and-action {
+      width: 50%;
+      display: flex;
+      flex-flow: column;
+      justify-content: space-between;
+      align-items: flex-end;
+      .text {
+        width: 100%;
+      }
+    }
+    .carousel-section {
+      width: 50%;
+    }
+    :deep(.carousel-section) {
+      .splide__arrows {
+        top: auto;
+        bottom: 0;
+        .splide__arrow {
+          &.splide__arrow--prev {
+            left: 50%;
+            transform: translateX(-120%);
+          }
+          &.splide__arrow--next {
+            right: 50%;
+            transform: translateX(120%);
+          }
+        }
+      }
+    }
+
+    @media screen and (max-width: 1200px) {
+      & {
+        flex-flow: column;
+        gap: 16px;
+        .text-and-action {
+          width: 100%;
+        }
+        .carousel-section {
+          width: 100%;
+        }
+      }
+    }
+  }
+}
+</style>

+ 100 - 0
src/components/Widgets/Donation/DonationIntro/OptionPanel.vue

@@ -0,0 +1,100 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div>
+        <div>
+          متن:
+        </div>
+        <form-builder-tiptap-editor v-model:value="localOptions.text"
+                                    :options="editorOptions" />
+      </div>
+      <q-separator class="q-my-lg" />
+      <div class="row">
+        <div class="col-12">
+          <q-btn color="primary"
+                 class="full-width"
+                 @click="addSlide">
+            افزودن تصویر آپلود شده
+          </q-btn>
+        </div>
+        <div class="col-12">
+          <div class="input-container col-12 q-mt-md">
+            <div class="outsideLabel">آدرس عکس</div>
+            <image-uploader v-model:file="newSlide" />
+          </div>
+        </div>
+      </div>
+      <div class="slides">
+        <div v-for="(slide, slideIndex) in localOptions.slides"
+             :key="slideIndex"
+             class="slide-item">
+          <q-btn color="red"
+                 @click="removeSlide(slideIndex)">
+            <q-icon name="close" />
+          </q-btn>
+          <q-img :src="slide" />
+        </div>
+      </div>
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import ImageUploader from 'src/components/ImageUploader.vue'
+import PostMixin from 'src/components/Widgets/Post/PostMixin.js'
+import FormBuilderTiptapEditor from 'src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    FormBuilderTiptapEditor,
+    OptionPanelTabs,
+    ImageUploader
+  },
+  mixins: [mixinOptionPanel, PostMixin],
+  data () {
+    const authorization = 'Bearer ' + this.$store.getters['Auth/accessToken']
+    const uploadVideo = this.uploadVideo
+    const uploadAudio = this.uploadAudio
+    return {
+      newSlide: null,
+      editorOptions: {
+        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
+      },
+      defaultOptions: {
+        text: null,
+        slides: []
+      }
+    }
+  },
+  methods: {
+    addSlide () {
+      if (!this.newSlide) {
+        return
+      }
+      this.localOptions.slides.push(JSON.parse(JSON.stringify(this.newSlide)))
+    },
+    removeSlide(slideIndex) {
+      this.localOptions.slides.splice(slideIndex, 1)
+    }
+  }
+})
+</script>

+ 128 - 0
src/components/Widgets/Donation/DonationIntro/componetns/ContentItem.vue

@@ -0,0 +1,128 @@
+<template>
+  <q-card flat
+          class="content-item">
+    <div class="thumbnail">
+      <img class="thumbnail-img"
+           :src="content.thumbnail"
+           @click="goToPage">
+    </div>
+    <div class="infos">
+      <div class="title ellipsis-2-lines">
+        <router-link :to="{name: 'Public.Content.Show', params: {id: content.id}}">
+          {{ content.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-content"
+             :to="{name: 'Public.Content.Show', params: {id: content.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 Assist from 'src/assets/js/Assist.js'
+import { Content } from 'src/models/Content.js'
+
+export default defineComponent({
+  name: 'ContentItem',
+  props: {
+    content: {
+      type: Content,
+      default: new Content()
+    }
+  },
+  data() {
+    return {
+    }
+  },
+  methods: {
+    goToPage () {
+      this.$router.push({ name: 'Public.Content.Show', params: { id: this.content.id } })
+    },
+    miladiToShamsi () {
+      return Assist.miladiToShamsi(this.content.creation_time, 'jD jMMMM jYYYY ساعت HH:mm')
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.content-item {
+  width: 100%;
+  background-color: #F6F6F6;
+  border-radius: 8px;
+  position: relative;
+  z-index: 1;
+
+  .thumbnail {
+    padding: 0;
+    cursor: pointer;
+    .thumbnail-img {
+      width: 100%;
+      height: auto;
+    }
+  }
+  .infos {
+    position: absolute;
+    left: 0;
+    padding-top: 12px;
+    bottom: 60px;
+    width: 100%;
+    z-index: 2;
+    -webkit-box-shadow: inset 0px -200px 55px -90px rgba(0,0,0,0.32);
+    -moz-box-shadow: inset 0px -200px 55px -90px rgba(0,0,0,0.32);
+    box-shadow: inset 0px -200px 55px -90px rgba(0,0,0,0.32);
+    .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-content {
+      width: 100%;
+      height: 60px;
+      border-top-right-radius: 0;
+      border-top-left-radius: 0;
+    }
+  }
+}
+</style>

+ 86 - 22
src/components/Widgets/Donation/DonationShow/DonationShow.vue

@@ -18,24 +18,43 @@
           <q-img :src="donationCategory.thumbnail" />
         </div>
       </div>
-      <div v-if="donation.id"
+      <div class="show-donation-selector">
+        <q-banner class="donation-category-title">
+          پرداخت
+          صدقه
+          آنلاین
+        </q-banner>
+        <div class="donation-selector-form">
+          <entity-create v-if="mounted"
+                         ref="entityCreate"
+                         v-model:value="donationSelectorInputs"
+                         :api="donationSelectorApi"
+                         :entity-id-key="donationSelectorEntityIdKey"
+                         :entity-param-key="donationSelectorEntityParamKey"
+                         :show-route-name="donationSelectorShowRouteName"
+                         :default-layout="false"
+                         :show-close-button="false"
+                         :show-edit-button="false"
+                         :show-expand-button="false"
+                         :show-save-button="false"
+                         :show-reload-button="false" />
+        </div>
+      </div>
+      <div v-if="selectedDonation?.id"
            class="show-donation-section">
         <div class="show-donation-payment-section">
           <q-banner class="donation-category-title">
-            پرداخت
-            {{donation.title}}
+            شرح مورد استفاده
+            <!--            {{selectedDonation.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" />
+                 v-html="selectedDonation.description" />
           </div>
           <div class="donation-thumbnail">
-            <q-img :src="donation.thumbnail" />
+            <q-img :src="selectedDonation.thumbnail" />
           </div>
         </div>
       </div>
@@ -44,15 +63,21 @@
 </template>
 
 <script>
-import { Donation } from 'src/models/Donation.js'
+import { shallowRef } from 'vue'
+import { EntityCreate } from 'quasar-crud'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import { DonationList } from 'src/models/Donation.js'
+import BtnControl from 'src/components/Control/btn.vue'
 import { DonationCategory } from 'src/models/DonationCategory.js'
 import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+import { FormBuilderAssist } from 'quasar-form-builder'
+
+const BtnControlComp = shallowRef(BtnControl)
 
 export default {
   name: 'DonationList',
-  components: { Breadcrumbs },
+  components: { Breadcrumbs, EntityCreate },
   mixins: [mixinWidget],
   data () {
     return {
@@ -68,21 +93,47 @@ export default {
       table: {
         columns: []
       },
-      donation: new Donation(),
+      donations: new DonationList(),
       donationCategory: new DonationCategory(),
       mounted: false,
-      createRouteName: ''
+      createRouteName: '',
+
+      donationSelectorApi: 'id',
+      donationSelectorInputs: [
+        { type: 'select', name: 'donation', responseKey: 'donation', label: 'مورد استفاده', placeholder: ' ', optionValue: 'id', optionLabel: 'title', col: 'col-md-8 col-12' },
+        { type: 'input', name: 'amount', responseKey: 'amount', label: 'مبلغ', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'fullName', responseKey: 'fullName', label: 'نام و نام خانوادگی', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'input', name: 'mobile', responseKey: 'mobile', label: 'موبایل(جهت اصلاع رسانی)', placeholder: ' ', col: 'col-md-4 col-12' },
+        { type: 'checkbox', name: 'private', responseKey: 'private', label: 'ناشناس بمانم', placeholder: ' ', value: false, class: 'flex justify-end items-end full-height', col: 'col-md-2 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'پرداخت', placeholder: ' ', atClick: () => {}, class: 'flex justify-end items-end full-height', col: 'col-md-2 col-12' }
+      ],
+      donationSelectorEntityIdKey: 'id',
+      donationSelectorEntityParamKey: 'id',
+      donationSelectorShowRouteName: 'Admin.Donation.Show'
+    }
+  },
+  computed: {
+    selectedDonation () {
+      const donationId = FormBuilderAssist.getInputsByName(this.donationSelectorInputs, 'donation')?.value
+      if (!donationId) {
+        return null
+      }
+
+      return this.donations.list.find(item => item.id === donationId)
     }
   },
   mounted() {
-    this.getDonation()
+    this.getDonations()
   },
   methods: {
-    getDonation () {
-      this.donation.loading = true
-      APIGateway.donation.get(this.$route.params.donation_category_id)
-        .then((donation) => {
-          this.donation = new Donation(donation)
+    getDonations () {
+      this.donations.loading = true
+      APIGateway.donation.index({
+        category: this.$route.params.donation_category_id
+      })
+        .then(({ list }) => {
+          this.donations = new DonationList(list)
+          FormBuilderAssist.setAttributeByName(this.donationSelectorInputs, 'donation', 'options', this.donations.list)
           this.getDonationCategory()
         })
     },
@@ -110,10 +161,6 @@ export default {
           {
             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 } }
           }
         ]
       })
@@ -147,6 +194,23 @@ export default {
         .donation-category-description {}
       }
     }
+    .show-donation-selector {
+      .donation-category-title {
+        margin-bottom: 34px;
+      }
+      .donation-selector-form {
+        background: #F5F5F5;
+        padding: 44px 24px 24px 24px;
+        margin-bottom: 60px;
+        :deep(.entity-crud-formBuilder) {
+          .form-builder-checkbox {
+            .q-checkbox {
+              height: 48px !important;
+            }
+          }
+        }
+      }
+    }
     .show-donation-section {
       .show-donation-payment-section {
         margin-bottom: 16px;

+ 1 - 1
src/components/Widgets/Event/LastEvents/LastEvents.vue

@@ -82,7 +82,7 @@ export default {
         900: {
           perPage: 2
         },
-        500: {
+        600: {
           perPage: 1
         }
       },

+ 28 - 4
src/components/Widgets/FeatureBox/OptionPanel.vue

@@ -19,7 +19,8 @@
         </div>
         <div class="input-container q-py-md">
           <div class="outsideLabel">description</div>
-          <editor v-model:value="localOptions.description" />
+          <form-builder-tiptap-editor v-model:value="localOptions.description"
+                                      :options="editorOptions" />
         </div>
       </div>
     </template>
@@ -27,15 +28,17 @@
 </template>
 <script>
 import { defineComponent } from 'vue'
-import Editor from 'src/components/Utils/Editor.vue'
+import { APIGateway } from 'src/api/APIGateway.js'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
 import ImageUploader from 'src/components/ImageUploader.vue'
+import PostMixin from 'src/components/Widgets/Post/PostMixin.js'
+import FormBuilderTiptapEditor from 'src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
 
 export default defineComponent({
   name: 'OptionPanel',
-  components: { OptionPanelTabs, ImageUploader, Editor },
-  mixins: [mixinOptionPanel],
+  components: { OptionPanelTabs, ImageUploader, FormBuilderTiptapEditor },
+  mixins: [mixinOptionPanel, PostMixin],
   props: {
     options: {
       type: Object,
@@ -45,6 +48,10 @@ export default defineComponent({
     }
   },
   data() {
+    const authorization = 'Bearer ' + this.$store.getters['Auth/accessToken']
+    const uploadVideo = this.uploadVideo
+    const uploadAudio = this.uploadAudio
+
     return {
       defaultOptions: {
         title: null,
@@ -69,6 +76,23 @@ export default defineComponent({
             translateX: 0,
             translateY: 0
           }
+        },
+        editorOptions: {
+          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
         }
       }
     }

+ 1 - 5
src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue

@@ -175,11 +175,7 @@ export default {
   },
   methods: {
     getInvoiceTitle (invoice) {
-      if (invoice.item_info.item_type === 'CLASSROOM') {
-        return invoice.item_info.title
-      } else {
-        return invoice.item_info.title || '-'
-      }
+      return invoice.title || '-'
     },
     search () {
       this.$refs.entityIndex.search()

+ 179 - 5
src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue

@@ -83,17 +83,29 @@
       <q-separator />
       <q-card-section>
         <div class="row">
-          <div class="col-md-4 col-12 flex items-center justify-start">
+          <div v-if="invoice.item_type==='CLASSROOM'"
+               class="col-md-4 col-12 flex items-center justify-start">
             <q-img :src="invoice.item_info.thumbnail"
                    width="100px" />
             {{ invoice.item_info.title }}
           </div>
-          <div class="col-md-4 col-12">
+          <div v-else
+               class="col-md-4 col-12 flex items-center justify-start">
+            {{ invoice.title }}
+          </div>
+          <div v-if="invoice.item_type==='CLASSROOM'"
+               class="col-md-4 col-12">
             <q-icon name="isax:user" />
             استاد:
             {{ invoice.item_info.professor_info.firstname }}
             {{ invoice.item_info.professor_info.lastname }}
           </div>
+          <div v-else-if="invoice.item_type==='STORE_BASKET'"
+               class="col-md-4 col-12">
+            <q-icon name="isax:user" />
+            کد سفارش:
+            {{ invoice.item_info.id }}
+          </div>
           <div class="col-md-4 col-12">
             <q-icon name="isax:card" />
             قیمت:
@@ -110,7 +122,7 @@
         </q-card-section>
         <q-separator />
         <q-card-section>
-          <invoice-payment-card v-if="!invoice.loading && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
+          <invoice-payment-card v-if="!invoice.loading && invoice.status!=='PAID_FULL'"
                                 :wallet="wallet"
                                 :invoice="invoice"
                                 :show-invoice-link="false"
@@ -126,21 +138,80 @@
       </q-card>
 
     </div>
+    <div v-if="invoice.item_type==='STORE_BASKET'"
+         class="q-mt-md">
+      <entity-index v-model:value="tableInputs"
+                    title="لیست محصولات سفارش"
+                    :table="table"
+                    :loaded-data="invoice.item_info"
+                    :table-keys="tableKeys"
+                    :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 === 'download'">
+            <q-btn v-if="!inputData.col.value"
+                   color="primary"
+                   @click="openDownloadDialog(inputData.props.row)">
+              <q-icon name="download"
+                      class="q-mr-sm" />
+              دانلود
+            </q-btn>
+            <template v-else>
+              {{}}
+            </template>
+          </template>
+          <template v-else-if="inputData.col.name === 'action'">
+            <q-btn color="primary"
+                   outline
+                   :to="{name: inputData.props.row.product ? 'Admin.Store.Product.Show' : 'Admin.Store.Package.Show', params: {id: inputData.props.row.product || inputData.props.row.package}}">
+              <q-icon name="visibility"
+                      class="q-mr-sm" />
+              مشاهده
+            </q-btn>
+          </template>
+          <template v-else>
+            {{ inputData.col.value }}
+          </template>
+        </template>
+      </entity-index>
+      <q-dialog v-model="downloadDialog">
+        <q-card style="width: 800px; max-width: 95%;">
+          <q-card-section>
+            محتوای قابل دانلود
+          </q-card-section>
+          <q-separator />
+          <q-card-section>
+            <product-or-package-medias :basket-item="selectedBasketItem" />
+          </q-card-section>
+        </q-card>
+      </q-dialog>
+    </div>
   </div>
 </template>
 
 <script>
 import Assist from 'assets/js/Assist.js'
 import { User } from 'src/models/User.js'
+import { EntityIndex } from 'quasar-crud'
 import { Wallet } from 'src/models/Wallet.js'
+import { Product } from 'src/models/Product.js'
 import { Invoice } from 'src/models/Invoice.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import InvoicePaymentCard from 'src/components/InvoicePaymentCard/InvoicePaymentCard.vue'
+import ProductOrPackageMedias from 'src/components/Widgets/Store/Basket/UserStoreBasketShow/ProductOrPackageMedias.vue'
 
 export default {
   name: 'AdminInvoiceShow',
-  components: { InvoicePaymentCard },
+  components: { InvoicePaymentCard, EntityIndex, ProductOrPackageMedias },
   mixins: [mixinWidget],
   props: {
     invoiceId: {
@@ -148,13 +219,112 @@ export default {
       default: null
     }
   },
-  data: () => {
+  data () {
     return {
       user: new User(),
       wallet: new Wallet(),
       invoice: new Invoice(),
       defaultOptions: {
         showBackBtn: true
+      },
+
+      downloadDialog: false,
+      selectedBasketItem: null,
+      tableKeys: {
+        data: 'items_info.list',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      tableInputs: [],
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'id',
+            required: true,
+            label: 'شناسه',
+            align: 'left',
+            field: row => row.product_info?.id ? row.product_info.id : row.package_info.id
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'نام محصول',
+            align: 'left',
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).title
+          },
+          {
+            name: 'is_package_or_product',
+            required: true,
+            label: 'نوع محصول',
+            align: 'left',
+            field: row => row.package ? 'بسته' : 'تکی'
+          },
+          {
+            name: 'store_category',
+            required: true,
+            label: 'دسته فروشگاه',
+            align: 'left',
+            field: row => ((row.product_info?.id ? row.product_info : row.package_info).store_category_info?.parent?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).store_category_info?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).store_category_info?.title || '-')
+          },
+          {
+            name: 'store_category1',
+            required: true,
+            label: 'دسته محتوا',
+            align: 'left',
+            field: row => ((row.product_info?.id ? row.product_info : row.package_info).content_category_info?.parent?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).content_category_info?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).content_category_info?.title || '-')
+          },
+          {
+            name: 'count',
+            required: true,
+            label: 'تعداد',
+            align: 'left',
+            field: row => row.count
+          },
+          {
+            name: 'is_physical_info',
+            required: true,
+            label: 'نوع جنس',
+            align: 'left',
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).is_physical_info.label
+          },
+          {
+            name: 'physical_type_info',
+            required: true,
+            label: 'نوع محصول',
+            align: 'left',
+            field: row => row.product_info ? (new Product(row.product_info)).physical_type_info.label : '-'
+          },
+          {
+            name: 'unit_price',
+            required: true,
+            label: 'قیمت',
+            align: 'left',
+            field: row => parseInt((row.product_info?.id ? row.product_info : row.package_info).unit_price?.toString()).toLocaleString('fa')
+          },
+          {
+            name: 'download',
+            required: true,
+            label: 'دانلود',
+            align: 'left',
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).is_physical
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'عملیات',
+            align: 'left',
+            field: row => ''
+          }
+        ]
       }
     }
   },
@@ -164,6 +334,10 @@ export default {
     this.getInvoice()
   },
   methods: {
+    openDownloadDialog (basketItem) {
+      this.selectedBasketItem = basketItem
+      this.downloadDialog = true
+    },
     loadAuthData() { // prevent Hydration node mismatch
       this.user = this.$store.getters['Auth/user']
     },

+ 4 - 2
src/components/Widgets/Invoice/InvoiceShow/InvoiceShow.vue

@@ -89,13 +89,13 @@
       </q-card-section>
     </q-card>
     <div class="action q-mt-lg">
-      <q-card v-if="!invoice.loading && invoice.status!=='PAYING' && invoice.status!=='PAID_FULL' && invoice.status!=='PAID_IN_INSTALMENT'"
+      <q-card v-if="!invoice.loading && !(invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT')"
               class="q-mb-md">
         <q-card-section>
           تنها در صورتی که وضعیت سفارش در حال پرداخت باشد امکان پرداخت با کیف پول وجود دارد
         </q-card-section>
       </q-card>
-      <q-card v-if="!invoice.loading && (invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT')">
+      <q-card v-if="!invoice.loading && (invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT' || invoice.status==='PAID_IN_INSTALMENT')">
         <q-card-section>
           پرداخت
         </q-card-section>
@@ -105,6 +105,8 @@
                                 :invoice="invoice"
                                 class="payment-card"
                                 :can-action="localOptions.canAction"
+                                :show-message="invoice.status==='PAYING'"
+                                :show-wallet="invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT'"
                                 @onCancel="onCancel"
                                 @onAccept="onAccept"
                                 @onInstalmentPayed="onInstalmentPayed" />

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

@@ -82,7 +82,7 @@ export default {
         900: {
           perPage: 2
         },
-        500: {
+        600: {
           perPage: 1
         }
       },

+ 197 - 4
src/components/Widgets/Other/Shop/ShopCompleteInfo/ShopCompleteInfo.vue

@@ -150,6 +150,7 @@ import { Classroom } from 'src/models/Classroom.js'
 import { AnswerBook } from 'src/models/AnswerBook.js'
 import { FormBuilderAssist } from 'quasar-form-builder'
 import ShowEventTest from './components/ShowEventTest.vue'
+import { Provinces, Cities, getCitiesOfProvince } from 'src/assets/js/IranianCities.js'
 
 export default {
   name: 'ShopCompleteInfo',
@@ -157,6 +158,7 @@ export default {
   mixins: [mixinAuth],
   data: () => ({
     mounted: false,
+    inputDataLoaded: false,
     panel: 'userInfo',
     step: false,
     rulesAccept: false,
@@ -233,18 +235,76 @@ export default {
       },
       { type: 'checkbox', name: 'is_abroad_birth_address', placeholder: ' ', label: 'محل تولد خارج از کشور است؟', responseKey: 'is_abroad_birth_address', className: 'require', col: 'col-12' },
       { type: 'input', name: 'birth_country', label: 'کشور محل تولد', responseKey: 'birth_country', placeholder: ' ', className: 'require', col: 'col-md-4 col-12' },
-      { type: 'input', name: 'birth_province', label: 'استان محل تولد', responseKey: 'birth_province', placeholder: ' ', className: 'require', col: 'col-md-4 col-12' },
-      { type: 'input', name: 'birth_city', label: 'شهر محل تولد', responseKey: 'birth_city', placeholder: ' ', className: 'require', col: 'col-md-4 col-12' },
+      {
+        type: 'select',
+        name: 'birth_province',
+        options: Provinces,
+        optionLabel: 'name',
+        optionValue: 'name',
+        createNewValue: true,
+        newValueMode: 'add-unique',
+        label: 'استان محل تولد',
+        responseKey: 'birth_province',
+        placeholder: ' ',
+        className: 'require',
+        col: 'col-md-4 col-12'
+      },
+      {
+        type: 'select',
+        name: 'birth_city',
+        options: Cities,
+        optionLabel: 'name',
+        optionValue: 'name',
+        createNewValue: true,
+        newValueMode: 'add-unique',
+        label: 'شهر محل تولد',
+        responseKey: 'birth_city',
+        placeholder: ' ',
+        className: 'require',
+        col: 'col-md-4 col-12'
+      },
       { type: 'checkbox', name: 'is_abroad_living_address', placeholder: ' ', label: 'محل زندگی فعلی خارج از کشور است؟', responseKey: 'is_abroad_living_address', className: 'require', col: 'col-12' },
       { type: 'input', name: 'living_country', label: 'کشور محل زندگی فعلی', responseKey: 'living_country', placeholder: ' ', className: 'require', col: 'col-md-4 col-12' },
-      { type: 'input', name: 'living_province', label: 'استان محل زندگی فعلی', responseKey: 'living_province', placeholder: ' ', className: 'require', col: 'col-md-4 col-12' },
-      { type: 'input', name: 'living_city', label: 'شهر محل زندگی فعلی', responseKey: 'living_city', placeholder: ' ', className: 'require', col: 'col-md-4 col-12' },
+      {
+        type: 'select',
+        name: 'living_province',
+        options: Provinces,
+        optionLabel: 'name',
+        optionValue: 'name',
+        createNewValue: true,
+        newValueMode: 'add-unique',
+        label: 'استان محل زندگی فعلی',
+        responseKey: 'living_province',
+        placeholder: ' ',
+        className: 'require',
+        col: 'col-md-4 col-12'
+      },
+      {
+        type: 'select',
+        name: 'living_city',
+        options: Cities,
+        optionLabel: 'name',
+        optionValue: 'name',
+        createNewValue: true,
+        newValueMode: 'add-unique',
+        label: 'شهر محل زندگی فعلی',
+        responseKey: 'living_city',
+        placeholder: ' ',
+        className: 'require',
+        col: 'col-md-4 col-12'
+      },
       { type: 'input', name: 'living_postal_code', label: 'کد پستی محل زندگی', responseKey: 'living_postal_code', placeholder: ' ', className: 'require', col: ' col-md-3 col-12' },
       { type: 'input', name: 'living_address', label: 'آدرس پستی محل زندگی', responseKey: 'living_address', placeholder: ' ', className: 'require', col: 'col-md-9 col-12' },
       { type: 'hidden', name: 'id', responseKey: 'id' }
     ]
   }),
   computed: {
+    isAbroadBirthAddress () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'is_abroad_birth_address').value
+    },
+    isAbroadLivingAddress () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'is_abroad_living_address').value
+    },
     mainTitle () {
       if (this.$route.query.source === 'event') {
         return 'ثبت نام در رویداد'
@@ -258,6 +318,136 @@ export default {
       } else {
         return 'مشاهده جزییات و قوانین دوره'
       }
+    },
+    selectedBirthProvince () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'birth_province').value
+    },
+    selectedLivingProvince () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'living_province').value
+    }
+  },
+  watch: {
+    isAbroadBirthAddress: {
+      immediate: true,
+      handler (newValue) {
+        this.$nextTick(() => {
+          if (!newValue) {
+            FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'value', 'ایران')
+            FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'readonly', true)
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'type', 'select')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'options', Provinces)
+
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'select')
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', true)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'options', getCitiesOfProvince(this.selectedBirthProvince))
+            }
+          } else {
+            // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'value', null)
+            FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'readonly', false)
+
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'disable', false)
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'value', null)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+            }
+          }
+        })
+      }
+    },
+    isAbroadLivingAddress: {
+      immediate: true,
+      handler (newValue) {
+        this.$nextTick(() => {
+          if (!newValue) {
+            FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'value', 'ایران')
+            FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'readonly', true)
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'type', 'select')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'options', Provinces)
+
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'select')
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', true)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'options', getCitiesOfProvince(this.selectedLivingProvince))
+            }
+          } else {
+            // FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'value', null)
+            FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'readonly', false)
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'disable', false)
+
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'value', null)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+            }
+          }
+        })
+      }
+    },
+    selectedBirthProvince (newValue) {
+      if (this.isAbroadBirthAddress) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'input')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+        return
+      }
+
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', true)
+        if (this.inputDataLoaded) {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+        }
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedBirthProvince)
+
+      FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+      }
+    },
+    selectedLivingProvince (newValue) {
+      if (this.isAbroadBirthAddress) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'input')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+        return
+      }
+
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', true)
+        if (this.inputDataLoaded) {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+        }
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedLivingProvince)
+
+      FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+      }
     }
   },
   mounted () {
@@ -385,6 +575,9 @@ export default {
     },
     afterLoadInputData () {
       this.checkNationalCode()
+      this.$nextTick(() => {
+        this.inputDataLoaded = true
+      })
     },
     checkNationalCode () {
       if (FormBuilderAssist.getInputsByName(this.inputs, 'national_code')?.value) {

+ 192 - 0
src/components/Widgets/Post/LastPosts/LastPosts.vue

@@ -0,0 +1,192 @@
+<template>
+  <div class="LastPostsCarousel">
+    <div class="LastPostsCarousel-title">
+      <div>
+        <q-banner class="banner">
+          آخرین
+          اخبار
+        </q-banner>
+      </div>
+      <div>
+        <q-btn flat
+               :to="{name: 'Public.Post.List.News'}">
+          مشاهده همه
+          <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">
+      <div v-if="!posts.loading && posts.list.length > 0"
+           ref="slider"
+           class="splide"
+           role="group"
+           aria-label="Splide Basic HTML Example">
+        <div class="splide__track">
+          <ul class="splide__list">
+            <li v-for="(post, postIndex) in posts.list"
+                :key="postIndex"
+                class="splide__slide">
+              <post-item :post="post" />
+            </li>
+          </ul>
+        </div>
+      </div>
+      <div v-else-if="!posts.loading && posts.list.length === 0">
+        رویدادی یافت نشد.
+      </div>
+      <div v-else-if="posts.loading">
+        کمی صبر کنید ...
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Splide from '@splidejs/splide'
+import { PostList } from 'src/models/Post.js'
+import PostItem from './components/PostItem.vue'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import '@splidejs/splide/dist/css/splide-core.min.css'
+import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'LastPosts',
+  components: {
+    PostItem
+  },
+  mixins: [mixinPrefetchServerData, mixinWidget],
+  data () {
+    return {
+      loading: false,
+      slider: null,
+      posts: new PostList(),
+      defaultOptions: {
+        categoryId: null,
+        categoryCategoryId: null,
+        categoryCategoryCategoryId: null,
+        categoryFromQueryParams: false,
+        categoryCategoryFromQueryParams: false,
+        categoryCategoryCategoryFromQueryParams: false
+      },
+      breakpoints: {
+        1920: {
+          perPage: 1
+        },
+        900: {
+          perPage: 1
+        },
+        600: {
+          perPage: 1
+        }
+      },
+      maximizedToggle: true
+    }
+  },
+  computed: {
+    postCategoryId () {
+      if (this.localOptions.categoryId) {
+        return this.localOptions.categoryId
+      }
+
+      return null
+    }
+  },
+  mounted () {
+    this.loadSlider()
+  },
+  methods: {
+    loadSlider () {
+      this.$nextTick(() => {
+        if (!this.$refs.slider) {
+          return
+        }
+        new Splide(this.$refs.slider, {
+          direction: 'rtl',
+          paginationDirection: 'rtl',
+          // type: 'loop',
+          focus: 0,
+          gap: 24,
+          snap: true,
+          // focus: 'center',
+          breakpoints: this.breakpoints
+        }).mount()
+      })
+    },
+    prefetchServerDataPromise () {
+      return this.getLastPosts()
+    },
+    prefetchServerDataPromiseThen (postList) {
+      this.posts = new PostList(postList.list)
+      this.posts.loading = false
+      this.loadSlider()
+    },
+    prefetchServerDataPromiseCatch () {
+      this.posts.loading = false
+    },
+
+    getTerm (event) {
+      if (!event.beginning_registration_period) {
+        return '-'
+      }
+      return ShamsiDate.getTerm(event.beginning_registration_period)
+    },
+    getLastPosts () {
+      const params = {}
+
+      if (this.localOptions.categoryCategoryCategoryId) {
+        params.category__parent__parent = this.localOptions.categoryCategoryCategoryId
+      }
+
+      if (this.localOptions.categoryCategoryId) {
+        params.category__parent = this.localOptions.categoryCategoryId
+      }
+
+      if (this.localOptions.categoryId) {
+        params.category = this.localOptions.categoryId
+      }
+
+      this.posts.loading = true
+      return APIGateway.post.index(params)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.LastPostsCarousel {
+  padding: 60px 0;
+  .LastPostsCarousel-title {
+    display: flex;
+    flex-flow: row;
+    justify-content: space-between;
+    padding-bottom: 33px;
+  }
+  :deep(.carousel-section) {
+    .splide__arrows {
+      top: auto;
+      bottom: 0;
+      .splide__arrow {
+        &.splide__arrow--prev {
+          left: 50%;
+          transform: translateX(-120%);
+        }
+        &.splide__arrow--next {
+          right: 50%;
+          transform: translateX(120%);
+        }
+      }
+    }
+  }
+}
+</style>

+ 65 - 0
src/components/Widgets/Post/LastPosts/OptionPanel.vue

@@ -0,0 +1,65 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div class="q-mt-md">
+        کد دسته پدربزرگ
+      </div>
+      <div class="row">
+        <div class="col-md-8 col-12">
+          <q-input v-model="localOptions.categoryCategoryCategoryId" />
+        </div>
+      </div>
+      <div>
+        کد دسته پدر
+      </div>
+      <div class="row">
+        <div class="col-md-4 col-12">
+          <q-checkbox v-model="localOptions.categoryCategoryFromQueryParams"
+                      label="استفاده از کوری پارام صفحه" />
+        </div>
+        <div v-if="!localOptions.categoryCategoryFromQueryParams"
+             class="col-md-8 col-12">
+          <q-input v-model="localOptions.categoryCategoryId" />
+        </div>
+      </div>
+      <div>
+        کد دسته فرزند
+      </div>
+      <div class="row">
+        <div class="col-md-4 col-12">
+          <q-checkbox v-model="localOptions.categoryFromQueryParams"
+                      label="استفاده از کوری پارام صفحه" />
+        </div>
+        <div v-if="!localOptions.categoryFromQueryParams"
+             class="col-md-8 col-12">
+          <q-input v-model="localOptions.categoryId" />
+        </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 {
+      defaultOptions: {
+        categoryId: null,
+        categoryCategoryId: null,
+        categoryCategoryCategoryId: null,
+        categoryFromQueryParams: false,
+        categoryCategoryFromQueryParams: false
+      }
+    }
+  }
+})
+</script>

+ 149 - 0
src/components/Widgets/Post/LastPosts/components/PostItem.vue

@@ -0,0 +1,149 @@
+<template>
+  <div class="col-md-4 col-sm-6 col-12">
+    <q-card flat
+            class="PostItem">
+      <div class="content">
+        <div class="title-and-body">
+          <div class="title ellipsis-2-lines">
+            <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
+              {{post.title}}
+            </router-link>
+          </div>
+          <div class="body"
+               v-html="post.text" />
+        </div>
+        <div class="meta-and-action">
+          <div class="date-time">
+            <q-icon name="calendar_month"
+                    size="20px" />
+            <span class="date-time-value">
+              {{ getDateTime(post.creation_time) }}
+            </span>
+          </div>
+          <q-btn color="primary"
+                 outline
+                 class="btn-show-post"
+                 :to="{name: 'Public.Post.Show', params: {id: post.id}}">
+            مشاهده جزئیات
+          </q-btn>
+        </div>
+      </div>
+      <div class="thumbnail">
+        <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
+          <q-img :src="post.thumbnail" />
+        </router-link>
+      </div>
+    </q-card>
+  </div>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { Post } from 'src/models/Post.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+
+export default defineComponent({
+  name: 'PostItem',
+  props: {
+    post: {
+      type: Post,
+      default: new Post()
+    }
+  },
+  methods: {
+    getDateTime (dateTime) {
+      return ShamsiDate.getTitledDateTime(dateTime, 'a hh:mm - jYYYY/jMM/jDD')
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.PostItem {
+  display: flex;
+  flex-flow: row;
+  justify-content: space-between;
+  background-color: white;
+  border-radius: 8px;
+  padding: 0;
+  margin: 0;
+  $thumbnail-width: 400px;
+  .thumbnail {
+    //width: 45%;
+    width: $thumbnail-width;
+    .q-img {
+      border-radius: 0 8px 8px 0;
+    }
+  }
+  .content {
+    //width: 55%;
+    width: calc( 100% - #{$thumbnail-width} );
+    padding: 52px 80px 52px 40px;
+    display: flex;
+    flex-flow: column;
+    justify-content: space-between;
+    .title-and-body {
+      margin-bottom: 28px;
+      .title {
+        font-size: 18px;
+        letter-spacing: 0;
+        margin-bottom: 26px;
+        a {
+          color: #212121;
+        }
+      }
+      .body {
+        font-size: 16px;
+        letter-spacing: 0;
+        color: #212121;
+        height: 220px;
+        overflow: hidden;
+      }
+    }
+    .meta-and-action {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .date-time {
+        color: #929292;
+        .date-time-value {
+          margin-left: 8px;
+        }
+      }
+    }
+  }
+  @media screen and (max-width: 1024px) {
+    & {
+      flex-flow: column;
+      .thumbnail {
+        order: 1;
+        width: 100%;
+        margin-bottom: 8px;
+        .q-img {
+          border-radius: 8px;
+        }
+      }
+      .content {
+        order: 2;
+        width: 100%;
+        padding: 24px !important;
+      }
+    }
+  }
+  @media screen and (max-width: 600px) {
+    & {
+      .content {
+        .meta-and-action {
+          flex-flow: column;
+          justify-content: space-between;
+          align-items: flex-start;
+          gap: 8px;
+          .q-btn {
+            width: 100%;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 6 - 0
src/components/Widgets/Post/PostShow/PostShow.vue

@@ -292,10 +292,16 @@ export default {
     .summary-body {
       font-size: 20px;
       color: #707070;
+      img {
+        max-width: 100% !important;
+      }
     }
   }
   :deep(.post-body) {
     margin-top: 32px;
+    img {
+      max-width: 100% !important;
+    }
     .barehtml {
       width: 600px;
       max-width: 100%;

+ 1 - 1
src/components/Widgets/ProjectAttendanceSheet/ProjectAttendanceSheetShow/ProjectAttendanceSheetShow.vue

@@ -88,7 +88,7 @@
           تنها در صورتی که وضعیت سفارش در حال پرداخت باشد امکان پرداخت با کیف پول وجود دارد
         </q-card-section>
       </q-card>
-      <q-card v-if="!invoice.loading && (invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT')">
+      <q-card v-if="!invoice.loading && (invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT' || invoice.status==='PAID_IN_INSTALMENT')">
         <q-card-section>
           پرداخت
         </q-card-section>

+ 1 - 1
src/components/Widgets/Registration/RegistrationShow/RegistrationShow.vue

@@ -88,7 +88,7 @@
           تنها در صورتی که وضعیت سفارش در حال پرداخت باشد امکان پرداخت با کیف پول وجود دارد
         </q-card-section>
       </q-card>
-      <q-card v-if="!invoice.loading && (invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT')">
+      <q-card v-if="!invoice.loading && (invoice.status==='PAYING' || invoice.status==='PAYING_IN_INSTALMENT' || invoice.status==='PAID_IN_INSTALMENT')">
         <q-card-section>
           پرداخت
         </q-card-section>

+ 48 - 33
src/components/Widgets/Statictic/PublicWebStatistic/PublicWebStatistic.vue

@@ -7,48 +7,63 @@
         </q-banner>
       </div>
     </div>
-    <div class="PublicWebStatistic-boxes row">
-      <div class="PublicWebStatistic-boxes row" />
+    <div class="PublicWebStatistic-boxes">
+      <q-linear-progress v-if="loading"
+                         indeterminate />
+      <div v-else
+           class="row q-col-gutter-md">
+        <div class="col-md-4 col-12">
+          <statistic-item :value="statistics.users_count"
+                          title="کاربر فعال"
+                          icon="groups" />
+        </div>
+        <div class="col-md-4 col-12">
+          <statistic-item :value="statistics.training_classrooms_count"
+                          title="دوره"
+                          icon="co_present" />
+        </div>
+        <div class="col-md-4 col-12">
+          <statistic-item :value="statistics.event_classrooms_count"
+                          title="رویداد"
+                          icon="event_note" />
+        </div>
+      </div>
     </div>
   </div>
 </template>
 
 <script>
-import { User } from 'src/models/User.js'
-// import ShamsiDate from 'src/assets/ShamsiDate.js'
-// import { APIGateway } from 'src/api/APIGateway.js'
-import { ClassroomList } from 'src/models/Classroom.js'
-import { ClassroomRegistrationList } from 'src/models/ClassroomRegistration.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import StatisticItem from './components/Item.vue'
 
 export default {
   name: 'PublicWebStatistic',
-  data: () => ({
-    loading: false,
-    user: new User(),
-    classroomsKey: Date.now(),
-    userRegistrations: new ClassroomRegistrationList(),
-    slide: 0,
-    classrooms: new ClassroomList(),
-    breakpoints: {
-      // 1024 and up
-      1024: {
-        itemsToShow: 3,
-        snapAlign: 'start'
-      },
-      // 700px and up
-      700: {
-        itemsToShow: 3.5,
-        snapAlign: 'center'
-      },
-      // 300px and up
-      300: {
-        itemsToShow: 1.5,
-        snapAlign: 'center'
+  components: { StatisticItem },
+  data () {
+    return {
+      loading: false,
+      statistics: {
+        users_count: 0,
+        training_classrooms_count: 0,
+        event_classrooms_count: 0
       }
-    },
-    maximizedToggle: true,
-    dialog: false
-  })
+    }
+  },
+  mounted() {
+    this.getStatistics()
+  },
+  methods: {
+    getStatistics () {
+      this.loading = true
+      APIGateway.aggregator.statistics()
+        .then((statistics) => {
+          this.statistics = statistics
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    }
+  }
 }
 </script>
 

+ 68 - 0
src/components/Widgets/Statictic/PublicWebStatistic/components/Item.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="item">
+    <div class="item-icon">
+      <q-icon :name="icon" />
+    </div>
+    <div class="item-content">
+      <div class="item-title">
+        {{ title }}
+      </div>
+      <div class="item-value">
+        {{ value }}
+        +
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'StatisticItem',
+  props: {
+    title: {
+      type: String,
+      default: undefined
+    },
+    value: {
+      type: String,
+      default: undefined
+    },
+    icon: {
+      type: String,
+      default: undefined
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.item {
+  border-radius: 20px;
+  background: #FBF4EA;
+  padding: 64px 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 20px;
+  .item-icon {
+    color: #EAC38A;
+    width: 70px;
+    .q-icon {
+      font-size: 70px;
+    }
+  }
+  .item-content {
+    width: fit-content;
+    .item-title {
+      font-size: 18px;
+      color: #212121;
+      margin-bottom: 8px;
+    }
+    .item-value {
+      font-size: 18px;
+      font-weight: bold;
+      color: #212121;
+    }
+  }
+}
+</style>

+ 53 - 10
src/components/Widgets/Store/Basket/AdminStoreBasketList/AdminStoreBasketList.vue

@@ -3,12 +3,13 @@
        :style="localOptions.style">
     <div v-if="showBackBtn"
          class="more-action">
-      <q-btn color="primary">
-        <q-icon name="file_download" />
-        دریافت
-      </q-btn>
+         <!--      <q-btn color="primary">-->
+         <!--        <q-icon name="file_download" />-->
+         <!--        دریافت-->
+         <!--      </q-btn>-->
     </div>
     <entity-index v-if="mounted"
+                  ref="entityIndex"
                   v-model:value="inputs"
                   title="لیست سفارشات"
                   :api="api"
@@ -41,11 +42,15 @@
 </template>
 
 <script>
+import { shallowRef } from 'vue'
+import Assist from 'assets/js/Assist.js'
 import { EntityIndex } from 'quasar-crud'
 import { Basket } from 'src/models/Basket.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
-import Assist from 'assets/js/Assist'
+import BtnControl from 'src/components/Control/btn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
 
 export default {
   name: 'AdminBasketList',
@@ -67,7 +72,40 @@ export default {
         perPage: 'per_page',
         pageKey: 'page'
       },
-      inputs: [],
+      inputs: [
+        {
+          type: 'input',
+          name: 'search',
+          label: 'جست جو',
+          placeholder: ' ',
+          col: 'col-md-4 col-12'
+        },
+        {
+          type: 'select',
+          name: 'order_type',
+          label: 'نوع سفارش',
+          options: (new Basket()).order_typeEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        {
+          type: 'select',
+          name: 'status',
+          label: 'وضعیت',
+          options: [
+            {
+              label: 'پرداخت شده',
+              value: 'PAID'
+            },
+            {
+              label: 'ارسال شده',
+              value: 'SENT'
+            }],
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: this.search, col: 'col-md-2 col-12' }
+      ],
       table: {
         columns: [
           {
@@ -103,12 +141,12 @@ export default {
             required: true,
             label: 'نوع سفارش',
             align: 'left',
-            field: () => '-'
+            field: (row) => row.order_type ? new Basket(row).order_type_info.label : '-'
           },
           {
             name: 'overall_order_price',
             required: true,
-            label: 'قیمت کل(تومان)',
+            label: 'قیمت کل(ریال)',
             align: 'left',
             field: row => parseInt(row.overall_order_price).toLocaleString('fa')
           },
@@ -120,11 +158,11 @@ export default {
             field: row => (new Basket(row)).status_info.label
           },
           {
-            name: 'creation_time',
+            name: 'order_time',
             required: true,
             label: 'تاریخ ثبت',
             align: 'left',
-            field: row => Assist.miladiToShamsi(row.creation_time)
+            field: row => Assist.miladiToShamsi(row.order_time)
           },
           {
             name: 'action',
@@ -141,6 +179,11 @@ export default {
   },
   mounted() {
     this.mounted = true
+  },
+  methods: {
+    search () {
+      this.$refs.entityIndex.search()
+    }
   }
 }
 </script>

+ 135 - 0
src/components/Widgets/Store/Basket/AdminStoreBasketPurchaseRegulations/AdminStoreBasketPurchaseRegulations.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="AdminPostCreate"
+       :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 FormBuilderTiptapEditor from 'src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue'
+import { FormBuilderAssist } from 'quasar-form-builder'
+
+const BtnControlComp = shallowRef(BtnControl)
+const FormBuilderTiptapEditorComp = shallowRef(FormBuilderTiptapEditor)
+
+export default {
+  name: 'AdminPostCreate',
+  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: 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: this.onSubmit, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted () {
+    this.getPurchaseRegulations()
+  },
+  methods: {
+    getPurchaseRegulations () {
+      this.purchaseRegulationsLoading = true
+      APIGateway.appSetting.get('PurchaseRegulations')
+        .then((purchaseRegulations) => {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'text', 'value', purchaseRegulations.value)
+          this.mounted = true
+        })
+        .finally(() => {
+          this.purchaseRegulationsLoading = false
+        })
+    },
+    onSubmit () {
+      const formData = FormBuilderAssist.getFormData(this.inputs)
+      this.purchaseRegulationsLoading = true
+      APIGateway.appSetting.update({ key: 'PurchaseRegulations', value: formData.text })
+        .then((purchaseRegulations) => {
+          this.purchaseRegulations = purchaseRegulations
+        })
+        .finally(() => {
+          this.purchaseRegulationsLoading = false
+        })
+    }
+  }
+}
+</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>

+ 21 - 0
src/components/Widgets/Store/Basket/AdminStoreBasketPurchaseRegulations/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>

+ 56 - 11
src/components/Widgets/Store/Basket/AdminStoreBasketShow/AdminStoreBasketShow.vue

@@ -61,6 +61,18 @@
             <q-img :src="inputData.col.value"
                    width="100px" />
           </template>
+          <template v-else-if="inputData.col.name === 'download'">
+            <q-btn v-if="!inputData.col.value"
+                   color="primary"
+                   @click="openDownloadDialog(inputData.props.row)">
+              <q-icon name="download"
+                      class="q-mr-sm" />
+              دانلود
+            </q-btn>
+            <template v-else>
+              {{}}
+            </template>
+          </template>
           <template v-else-if="inputData.col.name === 'action'">
             <q-btn color="primary"
                    outline
@@ -75,6 +87,17 @@
           </template>
         </template>
       </entity-index>
+      <q-dialog v-model="downloadDialog">
+        <q-card style="width: 800px; max-width: 95%;">
+          <q-card-section>
+            محتوای قابل دانلود
+          </q-card-section>
+          <q-separator />
+          <q-card-section>
+            <product-or-package-medias :basket-item="selectedBasketItem" />
+          </q-card-section>
+        </q-card>
+      </q-dialog>
     </div>
   </div>
 </template>
@@ -83,13 +106,14 @@
 import { shallowRef } from 'vue'
 import { Basket } from 'src/models/Basket.js'
 import { Product } from 'src/models/Product.js'
-import { Package } from 'src/models/Package.js'
+// import { Package } from 'src/models/Package.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import BtnControl from 'components/Control/btn.vue'
 import { EntityShow, EntityIndex } from 'quasar-crud'
 import { FormBuilderAssist } from 'quasar-form-builder'
 import FormBuilderDateTime from 'src/components/FormBuilderCustumComponents/FormBuilderDateTime.vue'
+import ProductOrPackageMedias from 'src/components/Widgets/Store/Basket/UserStoreBasketShow/ProductOrPackageMedias.vue'
 
 const BtnControlComp = shallowRef(BtnControl)
 const FormBuilderDateTimeComp = shallowRef(FormBuilderDateTime)
@@ -98,13 +122,16 @@ export default {
   name: 'AdminStoreBasketShow',
   components: {
     EntityShow,
-    EntityIndex
+    EntityIndex,
+    ProductOrPackageMedias
   },
   mixins: [mixinWidget],
   data () {
     return {
       mounted: false,
       entityLoading: false,
+      downloadDialog: false,
+      selectedBasketItem: null,
       api: APIGateway.basket.APIAdresses.byId(this.$route.params.id),
       entityIdKey: 'id',
       entityParamKey: 'id',
@@ -133,14 +160,14 @@ export default {
             required: true,
             label: 'شناسه',
             align: 'left',
-            field: row => row.id
+            field: row => row.product_info?.id ? row.product_info.id : row.package_info.id
           },
           {
             name: 'title',
             required: true,
             label: 'نام محصول',
             align: 'left',
-            field: row => (row.product_info || row.package_info).title
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).title
           },
           {
             name: 'is_package_or_product',
@@ -150,11 +177,18 @@ export default {
             field: row => row.package ? 'بسته' : 'تکی'
           },
           {
-            name: 'content_category',
+            name: 'store_category',
+            required: true,
+            label: 'دسته فروشگاه',
+            align: 'left',
+            field: row => ((row.product_info?.id ? row.product_info : row.package_info).store_category_info?.parent?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).store_category_info?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).store_category_info?.title || '-')
+          },
+          {
+            name: 'store_category1',
             required: true,
-            label: 'دسته بندی اصلی',
+            label: 'دسته محتوا',
             align: 'left',
-            field: row => row.content_category
+            field: row => ((row.product_info?.id ? row.product_info : row.package_info).content_category_info?.parent?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).content_category_info?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).content_category_info?.title || '-')
           },
           {
             name: 'count',
@@ -168,7 +202,7 @@ export default {
             required: true,
             label: 'نوع جنس',
             align: 'left',
-            field: row => row.product_info ? (new Product(row.product_info)).is_physical_info.label : (new Package(row.product_info)).is_physical_info.label
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).is_physical_info.label
           },
           {
             name: 'physical_type_info',
@@ -182,7 +216,14 @@ export default {
             required: true,
             label: 'قیمت',
             align: 'left',
-            field: row => parseInt((row.product_info || row.package_info).unit_price.toString()).toLocaleString('fa')
+            field: row => parseInt((row.product_info?.id ? row.product_info : row.package_info).unit_price?.toString()).toLocaleString('fa')
+          },
+          {
+            name: 'download',
+            required: true,
+            label: 'دانلود',
+            align: 'left',
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).is_physical
           },
           {
             name: 'action',
@@ -233,8 +274,8 @@ export default {
       { type: 'separator', name: 'space1', label: 'مشخصات سفارش', className: 'custom-separator', col: 'col-12' },
       { type: 'input', name: 'id', responseKey: 'id', label: 'شناسه', placeholder: ' ', col: 'col-md-2 col-12' },
       { type: 'input', name: 'items_info.length', responseKey: 'items_info.length', label: 'تعداد اقلام', placeholder: ' ', col: 'col-md-2 col-12' },
-      { type: FormBuilderDateTimeComp, name: 'creation_time', responseKey: 'creation_time', outsideLabel: 'تاریخ ثبت', placeholder: ' ', disable: true, col: 'col-md-3 col-12' },
-      { type: 'input', name: 'overall_order_price', responseKey: 'overall_order_price', label: 'قیمت کل(تومان)', placeholder: ' ', col: 'col-md-3 col-12' },
+      { type: FormBuilderDateTimeComp, name: 'creation_time', responseKey: 'creation_time', outsideLabel: 'تاریخ ثبت', placeholder: ' ', disable: true, col: 'col-md-6 col-12' },
+      { type: 'input', name: 'overall_order_price', responseKey: 'overall_order_price', label: 'قیمت کل(ریال)', placeholder: ' ', col: 'col-md-2 col-12' },
       { type: BtnControlComp, name: 'btnSent', responseKey: 'btnSent', label: 'ارسال', placeholder: ' ', outline: true, ignoreValue: true, atClick: this.sendBasket, loading: this.entityLoading, col: 'col-md-2 col-12' }
     ]
   },
@@ -242,6 +283,10 @@ export default {
     this.mounted = true
   },
   methods: {
+    openDownloadDialog (basketItem) {
+      this.selectedBasketItem = basketItem
+      this.downloadDialog = true
+    },
     showUserPage () {
       const routeData = this.$router.resolve({ name: 'Admin.User.Show', params: { id: this.basket.owner } })
       window.open(routeData.href, '_blank')

+ 113 - 0
src/components/Widgets/Store/Basket/AdminStoreDeliveryCost/AdminStoreDeliveryCost.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="AdminPostCreate"
+       :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 PostMixin from 'src/components/Widgets/Post/PostMixin.js'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'AdminPostCreate',
+  components: { EntityCreate },
+  mixins: [mixinWidget, PostMixin],
+  data () {
+    return {
+      mounted: false,
+      entityLoading: false,
+      api: null,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: null,
+      inputs: [
+        {
+          type: 'input',
+          name: 'text',
+          responseKey: 'text',
+          label: 'هزینه ارسال با پست پیشتاز',
+          col: 'col-md-12 col-12'
+        },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ثبت', placeholder: ' ', atClick: this.onSubmit, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted () {
+    this.getPurchaseRegulations()
+  },
+  methods: {
+    getPurchaseRegulations () {
+      this.purchaseRegulationsLoading = true
+      APIGateway.appSetting.get('DeliveryCost-post-pishtaz')
+        .then((purchaseRegulations) => {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'text', 'value', purchaseRegulations.value)
+          this.mounted = true
+        })
+        .finally(() => {
+          this.purchaseRegulationsLoading = false
+        })
+    },
+    onSubmit () {
+      const formData = FormBuilderAssist.getFormData(this.inputs)
+      this.purchaseRegulationsLoading = true
+      APIGateway.appSetting.update({ key: 'DeliveryCost-post-pishtaz', value: formData.text })
+        .then((purchaseRegulations) => {
+          this.purchaseRegulations = purchaseRegulations
+        })
+        .finally(() => {
+          this.purchaseRegulationsLoading = false
+        })
+    }
+  }
+}
+</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>

+ 21 - 0
src/components/Widgets/Store/Basket/AdminStoreDeliveryCost/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>

+ 5 - 5
src/components/Widgets/Store/Basket/UserStoreBasketList/UserStoreBasketList.vue

@@ -34,11 +34,11 @@
 </template>
 
 <script>
+import Assist from 'assets/js/Assist.js'
 import { EntityIndex } from 'quasar-crud'
 import { Basket } from 'src/models/Basket.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
-import Assist from 'assets/js/Assist'
 
 export default {
   name: 'UserStoreBasketList',
@@ -96,12 +96,12 @@ export default {
             required: true,
             label: 'نوع سفارش',
             align: 'left',
-            field: () => '-'
+            field: (row) => row.order_type ? new Basket(row).order_type_info.label : '-'
           },
           {
             name: 'overall_order_price',
             required: true,
-            label: 'قیمت کل(تومان)',
+            label: 'قیمت کل(ریال)',
             align: 'left',
             field: row => parseInt(row.overall_order_price).toLocaleString('fa')
           },
@@ -113,11 +113,11 @@ export default {
             field: row => (new Basket(row)).status_info.label
           },
           {
-            name: 'creation_time',
+            name: 'order_time',
             required: true,
             label: 'تاریخ ثبت',
             align: 'left',
-            field: row => Assist.miladiToShamsi(row.creation_time)
+            field: row => Assist.miladiToShamsi(row.order_time)
           },
           {
             name: 'action',

+ 57 - 0
src/components/Widgets/Store/Basket/UserStoreBasketShow/ProductOrPackageMedias.vue

@@ -0,0 +1,57 @@
+<template>
+  <q-card v-for="(product, productIndex) in products.list"
+          :key="productIndex"
+          class="q-mb-md">
+    <q-card-section>{{ product.title }}</q-card-section>
+    <q-separator />
+    <q-card-section>
+      <content-medias v-if="product.medias_info.list.length > 0"
+                      :can-delete="false"
+                      :can-add-media="false"
+                      :show-download-link="true"
+                      :show-page-link="false"
+                      :readonly="true"
+                      :value="product.medias_info.list" />
+      <q-badge v-else>
+        محتوای قابل دانلودی برای این محصول وجود ندارد
+      </q-badge>
+    </q-card-section>
+  </q-card>
+</template>
+
+<script>
+import { BasketItem } from 'src/models/BasketItem.js'
+import { ProductList } from 'src/models/Product.js'
+import ContentMedias from 'src/components/FormBuilderCustumComponents/ContentMedias/ContentMedias.vue'
+
+export default {
+  name: 'ProductOrPackageMedias',
+  components: { ContentMedias },
+  props: {
+    basketItem: BasketItem,
+    default: new BasketItem()
+  },
+  data () {
+    return {
+      products: new ProductList(),
+      medias: null
+    }
+  },
+  mounted() {
+    this.loadProducts()
+  },
+  methods: {
+    loadProducts () {
+      if (this.basketItem.product_info.id) {
+        this.products = new ProductList([this.basketItem.product_info])
+      } else if (this.basketItem.package_info.id) {
+        this.products = new ProductList(this.basketItem.package_info.products_info)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 56 - 14
src/components/Widgets/Store/Basket/UserStoreBasketShow/UserStoreBasketShow.vue

@@ -42,7 +42,6 @@
         </div>
       </template>
     </entity-show>
-
     <div class="q-mt-md">
       <entity-index v-if="basket.id"
                     v-model:value="tableInputs"
@@ -61,6 +60,16 @@
             <q-img :src="inputData.col.value"
                    width="100px" />
           </template>
+          <template v-else-if="inputData.col.name === 'download'">
+            <q-btn v-if="!inputData.col.value"
+                   color="primary"
+                   @click="openDownloadDialog(inputData.props.row)">
+              <q-icon name="download"
+                      class="q-mr-sm" />
+              دانلود
+            </q-btn>
+            <span v-else />
+          </template>
           <template v-else-if="inputData.col.name === 'action'">
             <q-btn color="primary"
                    outline
@@ -75,19 +84,31 @@
           </template>
         </template>
       </entity-index>
+      <q-dialog v-model="downloadDialog">
+        <q-card style="width: 800px; max-width: 95%;">
+          <q-card-section>
+            محتوای قابل دانلود
+          </q-card-section>
+          <q-separator />
+          <q-card-section>
+            <product-or-package-medias :basket-item="selectedBasketItem" />
+          </q-card-section>
+        </q-card>
+      </q-dialog>
     </div>
   </div>
 </template>
 
 <script>
 import { shallowRef } from 'vue'
-import { Package } from 'src/models/Package'
+// import { Package } from 'src/models/Package'
 import { Basket } from 'src/models/Basket.js'
 import { Product } from 'src/models/Product.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { EntityShow, EntityIndex } from 'quasar-crud'
 import { FormBuilderAssist } from 'quasar-form-builder'
+import ProductOrPackageMedias from './ProductOrPackageMedias.vue'
 import FormBuilderDateTime from 'src/components/FormBuilderCustumComponents/FormBuilderDateTime.vue'
 
 const FormBuilderDateTimeComp = shallowRef(FormBuilderDateTime)
@@ -96,12 +117,15 @@ export default {
   name: 'AdminStoreBasketShow',
   components: {
     EntityShow,
-    EntityIndex
+    EntityIndex,
+    ProductOrPackageMedias
   },
   mixins: [mixinWidget],
   data () {
     return {
       mounted: false,
+      downloadDialog: false,
+      selectedBasketItem: null,
       entityLoading: false,
       api: APIGateway.basket.APIAdresses.byId(this.$route.params.id),
       entityIdKey: 'id',
@@ -131,14 +155,14 @@ export default {
             required: true,
             label: 'شناسه',
             align: 'left',
-            field: row => row.id
+            field: row => row.product_info?.id ? row.product_info.id : row.package_info.id
           },
           {
             name: 'title',
             required: true,
             label: 'نام محصول',
             align: 'left',
-            field: row => (row.product_info || row.package_info).title
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).title
           },
           {
             name: 'is_package_or_product',
@@ -148,11 +172,18 @@ export default {
             field: row => row.package ? 'بسته' : 'تکی'
           },
           {
-            name: 'content_category',
+            name: 'store_category',
+            required: true,
+            label: 'دسته فروشگاه',
+            align: 'left',
+            field: row => ((row.product_info?.id ? row.product_info : row.package_info).store_category_info?.parent?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).store_category_info?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).store_category_info?.title || '-')
+          },
+          {
+            name: 'store_category1',
             required: true,
-            label: 'دسته بندی اصلی',
+            label: 'دسته محتوا',
             align: 'left',
-            field: row => row.content_category
+            field: row => ((row.product_info?.id ? row.product_info : row.package_info).content_category_info?.parent?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).content_category_info?.parent?.title || '-') + '، ' + ((row.product_info || row.package_info).content_category_info?.title || '-')
           },
           {
             name: 'count',
@@ -166,7 +197,7 @@ export default {
             required: true,
             label: 'نوع جنس',
             align: 'left',
-            field: row => row.product_info ? (new Product(row.product_info)).is_physical_info.label : (new Package(row.product_info)).is_physical_info.label
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).is_physical_info.label
           },
           {
             name: 'physical_type_info',
@@ -180,12 +211,19 @@ export default {
             required: true,
             label: 'قیمت',
             align: 'left',
-            field: row => parseInt((row.product_info || row.package_info).unit_price?.toString()).toLocaleString('fa')
+            field: row => parseInt((row.product_info?.id ? row.product_info : row.package_info).unit_price?.toString()).toLocaleString('fa')
+          },
+          {
+            name: 'download',
+            required: true,
+            label: 'دانلود',
+            align: 'left',
+            field: row => (row.product_info?.id ? row.product_info : row.package_info).is_physical
           },
           {
             name: 'action',
             required: true,
-            label: 'عملیات',
+            label: 'مشاهده',
             align: 'left',
             field: row => ''
           }
@@ -219,15 +257,19 @@ export default {
     this.inputs = [
       { type: 'separator', name: 'space1', label: 'مشخصات سفارش', className: 'custom-separator', col: 'col-12' },
       { type: 'input', name: 'id', responseKey: 'id', label: 'شناسه', placeholder: ' ', disable: true, col: 'col-md-2 col-12' },
-      { type: 'input', name: 'items_info.length', responseKey: 'items_info.length', label: 'تعداد اقلام', placeholder: ' ', disable: true, col: 'col-md-2 col-12' },
-      { type: FormBuilderDateTimeComp, name: 'creation_time', responseKey: 'creation_time', outsideLabel: 'تاریخ ثبت', placeholder: ' ', disable: true, col: 'col-md-3 col-12' },
-      { type: 'input', name: 'overall_order_price', responseKey: 'overall_order_price', label: 'قیمت کل(تومان)', placeholder: ' ', disable: true, col: 'col-md-3 col-12' }
+      { type: 'input', name: 'items_info.length', responseKey: 'items_info.length', label: 'تعداد اقلام', placeholder: ' ', disable: true, col: 'col-md-1 col-12' },
+      { type: FormBuilderDateTimeComp, name: 'creation_time', responseKey: 'creation_time', outsideLabel: 'تاریخ ثبت', placeholder: ' ', disable: true, col: 'col-md-6 col-12' },
+      { type: 'input', name: 'overall_order_price', responseKey: 'overall_order_price', label: 'قیمت کل(ریال)', placeholder: ' ', disable: true, col: 'col-md-3 col-12' }
     ]
   },
   mounted() {
     this.mounted = true
   },
   methods: {
+    openDownloadDialog (basketItem) {
+      this.selectedBasketItem = basketItem
+      this.downloadDialog = true
+    },
     afterLoadInputData (data) {
       this.entityLoading = false
       this.basket = new Basket(data)

+ 2 - 2
src/components/Widgets/Store/Cart/components/packageItem.vue

@@ -19,11 +19,11 @@
     <div class="package-item__price">
       <div class="package-item__price-base">
         {{ parseInt(packageItem.unit_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
       <div class="package-item__price-final">
         {{ packageItem.getFinalPrice() }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="package-item__actions">

+ 2 - 2
src/components/Widgets/Store/Cart/components/productItem.vue

@@ -27,11 +27,11 @@
     <div class="product-item__price">
       <div class="product-item__price-base">
         {{ parseInt(product.unit_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
       <div class="product-item__price-final">
         {{ product.getFinalPrice() }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="product-item__actions">

+ 24 - 5
src/components/Widgets/Store/Cart/components/step1/cartItem.vue

@@ -50,11 +50,11 @@
         <div v-if="packageOrProductObject.unit_price != packageOrProductObject.sellable_price"
              class="cart-item__price-base">
           {{ packageOrProductObject.unit_price.toLocaleString('fa') }}
-          تومان
+          ریال
         </div>
         <div class="cart-item__price-final">
           {{ packageOrProductObject.sellable_price.toLocaleString('fa') }}
-          تومان
+          ریال
         </div>
       </div>
       <div v-if="basketItem.package"
@@ -170,10 +170,17 @@ export default {
       width: 32px;
       height: 32px;
     }
+    @media screen and (max-width: 600px) {
+      & {
+        top: 4px;
+        left: auto;
+        right: 4px;
+      }
+    }
   }
   .cart-item__thumbnail {
     width: $cart-item__thumbnail-width;
-    @media screen and (max-width: 500px) {
+    @media screen and (max-width: 600px) {
       & {
         width: 100%;
       }
@@ -214,6 +221,13 @@ export default {
           font-size: 16px;
         }
       }
+      @media screen and (max-width: 1200px) {
+        & {
+          flex-flow: column;
+          gap: 8px;
+          margin-bottom: 8px;
+        }
+      }
     }
     .cart-item__type-info {
       position: absolute;
@@ -236,7 +250,7 @@ export default {
       right: 20px;
       top: 65px;
       width: 120px;
-      @media screen and (max-width: 500px) {
+      @media screen and (max-width: 1200px) {
         & {
           position: relative;
           top: 0;
@@ -263,7 +277,7 @@ export default {
         color: $primary;
         font-size: 24px;
       }
-      @media screen and (max-width: 500px) {
+      @media screen and (max-width: 1200px) {
         & {
           position: relative;
           top: 0;
@@ -297,5 +311,10 @@ export default {
       }
     }
   }
+  @media screen and (max-width: 600px) {
+    & {
+      padding: 56px 22px 24px 24px;
+    }
+  }
 }
 </style>

+ 5 - 5
src/components/Widgets/Store/Cart/components/step1/sidebar.vue

@@ -6,7 +6,7 @@
       </div>
       <div class="step1__sidebar-total-price-value">
         {{ basket.price.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div v-if="basket.margin_amount"
@@ -16,7 +16,7 @@
       </div>
       <div class="step1__sidebar-total-discount-value">
         {{ basket.margin_amount.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div v-if="basket.margin_amount"
@@ -28,7 +28,7 @@
       </div>
       <div class="step1__sidebar-total-price-value">
         {{ basket.sellable_price.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div v-if="basket.discount"
@@ -38,7 +38,7 @@
       </div>
       <div class="step1__sidebar-discount-code-value">
         {{ basket.discount_amount.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="step1__sidebar-separator" />
@@ -48,7 +48,7 @@
       </div>
       <div class="step1__sidebar-final-price-value">
         {{ basket.order_price.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="step1__sidebar-continue-action">

+ 51 - 3
src/components/Widgets/Store/Cart/components/step2/cartAddress.vue

@@ -54,6 +54,7 @@
 import { Basket } from 'src/models/Basket.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { FormBuilder, FormBuilderAssist } from 'quasar-form-builder'
+import { Provinces, Cities, getCitiesOfProvince } from 'src/assets/js/IranianCities.js'
 
 export default {
   name: 'CartAddress',
@@ -68,13 +69,38 @@ export default {
   data () {
     return {
       addressRadio: 1,
+      inputDataLoaded: false,
       mounted: false,
       dialog: false,
       setAddressLoading: false,
       addressInputs: [
-        { type: 'input', name: 'province', responseKey: 'province', label: 'استان', placeholder: ' ', col: 'col-md-4 col-12' },
-        { type: 'input', name: 'city', responseKey: 'city', label: 'شهر', placeholder: ' ', col: 'col-md-4 col-12' },
-        { type: 'input', name: 'postal_code', responseKey: 'postal_code', label: 'کد پستی', placeholder: ' ', col: 'col-md-4 col-12' },
+        {
+          type: 'select',
+          name: 'province',
+          options: Provinces,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          responseKey: 'province',
+          label: 'استان',
+          placeholder: ' ',
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'select',
+          name: 'city',
+          options: Cities,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          responseKey: 'city',
+          label: 'شهر',
+          placeholder: ' ',
+          col: 'col-md-6 col-12'
+        },
+        { type: 'input', name: 'postal_code', responseKey: 'postal_code', label: 'کد پستی', placeholder: ' ', col: 'col-12' },
         { type: 'input', name: 'address', responseKey: 'address', label: 'آدرس پستی', placeholder: ' ', col: 'col-12' }
       ]
     }
@@ -82,6 +108,25 @@ export default {
   computed: {
     addressString () {
       return 'file_download'
+    },
+    selectedProvince () {
+      return FormBuilderAssist.getInputsByName(this.addressInputs, 'province').value
+    }
+  },
+  watch: {
+    selectedProvince (newValue) {
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.addressInputs, 'city', 'disable', true)
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedProvince)
+
+      FormBuilderAssist.setAttributeByName(this.addressInputs, 'city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.addressInputs, 'city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.addressInputs, 'city', 'value', null)
+      }
     }
   },
   mounted() {
@@ -109,6 +154,9 @@ export default {
     },
     setFormData () {
       FormBuilderAssist.setInputValues(this.basket, this.addressInputs)
+      this.$nextTick(() => {
+        this.inputDataLoaded = true
+      })
     }
   }
 }

+ 28 - 4
src/components/Widgets/Store/Cart/components/step2/deliveryInfo.vue

@@ -19,7 +19,7 @@
         </div>
         <div class="delivery-info__selected-type-meta">
           <q-icon name="shopping_bag" />
-          هزینه به آدرس و وزن مرصوله متغیر میباشد
+          هزینه به آدرس و وزن مرسوله متغیر میباشد
         </div>
       </div>
       <div class="delivery-info__price-info">
@@ -28,8 +28,13 @@
           هزینه ارسال
         </div>
         <div class="delivery-info__price-value">
-          {{ basket.delivery_cost.toLocaleString('fa') }}
-          تومان
+          <q-linear-progress v-if="deliveryCostLoading"
+                             indeterminate />
+          <template v-else>
+            <!--            {{ basket.delivery_cost.toLocaleString('fa') }}-->
+            {{ deliveryCost.toLocaleString('fa') }}
+            ریال
+          </template>
         </div>
       </div>
     </div>
@@ -38,6 +43,7 @@
 
 <script>
 import { Basket } from 'src/models/Basket.js'
+import { APIGateway } from 'src/api/APIGateway.js'
 
 export default {
   name: 'DeliveryInfo',
@@ -49,7 +55,25 @@ export default {
   },
   data () {
     return {
-      radioValue: 1
+      radioValue: 1,
+      deliveryCost: 0,
+      deliveryCostLoading: false
+    }
+  },
+  mounted() {
+    this.getDeliveryCost()
+  },
+  methods: {
+    getDeliveryCost () {
+      this.deliveryCostLoading = true
+      APIGateway.appSetting.get('DeliveryCost-post-pishtaz')
+        .then((deliveryCost) => {
+          this.deliveryCost = parseInt(deliveryCost.value || 0)
+          this.mounted = true
+        })
+        .finally(() => {
+          this.deliveryCostLoading = false
+        })
     }
   }
 }

+ 33 - 6
src/components/Widgets/Store/Cart/components/step2/sidebar.vue

@@ -6,7 +6,7 @@
       </div>
       <div class="step2__sidebar-total-price-value">
         {{ basket.price.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="step2__sidebar-overall-discount-amount">
@@ -15,7 +15,7 @@
       </div>
       <div class="step2__sidebar-overall-discount-amount-value">
         {{ basket.overall_discount_amount.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="step2__sidebar-delivery-cost">
@@ -23,8 +23,18 @@
         هزینه ارسال
       </div>
       <div class="step2__sidebar-delivery-cost-value">
-        {{ basket.delivery_cost.toLocaleString('fa') }}
-        تومان
+        <q-linear-progress v-if="deliveryCostLoading"
+                           indeterminate />
+        <template v-else>
+          <!--          {{ basket.delivery_cost.toLocaleString('fa') }}-->
+          <span v-if="basket.order_type!=='VIRTUAL'">
+            {{ deliveryCost.toLocaleString('fa') }}
+          </span>
+          <span v-else>
+            0
+          </span>
+          ریال
+        </template>
       </div>
     </div>
     <div class="step2__sidebar-separator" />
@@ -34,7 +44,7 @@
       </div>
       <div class="step2__sidebar-final-price-value">
         {{ basket.overall_order_price.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="step2__sidebar-continue-action">
@@ -49,6 +59,7 @@
 
 <script>
 import { Basket } from 'src/models/Basket.js'
+import { APIGateway } from 'src/api/APIGateway.js'
 
 export default {
   name: 'cartItem',
@@ -61,12 +72,28 @@ export default {
   emits: ['complete'],
   data () {
     return {
-      step: 1
+      step: 1,
+      deliveryCost: 0,
+      deliveryCostLoading: false
     }
   },
+  mounted() {
+    this.getDeliveryCost()
+  },
   methods: {
     onComplete () {
       this.$emit('complete')
+    },
+    getDeliveryCost () {
+      this.deliveryCostLoading = true
+      APIGateway.appSetting.get('DeliveryCost-post-pishtaz')
+        .then((deliveryCost) => {
+          this.deliveryCost = parseInt(deliveryCost.value || 0)
+          this.mounted = true
+        })
+        .finally(() => {
+          this.deliveryCostLoading = false
+        })
     }
   }
 }

Разница между файлами не показана из-за своего большого размера
+ 2 - 2
src/components/Widgets/Store/Cart/components/step2/step2.vue


+ 2 - 2
src/components/Widgets/Store/Shop/Shop.vue

@@ -150,7 +150,7 @@ export default {
         { type: 'hidden', name: 'unit_price__lt', value: null },
         { type: 'hidden', name: 'unit_price__gte', value: null },
         { type: 'hidden', name: 'inventory__gte', value: null },
-        { type: 'hidden', name: 'category__in', value: null },
+        { type: 'hidden', name: 'store_category__in', value: null },
         { type: 'hidden', name: 'physical_type__in', value: null },
         { type: 'hidden', name: 'medias__type__in', value: null }
       ],
@@ -193,7 +193,7 @@ export default {
       FormBuilderAssist.setAttributeByName(this.productInputs, 'unit_price__lt', 'value', this.sidebarFilters.unit_price__lt)
       FormBuilderAssist.setAttributeByName(this.productInputs, 'unit_price__gte', 'value', this.sidebarFilters.unit_price__gte)
       FormBuilderAssist.setAttributeByName(this.productInputs, 'inventory__gte', 'value', this.sidebarFilters.inventory__gte)
-      FormBuilderAssist.setAttributeByName(this.productInputs, 'category__in', 'value', this.sidebarFilters.category__in?.join())
+      FormBuilderAssist.setAttributeByName(this.productInputs, 'store_category__in', 'value', this.sidebarFilters.store_category__in?.join())
       FormBuilderAssist.setAttributeByName(this.productInputs, 'physical_type__in', 'value', this.sidebarFilters.physical_type__in?.join())
       FormBuilderAssist.setAttributeByName(this.productInputs, 'medias__type__in', 'value', this.sidebarFilters.medias__type__in?.join())
     },

+ 50 - 27
src/components/Widgets/Store/Shop/components/packageItem.vue

@@ -1,6 +1,15 @@
 <template>
   <div class="package-item">
     <div class="package-item__badges">
+      <div v-if="packageItem.is_purchased"
+           class="package-item__badge">
+        <div class="package-item__badge-title">
+          خریداری شده است
+        </div>
+        <div class="package-item__badge-icon">
+          <q-icon name="done_all" />
+        </div>
+      </div>
       <div class="package-item__badge">
         <div class="package-item__badge-title">
           بسته
@@ -20,11 +29,11 @@
       <div v-if="packageItem.unit_price != packageItem.sellable_price"
            class="package-item__price-base">
         {{ parseInt(packageItem.unit_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
       <div class="package-item__price-final">
         {{ packageItem.sellable_price.toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="package-item__actions">
@@ -34,18 +43,27 @@
              outline>
         مشاهده جزییات
       </q-btn>
-      <q-btn v-if="!basketItem"
-             class="package-item__actions-add-to-cart"
+      <template v-if="!packageItem.is_add_to_basket_disabled">
+        <q-btn v-if="!basketItem"
+               class="package-item__actions-add-to-cart"
+               :loading="addToCartLoading"
+               color="primary"
+               @click="addToCart">
+          افزودن به سبد
+        </q-btn>
+        <cart-count-action v-else
+                           :basket-item="basketItem"
+                           @increase="onIncrease"
+                           @decrease="onDecrease"
+                           @remove="onRemove" />
+      </template>
+      <q-btn v-if="packageItem.inventory === 0"
+             class="product-btn-add-to-cart"
              :loading="addToCartLoading"
-             color="primary"
-             @click="addToCart">
-        افزودن به سبد
+             disable
+             color="primary">
+        ناموجود
       </q-btn>
-      <cart-count-action v-else
-                         :basket-item="basketItem"
-                         @increase="onIncrease"
-                         @decrease="onDecrease"
-                         @remove="onRemove" />
     </div>
   </div>
 </template>
@@ -181,14 +199,16 @@ export default {
     padding: 0 4px;
     text-align: center;
     font-size: 18px;
-    margin-bottom: 52px;
+    //margin-bottom: 52px;
+    margin-bottom: 8px;
   }
   .package-item__price {
     display: flex;
     align-items: center;
     justify-content: space-between;
     padding: 0 20px;
-    margin-bottom: 43px;
+    //margin-bottom: 43px;
+    margin-bottom: 16px;
     .package-item__price-final {
       font-size: 20px;
       color: #475F4A;
@@ -200,7 +220,9 @@ export default {
     }
   }
   .package-item__actions {
-    display: none;
+    //display: none;
+    display: flex;
+    flex-flow: column;
     margin: 0 25px 25px 25px;
     .package-item__actions-view {
       background: white;
@@ -213,24 +235,25 @@ export default {
   }
   &:hover {
     .package-item__badges {
+      z-index: 1;
       .package-item__badge {
         .package-item__badge-title {
           display: block;
         }
       }
     }
-    :deep(.package-item__thumbnail) {
-      margin: 10px 105px 4px 105px;
-      .q-img {
-        width: 100%;
-      }
-    }
-    .package-item__title {
-      margin-bottom: 44px;
-    }
-    .package-item__price {
-      margin-bottom: 27px;
-    }
+    //:deep(.package-item__thumbnail) {
+    //  margin: 10px 105px 4px 105px;
+    //  .q-img {
+    //    width: 100%;
+    //  }
+    //}
+    //.package-item__title {
+    //  margin-bottom: 44px;
+    //}
+    //.package-item__price {
+    //  margin-bottom: 27px;
+    //}
     .package-item__actions {
       display: flex;
       flex-flow: column;

+ 48 - 27
src/components/Widgets/Store/Shop/components/productItem.vue

@@ -1,6 +1,15 @@
 <template>
   <div class="product-item">
     <div class="product-item__badges">
+      <div v-if="product.is_purchased"
+           class="product-item__badge">
+        <div class="product-item__badge-title">
+          خریداری شده است
+        </div>
+        <div class="product-item__badge-icon">
+          <q-icon name="done_all" />
+        </div>
+      </div>
       <div class="product-item__badge">
         <div class="product-item__badge-title">
           {{ product.is_physical_info.label }}
@@ -29,11 +38,11 @@
       <div v-if="product.unit_price !== product.sellable_price"
            class="product-item__price-base">
         {{ parseInt(product.unit_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
       <div class="product-item__price-final">
         {{ parseInt(product.sellable_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="product-item__actions">
@@ -43,18 +52,27 @@
              outline>
         مشاهده جزییات
       </q-btn>
-      <q-btn v-if="!basketItem"
-             class="product-item__actions-add-to-cart"
+      <template v-if="!product.is_add_to_basket_disabled">
+        <q-btn v-if="!basketItem && product.inventory > 0"
+               class="product-item__actions-add-to-cart"
+               :loading="addToCartLoading"
+               color="primary"
+               @click="addToCart">
+          افزودن به سبد
+        </q-btn>
+        <cart-count-action v-else
+                           :basket-item="basketItem"
+                           @increase="onIncrease"
+                           @decrease="onDecrease"
+                           @remove="onRemove" />
+      </template>
+      <q-btn v-if="product.inventory === 0"
+             class="product-btn-add-to-cart"
              :loading="addToCartLoading"
-             color="primary"
-             @click="addToCart">
-        افزودن به سبد
+             disable
+             color="primary">
+        ناموجود
       </q-btn>
-      <cart-count-action v-else
-                         :basket-item="basketItem"
-                         @increase="onIncrease"
-                         @decrease="onDecrease"
-                         @remove="onRemove" />
     </div>
   </div>
 </template>
@@ -193,14 +211,15 @@ export default {
     padding: 0 4px;
     text-align: center;
     font-size: 18px;
-    margin-bottom: 52px;
+    //margin-bottom: 52px;
+    margin-bottom: 8px;
   }
   .product-item__price {
     display: flex;
     align-items: center;
     justify-content: space-between;
     padding: 0 20px;
-    margin-bottom: 43px;
+    margin-bottom: 16px;
     .product-item__price-final {
       font-size: 18px;
       color: #475F4A;
@@ -212,7 +231,9 @@ export default {
     }
   }
   .product-item__actions {
-    display: none;
+    //display: none;
+    display: flex;
+    flex-flow: column;
     margin: 0 25px 25px 25px;
     .product-item__actions-view {
       background: white;
@@ -232,18 +253,18 @@ export default {
         }
       }
     }
-    :deep(.product-item__thumbnail) {
-      margin: 10px auto 4px auto;
-      .q-img {
-        width: 90px;
-      }
-    }
-    .product-item__title {
-      margin-bottom: 44px;
-    }
-    .product-item__price {
-      margin-bottom: 27px;
-    }
+    //:deep(.product-item__thumbnail) {
+    //  margin: 10px auto 4px auto;
+    //  .q-img {
+    //    width: 90px;
+    //  }
+    //}
+    //.product-item__title {
+    //  margin-bottom: 44px;
+    //}
+    //.product-item__price {
+    //  margin-bottom: 27px;
+    //}
     .product-item__actions {
       display: flex;
       flex-flow: column;

+ 3 - 3
src/components/Widgets/Store/Shop/components/sidebar.vue

@@ -35,12 +35,12 @@
         <div class="sidebar__price-label-min">
           از
           {{ parseInt(filters.price.min.toString()).toLocaleString('fa') }}
-          تومان
+          ریال
         </div>
         <div class="sidebar__price-label-max">
           تا
           {{ parseInt(filters.price.max.toString()).toLocaleString('fa') }}
-          تومان
+          ریال
         </div>
       </div>
     </div>
@@ -195,7 +195,7 @@ export default {
         unit_price__lt: this.filters.price.max,
         unit_price__gte: this.filters.price.min,
         inventory__gte: this.filters.exist ? 1 : null,
-        category__in: this.filters.categories,
+        store_category__in: this.filters.categories,
         physical_type__in: this.filters.types.filter(tp => this.physicalTypes.includes(tp)),
         medias__type__in: this.filters.types.filter(tp => this.virtualTypes.includes(tp))
       }

+ 9 - 8
src/components/Widgets/Store/StorePackage/AdminStorePackageCreate/AdminStorePackageCreate.vue

@@ -30,9 +30,9 @@ 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 'components/Control/btn.vue'
-import ProductSelector from 'components/FormBuilderCustumComponents/ProductSelector.vue'
-import ContentCategorySelector from 'components/FormBuilderCustumComponents/ContentCategorySelector.vue'
+import BtnControl from 'src/components/Control/btn.vue'
+import ProductSelector from 'src/components/FormBuilderCustumComponents/ProductSelector.vue'
+import ContentCategorySelector from 'src/components/FormBuilderCustumComponents/ContentCategorySelector.vue'
 
 const BtnControlComp = shallowRef(BtnControl)
 const ProductSelectorComp = shallowRef(ProductSelector)
@@ -42,22 +42,23 @@ export default {
   name: 'AdminContentCreate',
   components: { EntityCreate },
   mixins: [mixinWidget],
-  data: () => {
+  data () {
     return {
       mounted: false,
       entityLoading: false,
-      api: APIGateway.content.APIAdresses.base,
+      api: APIGateway.package.APIAdresses.base,
       entityIdKey: 'id',
       entityParamKey: 'id',
-      showRouteName: 'Admin.Content.Show',
+      showRouteName: 'Admin.Store.Package.Show',
       inputs: [
         { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-md-6 col-12' },
         { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', placeholder: ' ', col: 'col-md-6 col-12' },
         { type: ContentCategorySelectorComp, name: 'content_category', responseKey: 'content_category_info', categoryType: 'content', col: 'col-md-12 col-12' },
         { type: ContentCategorySelectorComp, name: 'store_category', responseKey: 'store_category_info', categoryType: 'store', col: 'col-md-12 col-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-md-12 col-12' },
-        { type: 'inputEditor', name: 'specification', responseKey: 'specification', label: 'specification', col: 'col-md-12 col-12' },
-        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_physical', responseKey: 'is_physical', label: 'فیزیکی', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_hidden', responseKey: 'is_hidden', label: 'پنهان باشد؟', placeholder: ' ', value: true, col: 'col-md-9 col-12' },
+        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد(ریال)', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'discount_percent', responseKey: 'discount_percent', label: 'درصد تخفیف', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'inventory', responseKey: 'inventory', label: 'موجودی', placeholder: ' ', col: 'col-md-2 col-12' },
         { type: 'input', name: 'maximum_order_count', responseKey: 'maximum_order_count', label: 'سقف سفارش مجاز', placeholder: ' ', col: 'col-md-2 col-12' },

+ 56 - 3
src/components/Widgets/Store/StorePackage/AdminStorePackageList/AdminStorePackageList.vue

@@ -39,6 +39,7 @@
           <q-tab-panel name="packages"
                        class="q-pa-none">
             <entity-index v-if="mounted"
+                          ref="entityIndex"
                           v-model:value="inputs"
                           title="لیست بسته ها"
                           :api="api"
@@ -75,11 +76,17 @@
 </template>
 
 <script>
+import { shallowRef } from 'vue'
 import { EntityIndex } from 'quasar-crud'
 import { Package } from 'src/models/Package.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
-import AdminStoreProductList from 'components/Widgets/Store/StoreProduct/AdminStoreProductList/AdminStoreProductList.vue'
+import BtnControl from 'src/components/Control/btn.vue'
+import ContentCategorySelector from 'src/components/FormBuilderCustumComponents/ContentCategorySelector.vue'
+import AdminStoreProductList from 'src/components/Widgets/Store/StoreProduct/AdminStoreProductList/AdminStoreProductList.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+const ContentCategorySelectorComp = shallowRef(ContentCategorySelector)
 
 export default {
   name: 'AdminStorePackageList',
@@ -96,7 +103,34 @@ export default {
         perPage: 'per_page',
         pageKey: 'page'
       },
-      inputs: [],
+      inputs: [
+        { type: ContentCategorySelectorComp, name: 'content_category', responseKey: 'content_category_info', categoryType: 'content', col: 'col-md-12 col-12' },
+        { type: ContentCategorySelectorComp, name: 'store_category', responseKey: 'store_category_info', categoryType: 'store', col: 'col-md-12 col-12' },
+        {
+          type: 'input',
+          name: 'search',
+          label: 'جست جو',
+          placeholder: ' ',
+          col: 'col-md-4 col-12'
+        },
+        {
+          type: 'select',
+          name: 'is_hidden',
+          label: 'وضعیت نمایش',
+          options: (new Package()).is_hiddenEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        {
+          type: 'select',
+          name: 'is_physical',
+          label: 'نوع بسته',
+          options: (new Package()).is_physicalEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: this.search, col: 'col-md-2 col-12' }
+      ],
       table: {
         columns: [
           {
@@ -132,7 +166,14 @@ export default {
             required: true,
             label: 'دسته فروشگاه',
             align: 'left',
-            field: row => row.store_category_info?.parent?.parent?.title + '، ' + row.store_category_info?.parent?.title + '، ' + row.store_category_info?.title
+            field: row => (row.store_category_info?.parent?.parent?.title || '-') + '، ' + (row.store_category_info?.parent?.title || '-') + '، ' + (row.store_category_info?.title || '-')
+          },
+          {
+            name: 'store_category1',
+            required: true,
+            label: 'دسته محتوا',
+            align: 'left',
+            field: row => (row.content_category_info?.parent?.parent?.title || '-') + '، ' + (row.content_category_info?.parent?.title || '-') + '، ' + (row.content_category_info?.title || '-')
           },
           {
             name: 'category_info',
@@ -141,6 +182,13 @@ export default {
             align: 'left',
             field: row => parseInt(row.unit_price.toString()).toLocaleString('fa')
           },
+          {
+            name: 'is_hidden',
+            required: true,
+            label: 'نمایش',
+            align: 'left',
+            field: row => (new Package(row)).is_hidden_info.label
+          },
           {
             name: 'action',
             required: true,
@@ -156,6 +204,11 @@ export default {
   },
   mounted() {
     this.mounted = true
+  },
+  methods: {
+    search () {
+      this.$refs.entityIndex.search()
+    }
   }
 }
 </script>

+ 3 - 2
src/components/Widgets/Store/StorePackage/AdminStorePackageShow/AdminStorePackageShow.vue

@@ -62,8 +62,9 @@ export default {
         { type: ContentCategorySelectorComp, name: 'content_category', responseKey: 'content_category_info', categoryType: 'content', col: 'col-md-12 col-12' },
         { type: ContentCategorySelectorComp, name: 'store_category', responseKey: 'store_category_info', categoryType: 'store', col: 'col-md-12 col-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-md-12 col-12' },
-        { type: 'inputEditor', name: 'specification', responseKey: 'specification', label: 'specification', col: 'col-md-12 col-12' },
-        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_physical', responseKey: 'is_physical', label: 'فیزیکی', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_hidden', responseKey: 'is_hidden', label: 'پنهان باشد؟', placeholder: ' ', col: 'col-md-9 col-12' },
+        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد(ریال)', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'discount_percent', responseKey: 'discount_percent', label: 'درصد تخفیف', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'inventory', responseKey: 'inventory', label: 'موجودی', placeholder: ' ', col: 'col-md-2 col-12' },
         { type: 'input', name: 'maximum_order_count', responseKey: 'maximum_order_count', label: 'سقف سفارش مجاز', placeholder: ' ', col: 'col-md-2 col-12' },

+ 114 - 44
src/components/Widgets/Store/StorePackage/StorePackageShow/StorePackageShow.vue

@@ -11,29 +11,22 @@
           <div class="package-show__title">
             {{ storePackage.title }}
           </div>
-          <div class="package-show__attrs">
-            <div class="package-show__attrs-title">
-              ویژگی ها:
-            </div>
-            <div class="package-show__attrs-items">
-              <div class="package-show__attrs-item">
-                <div class="package-show__attrs-item-label">
-                  وزن:
-                </div>
-                <div class="package-show__attrs-item-value">
-                  ۲۰۰ گرم
-                </div>
-              </div>
-              <div class="package-show__attrs-item">
-                <div class="package-show__attrs-item-label">
-                  وزن:
-                </div>
-                <div class="package-show__attrs-item-value">
-                  ۲۰۰ گرم
-                </div>
-              </div>
-            </div>
-          </div>
+          <!--          <div class="package-show__attrs">-->
+        <!--            <div class="package-show__attrs-title">-->
+        <!--              ویژگی ها:-->
+        <!--            </div>-->
+        <!--            <div class="package-show__attrs-items">-->
+        <!--              <div class="package-show__attrs-item">-->
+        <!--                <div class="package-show__attrs-item-label">-->
+        <!--                  وزن:-->
+        <!--                </div>-->
+        <!--                <div class="package-show__attrs-item-value">-->
+        <!--                  {{ storePackage.weight }}-->
+        <!--                  گرم-->
+        <!--                </div>-->
+        <!--              </div>-->
+        <!--            </div>-->
+        <!--          </div>-->
         </div>
       </div>
       <div class="package-show__summery-price">
@@ -45,22 +38,54 @@
           <q-icon name="shopping_cart" />
           قیمت محصول
         </div>
-        <div class="package-show__summery-price-base">
-          {{ storePackage.unit_price }}
-          تومان
+        <div v-if="storePackage.unit_price !== storePackage.sellable_price"
+             class="package-show__summery-price-base">
+          {{ parseInt(storePackage.unit_price).toLocaleString('fa') }}
+          ریال
         </div>
         <div class="package-show__summery-price-final">
-          {{ storePackage.getFinalPrice() }}
-          تومان
+          {{ parseInt(storePackage.sellable_price).toLocaleString('fa') }}
+          ریال
         </div>
-        <q-btn label="افزودن به سبد"
-               outline
-               color="primary"
-               :loading="addToCartLoading"
-               class="package-show__summery-price-action"
-               @click="addToCart" />
+        <div class="package-show__summery-price-action">
+          <template v-if="!storePackage.is_add_to_basket_disabled">
+            <q-btn v-if="!basketItem && storePackage.inventory > 0"
+                   class="package-btn-add-to-cart"
+                   :loading="addToCartLoading"
+                   color="primary"
+                   @click="addToCart">
+              افزودن به سبد
+            </q-btn>
+            <cart-count-action v-else
+                               :basket-item="basketItem"
+                               @increase="onIncrease"
+                               @decrease="onDecrease"
+                               @remove="onRemove" />
+          </template>
+          <q-btn v-if="storePackage.inventory === 0"
+                 class="package-btn-add-to-cart"
+                 :loading="addToCartLoading"
+                 disable
+                 color="primary">
+            ناموجود
+          </q-btn>
+        </div>
+        <!--        <q-btn label="افزودن به سبد"-->
+        <!--               outline-->
+        <!--               color="primary"-->
+        <!--               :loading="addToCartLoading"-->
+        <!--               class="package-show__summery-price-action"-->
+        <!--               @click="addToCart" />-->
       </div>
     </div>
+    <div v-if="storePackage.description"
+         class="package-show__descriptions">
+      <q-banner class="package-show__descriptions-title">
+        توضیحات:
+      </q-banner>
+      <div class="package-show__descriptions-data"
+           v-html="storePackage.description" />
+    </div>
     <div class="package-show__products">
       <q-expansion-item expand-separator
                         default-opened
@@ -73,14 +98,7 @@
       </q-expansion-item>
 
     </div>
-    <div v-if="storePackage.description"
-         class="package-show__descriptions">
-      <q-banner class="package-show__descriptions-title">
-        توضیحات:
-      </q-banner>
-      <div class="package-show__descriptions-data"
-           v-html="storePackage.description" />
-    </div>
+
   </div>
 </template>
 
@@ -92,10 +110,11 @@ import { APIGateway } from 'src/api/APIGateway.js'
 import ProductItem from './components/productItem.vue'
 import DiscountBadge from './components/discountBadge.vue'
 import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+import CartCountAction from 'src/components/cart/cartCountAction/cartCountAction.vue'
 
 export default {
   name: 'ContentShow',
-  components: { Breadcrumbs, ProductItem, DiscountBadge },
+  components: { Breadcrumbs, ProductItem, DiscountBadge, CartCountAction },
   mixins: [mixinWidget],
   data () {
     return {
@@ -106,6 +125,17 @@ export default {
   computed: {
     packageId () {
       return this.$route.params.id
+    },
+    basket () {
+      return this.$store.getters['Shop/basket']
+    },
+    basketItem () {
+      const target = this.basket.items_info.list.find(basketItem => basketItem.package === this.storePackage.id)
+      if (!target) {
+        return null
+      }
+
+      return target
     }
   },
   mounted() {
@@ -115,12 +145,36 @@ export default {
   methods: {
     addToCart () {
       this.addToCartLoading = true
-      APIGateway.basketItem.addProduct(this.packageId, 1)
+      APIGateway.basketItem.addPackage(this.packageId, 1)
         .finally(() => {
           this.addToCartLoading = false
           this.checkoutReview()
         })
     },
+    onIncrease () {
+      this.addToCartLoading = true
+      APIGateway.basketItem.incrementPackage(this.basketItem.package)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
+    onDecrease () {
+      this.addToCartLoading = true
+      APIGateway.basketItem.decrementPackage(this.basketItem.package)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
+    onRemove () {
+      this.addToCartLoading = true
+      APIGateway.basketItem.remove(this.basketItem.id)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
     checkoutReview () {
       this.basket.loading = true
       APIGateway.basket.checkoutReview()
@@ -257,6 +311,7 @@ export default {
       }
     }
     .package-show__summery-price {
+      position: relative;
       width: $price-section-with;
       border-radius: 8px;
       background: #F6F6F6;
@@ -287,6 +342,9 @@ export default {
       .package-show__summery-price-action {
         width: 100%;
         background: white !important;
+        .package-btn-add-to-cart {
+          width: 100%;
+        }
       }
       @media screen and (max-width: 900px) {
         & {
@@ -296,6 +354,17 @@ export default {
         }
       }
     }
+    @media screen and (max-width: 1024px) {
+      & {
+        flex-flow: column;
+      }
+      .package-show__summery-info {
+        width: 100%;
+      }
+      .package-show__summery-price {
+        width: 100%;
+      }
+    }
   }
   .package-show__products {
     margin-bottom: 47px;
@@ -337,6 +406,7 @@ export default {
     }
   }
   .package-show__descriptions {
+    margin-bottom: 47px;
     padding: 32px 32px 70px 32px;
     border: 1px solid #DFE1EC;
     border-radius: 8px;

+ 4 - 3
src/components/Widgets/Store/StorePackage/StorePackageShow/components/productItem.vue

@@ -29,11 +29,12 @@
           قیمت محصول:
         </div>
         <div class="product-item__price-info-value">
-          {{ product.getFinalPrice() }}
-          تومان
+          {{ parseInt(product.unit_price).toLocaleString('fa') }}
+          ریال
         </div>
       </div>
-      <q-btn color="primary"
+      <q-btn v-if="!product.is_hidden"
+             color="primary"
              :to="{ name: 'Public.Product', params: { id: product.id }}"
              class="product-item__price-action">
         مشاهده

+ 4 - 3
src/components/Widgets/Store/StoreProduct/AdminStoreProductCreate/AdminStoreProductCreate.vue

@@ -58,14 +58,15 @@ export default {
         { type: ContentCategorySelectorComp, name: 'store_category', responseKey: 'store_category_info', categoryType: 'store', col: 'col-md-12 col-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-12' },
         { type: ContentMediasComp, name: 'medias', responseKey: 'medias_info', col: 'col-12' },
-        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد(ریال)', placeholder: ' ', col: 'col-md-6 col-12' },
         { type: 'input', name: 'discount_percent', responseKey: 'discount_percent', label: 'درصد تخفیف', placeholder: ' ', col: 'col-md-6 col-12' },
-        { type: 'checkbox', name: 'is_physical', responseKey: 'is_physical', label: 'فیزیکی', placeholder: ' ', value: false, col: 'col-12' },
+        { type: 'checkbox', name: 'is_physical', responseKey: 'is_physical', label: 'فیزیکی', placeholder: ' ', value: false, col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_hidden', responseKey: 'is_hidden', label: 'پنهان باشد؟', placeholder: ' ', value: true, col: 'col-md-9 col-12' },
         { type: 'select', name: 'physical_type', responseKey: 'physical_type', label: 'نوع محصول فیزیکی', options: (new Product()).physical_typeEnums, placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'inventory', responseKey: 'inventory', label: 'موجودی', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'maximum_order_count', responseKey: 'maximum_order_count', label: 'سقف سفارش مجاز', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'weight', responseKey: 'weight', label: 'وزن', placeholder: ' ', col: 'col-md-3 col-12' },
-        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ویرایش محصول', placeholder: ' ', ignoreValue: true, atClick: () => {}, col: 'col-md-6' }
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ایجاد محصول', placeholder: ' ', ignoreValue: true, atClick: () => {}, col: 'col-md-6' }
       ]
     }
   },

+ 55 - 2
src/components/Widgets/Store/StoreProduct/AdminStoreProductList/AdminStoreProductList.vue

@@ -8,6 +8,7 @@
              :to="{name: 'Admin.Store.Product.Create'}" />
     </div>
     <entity-index v-if="mounted"
+                  ref="entityIndex"
                   v-model:value="inputs"
                   title="لیست محصولات"
                   :api="api"
@@ -40,10 +41,16 @@
 </template>
 
 <script>
+import { shallowRef } from 'vue'
 import { EntityIndex } from 'quasar-crud'
 import { Product } from 'src/models/Product.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import BtnControl from 'src/components/Control/btn.vue'
+import ContentCategorySelector from 'src/components/FormBuilderCustumComponents/ContentCategorySelector.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+const ContentCategorySelectorComp = shallowRef(ContentCategorySelector)
 
 export default {
   name: 'AdminContentList',
@@ -65,7 +72,34 @@ export default {
         perPage: 'per_page',
         pageKey: 'page'
       },
-      inputs: [],
+      inputs: [
+        { type: ContentCategorySelectorComp, name: 'content_category', responseKey: 'content_category_info', categoryType: 'content', col: 'col-md-12 col-12' },
+        { type: ContentCategorySelectorComp, name: 'store_category', responseKey: 'store_category_info', categoryType: 'store', col: 'col-md-12 col-12' },
+        {
+          type: 'input',
+          name: 'search',
+          label: 'جست جو',
+          placeholder: ' ',
+          col: 'col-md-4 col-12'
+        },
+        {
+          type: 'select',
+          name: 'is_hidden',
+          label: 'وضعیت نمایش',
+          options: (new Product()).is_hiddenEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        {
+          type: 'select',
+          name: 'is_physical',
+          label: 'نوع محصول',
+          options: (new Product()).is_physicalEnums,
+          placeholder: ' ',
+          col: 'col-md-3 col-12'
+        },
+        { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: this.search, col: 'col-md-2 col-12' }
+      ],
       table: {
         columns: [
           {
@@ -101,7 +135,14 @@ export default {
             required: true,
             label: 'دسته فروشگاه',
             align: 'left',
-            field: row => row.store_category_info?.parent?.parent?.title + '، ' + row.store_category_info?.parent?.title + '، ' + row.store_category_info?.title
+            field: row => (row.store_category_info?.parent?.parent?.title || '-') + '، ' + (row.store_category_info?.parent?.title || '-') + '، ' + (row.store_category_info?.title || '-')
+          },
+          {
+            name: 'store_category1',
+            required: true,
+            label: 'دسته محتوا',
+            align: 'left',
+            field: row => (row.content_category_info?.parent?.parent?.title || '-') + '، ' + (row.content_category_info?.parent?.title || '-') + '، ' + (row.content_category_info?.title || '-')
           },
           {
             name: 'unit_price',
@@ -110,6 +151,13 @@ export default {
             align: 'left',
             field: row => parseInt(row.unit_price.toString()).toLocaleString('fa')
           },
+          {
+            name: 'is_hidden',
+            required: true,
+            label: 'نمایش',
+            align: 'left',
+            field: row => (new Product(row)).is_hidden_info.label
+          },
           {
             name: 'action',
             required: true,
@@ -125,6 +173,11 @@ export default {
   },
   mounted() {
     this.mounted = true
+  },
+  methods: {
+    search () {
+      this.$refs.entityIndex.search()
+    }
   }
 }
 </script>

+ 3 - 3
src/components/Widgets/Store/StoreProduct/AdminStoreProductShow/AdminStoreProductShow.vue

@@ -62,11 +62,11 @@ export default {
         { type: ContentCategorySelectorComp, name: 'content_category', responseKey: 'content_category_info', categoryType: 'content', col: 'col-md-12 col-12' },
         { type: ContentCategorySelectorComp, name: 'store_category', responseKey: 'store_category_info', categoryType: 'store', col: 'col-md-12 col-12' },
         { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', col: 'col-md-12 col-12' },
-        { type: 'inputEditor', name: 'specification', responseKey: 'specification', label: 'specification', col: 'col-md-12 col-12' },
         { type: ContentMediasComp, name: 'medias', responseKey: 'medias_info', sourceType: 'PRODUCT', col: 'col-md-12 col-12' },
-        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'input', name: 'unit_price', responseKey: 'unit_price', label: 'قیمت واحد(ریال)', placeholder: ' ', col: 'col-md-6 col-12' },
         { type: 'input', name: 'discount_percent', responseKey: 'discount_percent', label: 'درصد تخفیف', placeholder: ' ', col: 'col-md-6 col-12' },
-        { type: 'checkbox', name: 'is_physical', responseKey: 'is_physical', label: 'فیزیکی', placeholder: ' ', col: 'col-12' },
+        { type: 'checkbox', name: 'is_physical', responseKey: 'is_physical', label: 'فیزیکی', placeholder: ' ', col: 'col-md-3 col-12' },
+        { type: 'checkbox', name: 'is_hidden', responseKey: 'is_hidden', label: 'پنهان باشد؟', placeholder: ' ', col: 'col-md-9 col-12' },
         { type: 'select', name: 'physical_type', responseKey: 'physical_type', label: 'نوع محصول فیزیکی', options: (new Product()).physical_typeEnums, placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'inventory', responseKey: 'inventory', label: 'موجودی', placeholder: ' ', col: 'col-md-3 col-12' },
         { type: 'input', name: 'maximum_order_count', responseKey: 'maximum_order_count', label: 'سقف سفارش مجاز', placeholder: ' ', col: 'col-md-3 col-12' },

+ 1 - 1
src/components/Widgets/Store/StoreProduct/ProductCarousel/ProductCarousel.vue

@@ -76,7 +76,7 @@ export default {
         900: {
           perPage: 2
         },
-        500: {
+        600: {
           perPage: 1
         }
       },

+ 49 - 27
src/components/Widgets/Store/StoreProduct/ProductCarousel/productItem.vue

@@ -1,6 +1,15 @@
 <template>
   <div class="product-item">
     <div class="product-item__badges">
+      <div v-if="product.is_purchased"
+           class="product-item__badge">
+        <div class="product-item__badge-title">
+          خریداری شده است
+        </div>
+        <div class="product-item__badge-icon">
+          <q-icon name="done_all" />
+        </div>
+      </div>
       <div class="product-item__badge">
         <div class="product-item__badge-title">
           {{ product.is_physical_info.label }}
@@ -29,11 +38,11 @@
       <div v-if="product.unit_price !== product.sellable_price"
            class="product-item__price-base">
         {{ parseInt(product.unit_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
       <div class="product-item__price-final">
         {{ parseInt(product.sellable_price).toLocaleString('fa') }}
-        تومان
+        ریال
       </div>
     </div>
     <div class="product-item__actions">
@@ -43,18 +52,27 @@
              outline>
         مشاهده جزییات
       </q-btn>
-      <q-btn v-if="!basketItem"
-             class="product-item__actions-add-to-cart"
+      <template v-if="!product.is_add_to_basket_disabled">
+        <q-btn v-if="!basketItem && product.inventory > 0"
+               class="product-item__actions-add-to-cart"
+               :loading="addToCartLoading"
+               color="primary"
+               @click="addToCart">
+          افزودن به سبد
+        </q-btn>
+        <cart-count-action v-else
+                           :basket-item="basketItem"
+                           @increase="onIncrease"
+                           @decrease="onDecrease"
+                           @remove="onRemove" />
+      </template>
+      <q-btn v-if="product.inventory === 0"
+             class="product-btn-add-to-cart"
              :loading="addToCartLoading"
-             color="primary"
-             @click="addToCart">
-        افزودن به سبد
+             disable
+             color="primary">
+        ناموجود
       </q-btn>
-      <cart-count-action v-else
-                         :basket-item="basketItem"
-                         @increase="onIncrease"
-                         @decrease="onDecrease"
-                         @remove="onRemove" />
     </div>
   </div>
 </template>
@@ -193,14 +211,16 @@ export default {
     padding: 0 4px;
     text-align: center;
     font-size: 18px;
-    margin-bottom: 52px;
+    //margin-bottom: 52px;
+    margin-bottom: 8px;
   }
   .product-item__price {
     display: flex;
     align-items: center;
     justify-content: space-between;
     padding: 0 20px;
-    margin-bottom: 43px;
+    //margin-bottom: 43px;
+    margin-bottom: 8px;
     .product-item__price-final {
       font-size: 18px;
       color: #475F4A;
@@ -212,7 +232,9 @@ export default {
     }
   }
   .product-item__actions {
-    display: none;
+    //display: none;
+    display: flex;
+    flex-flow: column;
     margin: 0 25px 25px 25px;
     .product-item__actions-view {
       background: white;
@@ -231,18 +253,18 @@ export default {
         }
       }
     }
-    :deep(.product-item__thumbnail) {
-      margin: 10px auto 4px auto;
-      img {
-        width: 90px;
-      }
-    }
-    .product-item__title {
-      margin-bottom: 44px;
-    }
-    .product-item__price {
-      margin-bottom: 27px;
-    }
+    //:deep(.product-item__thumbnail) {
+    //  margin: 10px auto 4px auto;
+    //  img {
+    //    width: 90px;
+    //  }
+    //}
+    //.product-item__title {
+    //  margin-bottom: 44px;
+    //}
+    //.product-item__price {
+    //  margin-bottom: 27px;
+    //}
     .product-item__actions {
       display: flex;
       flex-flow: column;

+ 88 - 16
src/components/Widgets/Store/StoreProduct/StoreProductShow/StoreProductShow.vue

@@ -11,6 +11,11 @@
           <div class="product-show__title">
             {{ product.title }}
           </div>
+          <q-badge v-if="product.is_purchased">
+            <q-icon name="done_all"
+                    class="q-mr-sm" />
+            این محصول را خریده اید
+          </q-badge>
           <div class="product-show__attrs">
             <div class="product-show__attrs-title">
               ویژگی ها:
@@ -60,18 +65,35 @@
         <div v-if="product.unit_price !== product.sellable_price"
              class="product-show__summery-price-base">
           {{ parseInt(product.unit_price).toLocaleString('fa') }}
-          تومان
+          ریال
         </div>
         <div class="product-show__summery-price-final">
           {{ parseInt(product.sellable_price).toLocaleString('fa') }}
-          تومان
+          ریال
+        </div>
+        <div class="product-show__summery-price-action">
+          <template v-if="!product.is_add_to_basket_disabled">
+            <q-btn v-if="!basketItem && product.inventory > 0"
+                   class="product-btn-add-to-cart"
+                   :loading="addToCartLoading"
+                   color="primary"
+                   @click="addToCart">
+              افزودن به سبد
+            </q-btn>
+            <cart-count-action v-else
+                               :basket-item="basketItem"
+                               @increase="onIncrease"
+                               @decrease="onDecrease"
+                               @remove="onRemove" />
+          </template>
+          <q-btn v-if="product.inventory === 0"
+                 class="product-btn-add-to-cart"
+                 :loading="addToCartLoading"
+                 disable
+                 color="primary">
+            ناموجود
+          </q-btn>
         </div>
-        <q-btn label="افزودن به سبد"
-               outline
-               color="primary"
-               :loading="addToCartLoading"
-               class="product-show__summery-price-action"
-               @click="addToCart" />
       </div>
     </div>
     <div v-if="product.description"
@@ -82,7 +104,7 @@
       <div class="product-show__descriptions-data"
            v-html="product.description" />
     </div>
-    <div v-if="product.medias_info"
+    <div v-if="product.is_purchased && product.medias_info.list.length > 0"
          class="product-show__medias">
       <medias-component :medias="product.medias_info" />
     </div>
@@ -97,10 +119,12 @@ import { APIGateway } from 'src/api/APIGateway.js'
 import MediasComponent from './components/Medias.vue'
 import DiscountBadge from './components/discountBadge.vue'
 import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+import CartCountAction from 'src/components/cart/cartCountAction/cartCountAction.vue'
+// import CartCountAction from './components/cartCountAction.vue'
 
 export default {
-  name: 'ContentShow',
-  components: { Breadcrumbs, MediasComponent, DiscountBadge },
+  name: 'StoreProductShow',
+  components: { Breadcrumbs, MediasComponent, DiscountBadge, CartCountAction },
   mixins: [mixinWidget],
   data () {
     return {
@@ -111,11 +135,22 @@ export default {
   computed: {
     productId () {
       return this.$route.params.id
+    },
+    basket () {
+      return this.$store.getters['Shop/basket']
+    },
+    basketItem () {
+      const target = this.basket.items_info.list.find(basketItem => basketItem.product === this.product.id)
+      if (!target) {
+        return null
+      }
+
+      return target
     }
   },
   mounted() {
     this.setBreadcrumbsLoading()
-    this.getPackage()
+    this.getProduct()
   },
   methods: {
     addToCart () {
@@ -126,17 +161,41 @@ export default {
           this.checkoutReview()
         })
     },
+    onIncrease () {
+      this.addToCartLoading = true
+      APIGateway.basketItem.incrementProduct(this.basketItem.product)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
+    onDecrease () {
+      this.addToCartLoading = true
+      APIGateway.basketItem.decrementProduct(this.basketItem.product)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
+    onRemove () {
+      this.addToCartLoading = true
+      APIGateway.basketItem.remove(this.basketItem.id)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
     checkoutReview () {
-      this.basket.loading = true
+      this.addToCartLoading = true
       APIGateway.basket.checkoutReview()
         .then((basket) => {
           this.$store.commit('Shop/updateBasket', new Basket(basket))
         })
         .finally(() => {
-          this.basket.loading = false
+          this.addToCartLoading = false
         })
     },
-    getPackage () {
+    getProduct () {
       this.product.loading = true
       APIGateway.product.get(this.productId)
         .then((product) => {
@@ -295,7 +354,9 @@ export default {
       }
       .product-show__summery-price-action {
         width: 100%;
-        background: white !important;
+        .product-btn-add-to-cart {
+          width: 100%;
+        }
       }
       @media screen and (max-width: 900px) {
         & {
@@ -305,6 +366,17 @@ export default {
         }
       }
     }
+    @media screen and (max-width: 1024px) {
+      & {
+        flex-flow: column;
+      }
+      .product-show__summery-info {
+        width: 100%;
+      }
+      .product-show__summery-price {
+        width: 100%;
+      }
+    }
   }
   .product-show__products {
     margin-bottom: 47px;

+ 1 - 1
src/components/Widgets/Store/StoreProduct/StoreProductShow/components/productItem.vue

@@ -30,7 +30,7 @@
         </div>
         <div class="product-item__price-info-value">
           {{ product.getFinalPrice() }}
-          تومان
+          ریال
         </div>
       </div>
       <q-btn color="primary"

+ 21 - 0
src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/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>

+ 160 - 0
src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/StoreRelatedProducts.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="StoreRelatedProducts"
+       :style="localOptions.style">
+    <q-card v-if="relatedProducts.list.length > 0"
+            class="StoreRelatedProducts__card">
+      <q-card-section>
+        محصولات مرتبط
+      </q-card-section>
+      <q-separator />
+      <div class="products">
+        <product-item v-for="(product, productIndex) in relatedProducts.list"
+                      :key="productIndex"
+                      :product="product"
+                      :add-to-cart-loading="addToCartLoading"
+                      class="product-item"
+                      @increase="onIncrease"
+                      @decrease="onDecrease"
+                      @remove="onRemove"
+                      @add-to-cart="addToCart" />
+      </div>
+    </q-card>
+  </div>
+</template>
+
+<script>
+import { Basket } from 'src/models/Basket.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import ProductItem from './components/productItem.vue'
+import { Product, ProductList } from 'src/models/Product.js'
+
+export default {
+  name: 'StoreRelatedProducts',
+  components: { ProductItem },
+  mixins: [mixinWidget],
+  data () {
+    return {
+      addToCartLoading: false,
+      relatedProducts: new ProductList()
+    }
+  },
+  computed: {
+    productId () {
+      return this.$route.params.id
+    }
+  },
+  mounted () {
+    this.getRelatedProducts()
+  },
+  methods: {
+    getRelatedProducts () {
+      this.relatedProducts.loading = true
+      APIGateway.product.relatedProducts({ id: this.productId })
+        .then((products) => {
+          this.relatedProducts = new ProductList(products.list)
+        })
+        .finally(() => {
+          this.relatedProducts.loading = false
+        })
+    },
+    addToCart (productId) {
+      this.addToCartLoading = true
+      APIGateway.basketItem.addProduct(productId, 1)
+        .finally(() => {
+          this.addToCartLoading = false
+          this.checkoutReview()
+        })
+    },
+    onIncrease (productId) {
+      this.addToCartLoading = true
+      APIGateway.basketItem.incrementProduct(productId)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
+    onDecrease (productId) {
+      this.addToCartLoading = true
+      APIGateway.basketItem.decrementProduct(productId)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
+    onRemove (basketItemId) {
+      this.addToCartLoading = true
+      APIGateway.basketItem.remove(basketItemId)
+        .finally(() => {
+          this.checkoutReview()
+          this.addToCartLoading = false
+        })
+    },
+    checkoutReview () {
+      this.addToCartLoading = true
+      APIGateway.basket.checkoutReview()
+        .then((basket) => {
+          this.$store.commit('Shop/updateBasket', new Basket(basket))
+        })
+        .finally(() => {
+          this.addToCartLoading = false
+        })
+    },
+    getProduct () {
+      this.product.loading = true
+      APIGateway.product.get(this.productId)
+        .then((product) => {
+          this.product = new Product(product)
+          this.updateBreadcrumbs()
+        })
+        .finally(() => {
+          this.product.loading = false
+        })
+    },
+    setBreadcrumbsLoading () {
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: false,
+        loading: true
+      })
+    },
+    updateBreadcrumbs () {
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'خانه',
+            to: { name: 'Public.Home' }
+          },
+          {
+            label: 'فروشگاه',
+            to: { name: 'Public.Shop' }
+          },
+          {
+            label: this.product.title,
+            to: { name: 'Public.Package', params: { id: this.product } }
+          }
+        ]
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.StoreRelatedProducts {
+  .StoreRelatedProducts__card {
+    box-shadow: none;
+    border: 1px solid #DFE1EC;
+    border-radius: 8px;
+    .products {
+      .product-item {
+        border-bottom: solid 1px #DFE1EC;
+        &:last-child {
+          border-bottom: none;
+        }
+      }
+    }
+  }
+}
+</style>

+ 248 - 0
src/components/Widgets/Store/StoreProduct/StoreRelatedProducts/components/productItem.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="product-item">
+    <div class="product-item__meta">
+      <div class="product-item__meta-thumbnail"
+           @click="goToProductPage">
+        <q-img :src="product.thumbnail" />
+      </div>
+      <div class="product-item__meta-info">
+        <div class="product-item__product-title">
+          <router-link :to="{ name: 'Public.Product', params: { id: product.id }}">
+            {{ product.title }}
+          </router-link>
+        </div>
+        <div class="product-item__product-atrs">
+          <q-badge v-if="product.is_purchased">
+            <q-icon name="done_all"
+                    class="q-mr-sm" />
+            این محصول را خریده اید
+          </q-badge>
+          <div class="product-item__product-atrs-title">
+            ویژگی ها
+          </div>
+          <div class="product-item__product-atrs-list">
+            <div class="product-item__product-atr">
+              <div class="product-item__product-atr-label">
+                {{ product.title }}
+              </div>
+              <div class="product-item__product-atr-value">
+                {{ product.title }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="product-item__price">
+      <div class="product-item__price-info">
+        <div class="product-item__price-info-label">
+          <q-icon name="shopping_cart" />
+          قیمت محصول
+        </div>
+        <div class="product-item__price-info-value">
+          <div v-if="product.unit_price !== product.sellable_price"
+               class="product-item__price-base">
+            {{ parseInt(product.unit_price).toLocaleString('fa') }}
+            ریال
+          </div>
+          <div class="product-item__price-final">
+            {{ parseInt(product.sellable_price).toLocaleString('fa') }}
+            ریال
+          </div>
+        </div>
+      </div>
+      <div class="product-item__price-action">
+        <template v-if="!product.is_add_to_basket_disabled">
+          <q-btn v-if="!basketItem && product.inventory > 0"
+                 class="product-btn-add-to-cart"
+                 :loading="addToCartLoading"
+                 color="primary"
+                 @click="addToCart">
+            افزودن به سبد
+          </q-btn>
+          <cart-count-action v-else
+                             :basket-item="basketItem"
+                             :loading="addToCartLoading"
+                             @increase="onIncrease"
+                             @decrease="onDecrease"
+                             @remove="onRemove" />
+        </template>
+        <q-btn v-if="product.inventory === 0"
+               class="product-btn-add-to-cart"
+               :loading="addToCartLoading"
+               disable
+               color="primary">
+          ناموجود
+        </q-btn>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Product } from 'src/models/Product.js'
+import CartCountAction from 'src/components/cart/cartCountAction/cartCountAction.vue'
+
+export default {
+  name: 'productItem',
+  components: { CartCountAction },
+  props: {
+    product: {
+      type: Product,
+      default: new Product()
+    },
+    addToCartLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['add-to-cart', 'increase', 'decrease', 'remove'],
+  computed: {
+    basket () {
+      return this.$store.getters['Shop/basket']
+    },
+    basketItem () {
+      const target = this.basket.items_info.list.find(basketItem => basketItem.product === this.product.id)
+      if (!target) {
+        return null
+      }
+
+      return target
+    }
+  },
+  methods: {
+    goToProductPage () {
+      this.$router.push({ name: 'Public.Product', params: { id: this.product.id } })
+    },
+    addToCart () {
+      this.$emit('add-to-cart', this.product.id)
+    },
+    onIncrease () {
+      this.$emit('increase', this.product.id)
+    },
+    onDecrease () {
+      this.$emit('decrease', this.product.id)
+    },
+    onRemove () {
+      this.$emit('remove', this.basketItem.id)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.product-item {
+  padding: 24px;
+  display: flex;
+  justify-content: space-between;
+  background: #F9F9FB;
+  .product-item__meta {
+    display: flex;
+    gap: 32px;
+    justify-content: flex-start;
+    .product-item__meta-thumbnail {
+      cursor: pointer;
+      width: 123px;
+    }
+    .product-item__meta-info {
+      .product-item__product-title {
+        color: #212121;
+        font-size: 18px;
+        margin-bottom: 24px;
+      }
+      .product-item__product-atrs {
+        .product-item__product-atrs-title {
+          font-size: 16px;
+          letter-spacing: 0;
+          color: #AAA095;
+          margin-bottom: 19px;
+        }
+        .product-item__product-atrs-list {
+          .product-item__product-atr {
+            font-size: 16px;
+            display: flex;
+            gap: 4px;
+            align-items: center;
+            margin-bottom: 31px;
+            justify-content: flex-start;
+            &:before {
+              content: ' ';
+              width: 8px;
+              height: 8px;
+              border-radius: 100%;
+              background: #475F4A;
+              margin-right: 6px;
+            }
+            .product-item__product-atr-label {
+              font-size: 16px;
+              color: #797979;
+            }
+            .product-item__product-atr-value {
+              font-size: 16px;
+              color: #212121;
+            }
+          }
+        }
+      }
+    }
+    @media screen and (max-width: 600px) {
+      & {
+        flex-flow: column;
+      }
+    }
+  }
+  .product-item__price {
+    display: flex;
+    flex-direction: column;
+    gap: 29px;
+    justify-content: center;
+    align-items: flex-end;
+    .product-item__price-info {
+      display: flex;
+      flex-flow: column;
+      justify-content: center;
+      align-items: flex-end;
+      .product-item__price-info-label {
+        font-size: 16px;
+        color: #616161;
+        margin-right: 53px;
+        margin-bottom: 20px;
+        .q-icon {
+          font-size: 20px;
+          color: #AAA095;
+          margin-right: 10px;
+        }
+      }
+      .product-item__price-info-value {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 0 20px;
+        //margin-bottom: 43px;
+        margin-bottom: 8px;
+        .product-item__price-final {
+          font-size: 24px;
+          color: #475F4A;
+        }
+        .product-item__price-base {
+          font-size: 16px;
+          color: #FF3D3D;
+          text-decoration: line-through;
+          margin-right: 18px;
+        }
+      }
+    }
+    .product-item__price-action {
+      width: 252px;
+      .product-btn-add-to-cart {
+        width: 100%;
+      }
+    }
+  }
+  @media screen and (max-width: 1024px) {
+    & {
+      flex-flow: column;
+    }
+  }
+}
+</style>

+ 56 - 3
src/components/Widgets/Team/AdminTeamShow/AdminTeamShow.vue

@@ -62,6 +62,7 @@
 <script>
 import { shallowRef } from 'vue'
 import { Team } from 'src/models/Team.js'
+import { User } from 'src/models/User.js'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { EntityEdit, EntityIndex } from 'quasar-crud'
@@ -69,8 +70,8 @@ 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 { Provinces, Cities, getCitiesOfProvince } from 'src/assets/js/IranianCities.js'
 import FormBuilderInputEditor from 'src/components/FormBuilderCustumComponents/FormBuilderInputEditor.vue'
-import { User } from 'src/models/User'
 
 const BtnControlComp = shallowRef(BtnControl)
 const EntityInputComp = shallowRef(EntityInput)
@@ -90,6 +91,7 @@ export default {
     const classroomId = this.$route.params.classroom_id
     return {
       mounted: false,
+      inputDataLoaded: false,
       team: new Team(),
       entityLoading: true,
       api: APIGateway.team.APIAdresses.byId(teamId),
@@ -290,8 +292,32 @@ export default {
       teamRegistrationListInputs: [
         { type: 'hidden', name: 'team', value: teamId },
         { type: 'select', name: 'owner__gender', value: null, options: (new User()).genderEnums, col: 'col-md-2 col-12', label: 'جنسیت', placeholder: ' ' },
-        { type: 'input', name: 'owner__living_province', value: null, col: 'col-md-2 col-12', label: 'استان', placeholder: ' ' },
-        { type: 'input', name: 'owner__living_city', value: null, col: 'col-md-2 col-12', label: 'شهر', placeholder: ' ' },
+        {
+          type: 'select',
+          name: 'owner__living_province',
+          options: Provinces,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          value: null,
+          col: 'col-md-2 col-12',
+          label: 'استان',
+          placeholder: ' '
+        },
+        {
+          type: 'select',
+          name: 'owner__living_city',
+          options: Cities,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          value: null,
+          col: 'col-md-2 col-12',
+          label: 'شهر',
+          placeholder: ' '
+        },
         { type: 'input', name: 'search', value: null, col: 'col-md-2 col-12', label: 'جست و جو', placeholder: ' ' },
         { type: BtnControlComp, name: 'btn', label: 'جستجو', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' }
       ],
@@ -372,6 +398,30 @@ export default {
       }
     }
   },
+  computed: {
+    selectedProvince () {
+      return FormBuilderAssist.getInputsByName(this.teamRegistrationListInputs, 'owner__living_province').value
+    }
+  },
+  watch: {
+    selectedProvince (newValue) {
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.teamRegistrationListInputs, 'owner__living_city', 'disable', true)
+        if (this.inputDataLoaded) {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+        }
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedProvince)
+
+      FormBuilderAssist.setAttributeByName(this.teamRegistrationListInputs, 'owner__living_city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.teamRegistrationListInputs, 'owner__living_city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+      }
+    }
+  },
   mounted () {
     this.setActionBtn()
     this.mounted = true
@@ -407,6 +457,9 @@ export default {
       this.entityLoading = false
       FormBuilderAssist.setAttributeByName(this.inputs, 'leader', 'value', this.team.leader)
       FormBuilderAssist.setAttributeByName(this.inputs, 'leader', 'selected', this.team.leader_info)
+      this.$nextTick(() => {
+        this.inputDataLoaded = true
+      })
     },
     edit() {
       this.entityLoading = true

+ 30 - 5
src/components/Widgets/TextWidget/OptionPanel.vue

@@ -5,7 +5,8 @@
       <div class="option-panel-container">
         <div class="row q-mt-sm">
           <div class="col-md-12">
-            <editor v-model:value="localOptions.text" />
+            <form-builder-tiptap-editor v-model:value="localOptions.text"
+                                        :options="editorOptions" />
           </div>
           <editor-options v-model:options="localOptions" />
         </div>
@@ -15,15 +16,18 @@
 </template>
 <script>
 import { defineComponent } from 'vue'
+import { APIGateway } from 'src/api/APIGateway.js'
+import EditorOptions from 'components/EditorOptions.vue'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import PostMixin from 'src/components/Widgets/Post/PostMixin.js'
+import FormBuilderTiptapEditor from 'src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
-import Editor from 'components/Utils/Editor2.vue'
-import EditorOptions from 'components/EditorOptions.vue'
+// import Editor from 'components/Utils/Editor2.vue'
 
 export default defineComponent({
   name: 'OptionPanel',
-  components: { Editor, OptionPanelTabs, EditorOptions },
-  mixins: [mixinOptionPanel],
+  components: { OptionPanelTabs, EditorOptions, FormBuilderTiptapEditor },
+  mixins: [mixinOptionPanel, PostMixin],
   props: {
     options: {
       type: Object,
@@ -33,6 +37,10 @@ export default defineComponent({
     }
   },
   data() {
+    const authorization = 'Bearer ' + this.$store.getters['Auth/accessToken']
+    const uploadVideo = this.uploadVideo
+    const uploadAudio = this.uploadAudio
+
     return {
       fontStyle: ['inherit', 'normal', 'italic'],
       foreColor: '#000000',
@@ -75,6 +83,23 @@ export default defineComponent({
           fontStyle: null,
           lineHeight: null
         }
+      },
+      editorOptions: {
+        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
       }
     }
   },

+ 1 - 1
src/components/Widgets/User/Profile/Profile.vue

@@ -58,7 +58,7 @@ export default {
     EntityEdit
   },
   mixins: [mixinWidget],
-  data: () => {
+  data () {
     return {
       mounted: false,
       entityLoading: true,

+ 9 - 5
src/components/cart/cartCountAction/cartCountAction.vue

@@ -4,24 +4,24 @@
       <q-btn outline
              color="primary"
              icon="add"
-             :loading="basketItem.loading"
+             :loading="basketItem?.loading || loading"
              @click="onIncrease" />
     </div>
     <div class="cart-count-action-count">
-      {{ basketItem.count }}
+      {{ basketItem?.count }}
     </div>
     <div class="cart-count-action-decrease">
-      <q-btn v-if="basketItem.count > 1"
+      <q-btn v-if="basketItem?.count > 1"
              outline
              color="red"
              icon="remove"
-             :loading="basketItem.loading"
+             :loading="basketItem?.loading || loading"
              @click="onDecrease" />
       <q-btn v-else
              flat
              color="red"
              icon="delete_outline"
-             :loading="basketItem.loading"
+             :loading="basketItem?.loading || loading"
              @click="onRemove" />
     </div>
   </div>
@@ -36,6 +36,10 @@ export default {
     basketItem: {
       type: BasketItem,
       default: new BasketItem()
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   emits: ['increase', 'decrease', 'remove'],

+ 1 - 0
src/css/Theme/Controls/carousel.scss

@@ -89,6 +89,7 @@
   .splide__pagination {
     margin-top: 24px;
     gap: 4px;
+    padding-inline-start: 0;
     li {
       .splide__pagination__page {
         transition: all 0.3s;

+ 1 - 1
src/css/Theme/Widgets/widgets.scss

@@ -1,5 +1,5 @@
 //@import 'src/css/Theme/Widgets/card';
-//@import 'src/css/Theme/Widgets/scrollBar.scss';
+@import './scrollBar.scss';
 //@import './chip';
 @import './banner.scss';
 

+ 19 - 0
src/models/Basket.js

@@ -20,6 +20,7 @@ class Basket extends Model {
       },
       { key: 'description' },
       { key: 'status' },
+      { key: 'order_type' },
       {
         key: 'price',
         default: 0
@@ -101,6 +102,24 @@ class Basket extends Model {
             value: 'SENT'
           }
         ]
+      },
+      order_type: {
+        infoKey: 'order_type_info',
+        enumListKey: 'order_typeEnums',
+        enums: [
+          {
+            label: 'فیزیکی',
+            value: 'PHYSICAL'
+          },
+          {
+            label: 'مجازی',
+            value: 'VIRTUAL'
+          },
+          {
+            label: 'فیزیکی و مجازی',
+            value: 'PHYSICAL_VIRTUAL'
+          }
+        ]
       }
     })
   }

+ 21 - 1
src/models/Content.js

@@ -12,10 +12,15 @@ class Content extends Model {
       { key: 'description' },
       { key: 'summary' },
       { key: 'thumbnail' },
+      { key: 'creation_time' },
       {
         key: 'is_free',
         defalut: false
       },
+      {
+        key: 'is_hidden',
+        defalut: true
+      },
       {
         key: 'medias',
         defalut: []
@@ -38,7 +43,22 @@ class Content extends Model {
         key: 'hashtags_info',
         relatedModel: ContentHashtagList
       }
-    ])
+    ], {
+      is_hidden: {
+        infoKey: 'is_hidden_info',
+        enumListKey: 'is_hiddenEnums',
+        enums: [
+          {
+            label: 'پنهان',
+            value: true
+          },
+          {
+            label: 'نمایان',
+            value: false
+          }
+        ]
+      }
+    })
 
     if (!this.thumbnail) {
       this.thumbnail = '/img/sample-post-thumbnail.jpg'

+ 5 - 0
src/models/Invoice.js

@@ -1,4 +1,5 @@
 import { User } from 'src/models/User.js'
+import { Basket } from 'src/models/Basket.js'
 import { Classroom } from 'src/models/Classroom.js'
 import { Model, Collection } from 'js-abstract-model'
 
@@ -96,6 +97,10 @@ class Invoice extends Model {
         ]
       }
     })
+
+    if (this.item_type === 'STORE_BASKET') {
+      this.item_info = new Basket(data.item_info)
+    }
   }
 }
 

+ 29 - 1
src/models/Package.js

@@ -9,14 +9,28 @@ class Package extends Model {
       { key: 'title' },
       { key: 'inventory' },
       { key: 'store_category' },
+      { key: 'store_category_info' },
       { key: 'content_category' },
-      { key: 'unit_price' },
+      { key: 'content_category_info' },
+      {
+        key: 'unit_price',
+        default: 0
+      },
       { key: 'sellable_price' },
       { key: 'discount_percent' },
       { key: 'description' },
       { key: 'is_physical' },
+      { key: 'is_hidden' },
       { key: 'maximum_order_count' },
       { key: 'weight' },
+      {
+        key: 'is_add_to_basket_disabled',
+        default: false
+      },
+      {
+        key: 'is_purchased',
+        default: false
+      },
       { key: 'specification' },
       { key: 'products' },
       {
@@ -37,6 +51,20 @@ class Package extends Model {
             value: false
           }
         ]
+      },
+      is_hidden: {
+        infoKey: 'is_hidden_info',
+        enumListKey: 'is_hiddenEnums',
+        enums: [
+          {
+            label: 'پنهان',
+            value: true
+          },
+          {
+            label: 'نمایان',
+            value: false
+          }
+        ]
       }
     })
 

+ 1 - 0
src/models/Post.js

@@ -12,6 +12,7 @@ class Post extends Model {
       { key: 'summary' },
       { key: 'thumbnail' },
       { key: 'category' },
+      { key: 'creation_time' },
       {
         key: 'category_info',
         relatedModel: PostCategory

+ 30 - 2
src/models/Product.js

@@ -9,8 +9,13 @@ class Product extends Model {
       { key: 'title' },
       { key: 'inventory' },
       { key: 'store_category' },
+      { key: 'store_category_info' },
       { key: 'content_category' },
-      { key: 'unit_price' },
+      { key: 'content_category_info' },
+      {
+        key: 'unit_price',
+        default: 0
+      },
       { key: 'sellable_price' },
       {
         key: 'discount_percent',
@@ -19,12 +24,21 @@ class Product extends Model {
       { key: 'description' },
       { key: 'maximum_order_count' },
       { key: 'is_physical' },
+      { key: 'is_hidden' },
       { key: 'weight' },
       { key: 'physical_type' },
       { key: 'specification' },
+      {
+        key: 'is_add_to_basket_disabled',
+        default: false
+      },
+      {
+        key: 'is_purchased',
+        default: false
+      },
       {
         key: 'medias',
-        defalut: []
+        default: []
       },
       {
         key: 'medias_info',
@@ -45,6 +59,20 @@ class Product extends Model {
           }
         ]
       },
+      is_hidden: {
+        infoKey: 'is_hidden_info',
+        enumListKey: 'is_hiddenEnums',
+        enums: [
+          {
+            label: 'پنهان',
+            value: true
+          },
+          {
+            label: 'نمایان',
+            value: false
+          }
+        ]
+      },
       physical_type: {
         infoKey: 'physical_type_info',
         enumListKey: 'physical_typeEnums',

+ 23 - 0
src/pages/Admin/StoreBasket/DeliveryCost.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.Store.Basket.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminStoreDeliveryCost' }], 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/StoreBasket/PurchaseRegulations.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.Store.Basket.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminStoreBasketPurchaseRegulations' }], 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>

+ 193 - 10
src/pages/Admin/User/show.vue

@@ -243,13 +243,15 @@
 <script>
 import Assist from 'assets/js/Assist.js'
 import Enums from 'src/assets/Enums/Enums.js'
+import { Invoice } from 'src/models/Invoice.js'
+import { Payment } from 'src/models/Payment.js'
 import { APIGateway } from 'src/api/APIGateway.js'
+import { Classroom } from 'src/models/Classroom.js'
 import { EntityEdit, EntityIndex } from 'quasar-crud'
-import { Classroom } from 'src/models/Classroom'
-import { Registration } from 'src/models/Registration'
-import { Invoice } from 'src/models/Invoice'
-import { Payment } from 'src/models/Payment'
-import { Transaction } from 'src/models/Transaction'
+import { Transaction } from 'src/models/Transaction.js'
+import { Registration } from 'src/models/Registration.js'
+import { Provinces, Cities, getCitiesOfProvince } from 'src/assets/js/IranianCities.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
 
 export default {
   name: 'Admin.User.Show',
@@ -259,6 +261,7 @@ export default {
     return {
       tab: 'userInfo',
       financialTab: 'invoiceList',
+      inputDataLoaded: false,
       api: APIGateway.user.APIAdresses.byId(userId),
       mounted: false,
       entityIdKey: 'id',
@@ -331,15 +334,63 @@ export default {
           col: 'col-md-3 col-12'
         },
 
-        { type: 'checkbox', name: 'is_abroad_birth_address', placeholder: ' ', label: 'محل تولد خارج از کشور است؟', responseKey: 'is_abroad_birth_address', className: 'require', col: 'col-12' },
+        { type: 'checkbox', name: 'is_abroad_birth_address', placeholder: ' ', label: 'محل تولد خارج از کشور است؟۳۳۳۳', responseKey: 'is_abroad_birth_address', className: 'require', col: 'col-12' },
         { type: 'input', name: 'birth_country', placeholder: ' ', label: 'کشور محل تولد', responseKey: 'birth_country', col: 'col-md-4 col-12' },
-        { type: 'input', name: 'birth_province', placeholder: ' ', label: 'استان محل تولد', responseKey: 'birth_province', col: 'col-md-4 col-12' },
-        { type: 'input', name: 'birth_city', placeholder: ' ', label: 'شهر محل تولد', responseKey: 'birth_city', col: 'col-md-4 col-12' },
+        {
+          type: 'select',
+          name: 'birth_province',
+          options: Provinces,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'استان محل تولد',
+          responseKey: 'birth_province',
+          col: 'col-md-4 col-12'
+        },
+        {
+          type: 'select',
+          name: 'birth_city',
+          options: Cities,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'شهر محل تولد',
+          responseKey: 'birth_city',
+          col: 'col-md-4 col-12'
+        },
 
         { type: 'checkbox', name: 'is_abroad_living_address', placeholder: ' ', label: 'محل زندگی فعلی خارج از کشور است؟', responseKey: 'is_abroad_living_address', className: 'require', col: 'col-12' },
         { type: 'input', name: 'living_country', placeholder: ' ', label: 'کشور محل زندگی فعلی', responseKey: 'living_country', col: 'col-md-4 col-12' },
-        { type: 'input', name: 'living_province', placeholder: ' ', label: 'استان محل زندگی فعلی', responseKey: 'living_province', col: 'col-md-4 col-12' },
-        { type: 'input', name: 'living_city', placeholder: ' ', label: 'شهر محل زندگی فعلی', responseKey: 'living_city', col: 'col-md-4 col-12' },
+        {
+          type: 'select',
+          name: 'living_province',
+          options: Provinces,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'استان محل زندگی فعلی',
+          responseKey: 'living_province',
+          col: 'col-md-4 col-12'
+        },
+        {
+          type: 'select',
+          name: 'living_city',
+          options: Cities,
+          optionLabel: 'name',
+          optionValue: 'name',
+          createNewValue: true,
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'شهر محل زندگی فعلی',
+          responseKey: 'living_city',
+          col: 'col-md-4 col-12'
+        },
         { type: 'input', name: 'living_postal_code', placeholder: ' ', label: 'کد پستی محل زندگی', responseKey: 'living_postal_code', col: 'col-md-3 col-12' },
         { type: 'input', name: 'living_address', placeholder: ' ', label: 'آدرس پستی محل زندگی', responseKey: 'living_address', col: 'col-md-6 col-12' },
 
@@ -855,6 +906,135 @@ export default {
       }
     }
   },
+  computed: {
+    isAbroadBirthAddress () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'is_abroad_birth_address').value
+    },
+    isAbroadLivingAddress () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'is_abroad_living_address').value
+    },
+    selectedBirthProvince () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'birth_province').value
+    },
+    selectedLivingProvince () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'living_province').value
+    }
+  },
+  watch: {
+    isAbroadBirthAddress (newValue) {
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'value', 'ایران')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'disable', true)
+        if (this.inputDataLoaded) {
+          // province
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'type', 'select')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'disable', false)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'value', null)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'options', Provinces)
+
+          // city
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'select')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', true)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'options', [])
+        }
+      } else {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'value', null)
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'disable', false)
+
+        if (this.inputDataLoaded) {
+          // province
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'type', 'input')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'disable', false)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'value', null)
+
+          // city
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'input')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+        }
+      }
+    },
+    isAbroadLivingAddress (newValue) {
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'value', 'ایران')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'disable', true)
+        if (this.inputDataLoaded) {
+          // province
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'type', 'select')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'disable', false)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'value', null)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'options', Provinces)
+
+          // city
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'select')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', true)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'options', [])
+        }
+      } else {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'value', null)
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'disable', false)
+        if (this.inputDataLoaded) {
+          // province
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'type', 'input')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'disable', false)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'value', null)
+
+          // city
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'input')
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+        }
+      }
+    },
+    selectedBirthProvince (newValue) {
+      if (this.isAbroadBirthAddress) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'input')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+        return
+      }
+
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', true)
+        if (this.inputDataLoaded) {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+        }
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedBirthProvince)
+
+      FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+      }
+    },
+    selectedLivingProvince (newValue) {
+      if (this.isAbroadBirthAddress) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'input')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+        return
+      }
+
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', true)
+        if (this.inputDataLoaded) {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+        }
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedLivingProvince)
+
+      FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+      }
+    }
+  },
   mounted () {
     this.mounted = true
   },
@@ -869,6 +1049,9 @@ export default {
       // console.log('roles', roles)
       // this.setInputValue('roles', roles)
       // setNewInputData(this.inputs)
+      this.$nextTick(() => {
+        this.inputDataLoaded = true
+      })
     },
     setInputValue (name, value) {
       const inputIndex = this.inputs.findIndex(input => input.name === name)

+ 200 - 5
src/pages/User/Profile/profile.vue

@@ -33,11 +33,12 @@ import { User } from 'src/models/User.js'
 import Enums from 'src/assets/Enums/Enums.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import Warning from 'src/components/other/Warning.vue'
+import { FormBuilderAssist } from 'quasar-form-builder'
 import VerifyEmail from 'src/components/other/VerifyEmail.vue'
 import NationalCode from 'src/components/other/NationalCode.vue'
 import VerifyMobileNumber from 'src/components/other/VerifyMobileNumber.vue'
+import { Provinces, Cities, getCitiesOfProvince } from 'src/assets/js/IranianCities.js'
 import FormBuilderBarCode from 'src/components/FormBuilderCustumComponents/FormBuilderBarCode.vue'
-import { FormBuilderAssist } from 'quasar-form-builder'
 
 const WarningComp = shallowRef(Warning)
 const VerifyEmailComp = shallowRef(VerifyEmail)
@@ -51,6 +52,7 @@ export default {
   data () {
     return {
       mounted: false,
+      inputDataLoaded: false,
       tab: 'userInfo',
       api: APIGateway.user.APIAdresses.current,
       entityIdKey: 'id',
@@ -122,18 +124,208 @@ export default {
         },
         { type: 'checkbox', name: 'is_abroad_birth_address', placeholder: ' ', label: 'محل تولد خارج از کشور است؟', responseKey: 'is_abroad_birth_address', className: 'require', col: 'col-12' },
         { type: 'input', name: 'birth_country', placeholder: ' ', label: 'کشور محل تولد', responseKey: 'birth_country', className: 'require', col: 'col-md-6 col-12' },
-        { type: 'input', name: 'birth_province', placeholder: ' ', label: 'استان محل تولد', responseKey: 'birth_province', className: 'require', col: 'col-md-6 col-12' },
-        { type: 'input', name: 'birth_city', placeholder: ' ', label: 'شهر محل تولد', responseKey: 'birth_city', className: 'require', col: 'col-md-6 col-12' },
+        {
+          type: 'select',
+          name: 'birth_province',
+          options: Provinces,
+          createNewValue: true,
+          optionLabel: 'name',
+          optionValue: 'name',
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'استان محل تولد',
+          responseKey: 'birth_province',
+          className: 'require',
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'select',
+          name: 'birth_city',
+          options: Cities,
+          createNewValue: true,
+          optionLabel: 'name',
+          optionValue: 'name',
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'شهر محل تولد',
+          responseKey: 'birth_city',
+          className: 'require',
+          col: 'col-md-6 col-12'
+        },
         { type: 'checkbox', name: 'is_abroad_living_address', placeholder: ' ', label: 'محل زندگی فعلی خارج از کشور است؟', responseKey: 'is_abroad_living_address', className: 'require', col: 'col-12' },
         { type: 'input', name: 'living_country', placeholder: ' ', label: 'کشور محل زندگی فعلی', responseKey: 'living_country', className: 'require', col: 'col-md-6 col-12' },
-        { type: 'input', name: 'living_province', placeholder: ' ', label: 'استان محل زندگی فعلی', responseKey: 'living_province', className: 'require', col: 'col-md-6 col-12' },
-        { type: 'input', name: 'living_city', placeholder: ' ', label: 'شهر محل زندگی فعلی', responseKey: 'living_city', className: 'require', col: 'col-md-6 col-12' },
+        {
+          type: 'select',
+          name: 'living_province',
+          options: Provinces,
+          createNewValue: true,
+          optionLabel: 'name',
+          optionValue: 'name',
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'استان محل زندگی فعلی',
+          responseKey: 'living_province',
+          className: 'require',
+          col: 'col-md-6 col-12'
+        },
+        {
+          type: 'select',
+          name: 'living_city',
+          options: Cities,
+          createNewValue: true,
+          optionLabel: 'name',
+          optionValue: 'name',
+          newValueMode: 'add-unique',
+          placeholder: ' ',
+          label: 'شهر محل زندگی فعلی',
+          responseKey: 'living_city',
+          className: 'require',
+          col: 'col-md-6 col-12'
+        },
         { type: 'input', name: 'living_postal_code', placeholder: ' ', label: 'کد پستی محل زندگی', responseKey: 'living_postal_code', className: 'require', col: 'col-md-6 col-12' },
         { type: 'input', name: 'living_address', placeholder: ' ', label: 'آدرس پستی محل زندگی', responseKey: 'living_address', className: 'require', col: 'col-md-6 col-12' },
         { type: 'hidden', name: 'id', responseKey: 'id', label: 'id', col: 'col-12' }
       ]
     }
   },
+  computed: {
+    isAbroadBirthAddress () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'is_abroad_birth_address').value
+    },
+    isAbroadLivingAddress () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'is_abroad_living_address').value
+    },
+    selectedBirthProvince () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'birth_province').value
+    },
+    selectedLivingProvince () {
+      return FormBuilderAssist.getInputsByName(this.inputs, 'living_province').value
+    }
+  },
+  watch: {
+    isAbroadBirthAddress: {
+      immediate: true,
+      handler (newValue) {
+        this.$nextTick(() => {
+          if (!newValue) {
+            FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'value', 'ایران')
+            FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'readonly', true)
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'type', 'select')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'options', Provinces)
+
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'select')
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', true)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'options', getCitiesOfProvince(this.selectedBirthProvince))
+            }
+          } else {
+            // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'value', null)
+            FormBuilderAssist.setAttributeByName(this.inputs, 'birth_country', 'readonly', false)
+
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'disable', false)
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_province', 'value', null)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+            }
+          }
+        })
+      }
+    },
+    isAbroadLivingAddress: {
+      immediate: true,
+      handler (newValue) {
+        this.$nextTick(() => {
+          if (!newValue) {
+            FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'value', 'ایران')
+            FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'readonly', true)
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'type', 'select')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'options', Provinces)
+
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'select')
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', true)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'options', getCitiesOfProvince(this.selectedLivingProvince))
+            }
+          } else {
+            // FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'value', null)
+            FormBuilderAssist.setAttributeByName(this.inputs, 'living_country', 'readonly', false)
+            if (this.inputDataLoaded) {
+              // province
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'disable', false)
+
+              // city
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'input')
+              FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_province', 'value', null)
+              // FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+            }
+          }
+        })
+      }
+    },
+    selectedBirthProvince (newValue) {
+      if (this.isAbroadBirthAddress) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'type', 'input')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+        return
+      }
+
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', true)
+        if (this.inputDataLoaded) {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+        }
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedBirthProvince)
+
+      FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'birth_city', 'value', null)
+      }
+    },
+    selectedLivingProvince (newValue) {
+      if (this.isAbroadBirthAddress) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'type', 'input')
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+        return
+      }
+
+      if (!newValue) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', true)
+        if (this.inputDataLoaded) {
+          FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+        }
+        return
+      }
+
+      const filteredCities = getCitiesOfProvince(this.selectedLivingProvince)
+
+      FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'options', filteredCities)
+      FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'disable', false)
+      if (this.inputDataLoaded) {
+        FormBuilderAssist.setAttributeByName(this.inputs, 'living_city', 'value', null)
+      }
+    }
+  },
   mounted() {
     this.mounted = true
   },
@@ -146,6 +338,9 @@ export default {
 
       this.updateGender(responseData, setNewInputData)
       this.updateMaritalStatus(responseData, setNewInputData)
+      this.$nextTick(() => {
+        this.inputDataLoaded = true
+      })
     },
     updateGender (responseData, setNewInputData) {
       // const gender = this.getInputValue('gender')

Некоторые файлы не были показаны из-за большого количества измененных файлов