Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

545 linhas
15 KiB

  1. <script>
  2. import Layout from "@/layout/custom.vue";
  3. import VueSelect from "vue3-select-component";
  4. import ApiServiece from "@/services/ApiService";
  5. import moment from "jalali-moment";
  6. import { onMounted, ref, watch, computed } from "vue";
  7. import { toast } from "vue3-toastify";
  8. import "vue3-toastify/dist/index.css";
  9. import Swal from "sweetalert2";
  10. import showDescription from "@/components/modals/commonModals/showDescription.vue";
  11. export default {
  12. name: "BORDER",
  13. components: {
  14. Layout,
  15. showDescription,
  16. VueSelect,
  17. },
  18. setup() {
  19. let searchTimeout = null;
  20. const selectedDiscountFormat = ref("");
  21. const selectedDiscountType = ref("");
  22. const categories = ref([]);
  23. const products = ref([]);
  24. const selectedcategory = ref();
  25. const selectedProduct = ref();
  26. const searchPage = ref();
  27. const currentPage = ref(1);
  28. const totalPages = ref(1);
  29. const paginate = ref(20);
  30. const page = ref(1);
  31. const filterLoading = ref(false);
  32. const searchQuery = ref("");
  33. const discounts = ref();
  34. const convertToJalali = (date) => {
  35. return moment(date, "YYYY-MM-DD HH:mm:ss")
  36. .locale("fa")
  37. .format("YYYY/MM/DD");
  38. };
  39. const handleSearchChange = () => {
  40. clearTimeout(searchTimeout);
  41. searchTimeout = setTimeout(() => {
  42. getDiscounts();
  43. page.value = 1;
  44. }, 500);
  45. };
  46. watch(searchQuery, () => {
  47. handleSearchChange();
  48. page.value = 1;
  49. });
  50. const getDiscounts = () => {
  51. filterLoading.value = true;
  52. ApiServiece.get(
  53. `admin/discounts?title=${searchQuery.value || ""}&product_id=${
  54. selectedProduct.value || ""
  55. }&category_id=${selectedcategory.value || ""}&type=${selectedDiscountFormat.value || ""}&paginate=${
  56. paginate.value || 10
  57. }&page=${page.value || 1}`
  58. )
  59. .then((resp) => {
  60. filterLoading.value = false;
  61. discounts.value = resp.data.data.data;
  62. console.log(resp.data.data);
  63. currentPage.value = resp.data.data.current_page;
  64. totalPages.value = resp.data.data.last_page;
  65. })
  66. .catch(() => {
  67. filterLoading.value = false;
  68. });
  69. };
  70. const visiblePages = computed(() => {
  71. const pages = [];
  72. if (totalPages.value <= 5) {
  73. for (let i = 1; i <= totalPages.value; i++) {
  74. pages.push(i);
  75. }
  76. } else {
  77. let start = currentPage.value - 2;
  78. let end = currentPage.value + 2;
  79. if (start < 1) start = 1;
  80. if (end > totalPages.value) end = totalPages.value;
  81. for (let i = start; i <= end; i++) {
  82. pages.push(i);
  83. }
  84. }
  85. return pages;
  86. });
  87. const deleteDiscount = (id, title) => {
  88. Swal.fire({
  89. text: `می خواهید تخفیف ${title} را حذف کنید؟`,
  90. icon: "warning",
  91. showCancelButton: true,
  92. confirmButtonColor: "#3085d6",
  93. cancelButtonColor: "#d33",
  94. confirmButtonText: "بله!",
  95. cancelButtonText: "خیر",
  96. }).then((result) => {
  97. if (result.isConfirmed) {
  98. ApiServiece.delete(`admin/discounts/${id}`)
  99. .then(() => {
  100. toast.success("!تخفیف با موفقیت حذف شد", {
  101. position: "top-right",
  102. autoClose: 3000,
  103. });
  104. discounts.value = discounts.value.filter(
  105. (discount) => discount.id !== id
  106. );
  107. })
  108. .catch((err) => {
  109. console.log(err);
  110. toast.error("!مشکلی در حذف کردن تخفیف پیش آمد", {
  111. position: "top-right",
  112. autoClose: 3000,
  113. });
  114. });
  115. }
  116. });
  117. };
  118. function handlePageInput() {
  119. if (searchPage.value < 1) {
  120. searchPage.value = 1;
  121. } else if (searchPage.value > totalPages.value) {
  122. searchPage.value = totalPages.value;
  123. }
  124. if (searchPage.value >= 1 && searchPage.value <= totalPages.value) {
  125. page.value = searchPage.value;
  126. }
  127. }
  128. const handleCategorySearch = async (searchTerm) => {
  129. if (searchTerm.length < 3) return;
  130. try {
  131. const response = await ApiServiece.get(
  132. `admin/categories?title=${searchTerm}`
  133. );
  134. categories.value = response.data.data;
  135. console.log(categories.value, "products");
  136. } catch (error) {
  137. console.error("Error fetching products:", error);
  138. categories.value = [];
  139. }
  140. };
  141. const formattedCategories = computed(() =>
  142. Array.isArray(categories.value)
  143. ? categories.value.map((category) => ({
  144. value: category.id,
  145. label: category.title,
  146. }))
  147. : []
  148. );
  149. const handleProductsSearch = async (searchTerm) => {
  150. if (searchTerm.length < 3) return;
  151. try {
  152. const response = await ApiServiece.get(
  153. `admin/products?title=${searchTerm}`
  154. );
  155. products.value = response.data.data;
  156. } catch (error) {
  157. console.error("Error fetching products:", error);
  158. products.value = [];
  159. }
  160. };
  161. const formattedProducts = computed(() =>
  162. Array.isArray(products.value)
  163. ? products.value.map((product) => ({
  164. value: product.id,
  165. label: product.title,
  166. }))
  167. : []
  168. );
  169. watch(page, () => {
  170. getDiscounts();
  171. });
  172. watch(selectedDiscountType, () => {
  173. selectedProduct.value = "",
  174. selectedcategory.value = ""
  175. if (selectedDiscountType.value === "") {
  176. filterLoading.value = true;
  177. ApiServiece.get(
  178. `admin/discounts?title=${searchQuery.value || ""}&paginate=${
  179. paginate.value || 10
  180. }&page=${page.value || 1}`
  181. )
  182. .then((resp) => {
  183. filterLoading.value = false;
  184. discounts.value = resp.data.data.data;
  185. console.log(resp.data.data);
  186. currentPage.value = resp.data.data.current_page;
  187. totalPages.value = resp.data.data.last_page;
  188. })
  189. .catch(() => {
  190. filterLoading.value = false;
  191. });
  192. }
  193. });
  194. watch([selectedProduct, selectedcategory], () => {
  195. getDiscounts();
  196. });
  197. watch(selectedDiscountFormat, () => {
  198. getDiscounts();
  199. });
  200. const nextPage = () => {
  201. if (currentPage.value < totalPages.value) {
  202. page.value++;
  203. getDiscounts();
  204. }
  205. };
  206. const prevPage = () => {
  207. if (currentPage.value > 1) {
  208. page.value--;
  209. getDiscounts();
  210. }
  211. };
  212. onMounted(() => {
  213. getDiscounts();
  214. });
  215. return {
  216. discounts,
  217. convertToJalali,
  218. deleteDiscount,
  219. searchQuery,
  220. filterLoading,
  221. currentPage,
  222. totalPages,
  223. nextPage,
  224. prevPage,
  225. page,
  226. handlePageInput,
  227. searchPage,
  228. visiblePages,
  229. selectedDiscountType,
  230. handleCategorySearch,
  231. formattedCategories,
  232. formattedProducts,
  233. handleProductsSearch,
  234. selectedcategory,
  235. selectedProduct,
  236. selectedDiscountFormat,
  237. };
  238. },
  239. };
  240. </script>
  241. <template>
  242. <Layout>
  243. <BRow>
  244. <div class="col-md-12">
  245. <div class="card shadow-sm border-0 rounded">
  246. <div
  247. class="card-header d-flex justify-content-between align-items-center"
  248. dir="rtl"
  249. >
  250. <div class="d-flex align-items-center">
  251. <input
  252. v-model="searchQuery"
  253. type="text"
  254. placeholder="جستجو..."
  255. class="form-control form-control-sm d-inline-block me-2"
  256. style="width: 250px; border-radius: 15px"
  257. />
  258. <select
  259. class="form-select form-select-sm"
  260. v-model="selectedDiscountFormat"
  261. style="width: 120px; border-radius: 15px"
  262. >
  263. <option value="" disabled selected>حالت</option>
  264. <option value="">همه</option>
  265. <option value="percentage">درصدی</option>
  266. <option value="const">مبلغی</option>
  267. </select>
  268. <select
  269. class="form-select form-select-sm"
  270. v-model="selectedDiscountType"
  271. style="width: 120px; border-radius: 15px ; margin-right: 7px;"
  272. >
  273. <option value="" disabled selected>اعمال بر</option>
  274. <option value="">همه</option>
  275. <option value="products">محصولات</option>
  276. <option value="categories">دسته ها</option>
  277. </select>
  278. <VueSelect
  279. v-if="selectedDiscountType === 'categories'"
  280. style="--vs-border-radius: 16px; margin-right: 7px"
  281. v-model="selectedcategory"
  282. :options="formattedCategories"
  283. placeholder="دسته ای را انتخاب کنید"
  284. @search="handleCategorySearch"
  285. />
  286. <VueSelect
  287. v-if="selectedDiscountType === 'products'"
  288. style="--vs-border-radius: 16px; margin-right: 7px"
  289. v-model="selectedProduct"
  290. :options="formattedProducts"
  291. placeholder="محصولی را انتخاب کنید"
  292. @search="handleProductsSearch"
  293. />
  294. </div>
  295. <router-link
  296. to="/addDiscount"
  297. class="btn btn-light text-primary btn-sm px-3"
  298. >
  299. افزودن تخفیف
  300. </router-link>
  301. </div>
  302. <div v-if="!filterLoading" class="card-body table-border-style p-0">
  303. <div class="table-responsive">
  304. <table class="table table-hover table-bordered m-0" dir="rtl">
  305. <thead class="table-light">
  306. <tr>
  307. <th>عنوان</th>
  308. <th>مدل</th>
  309. <th>مقدار</th>
  310. <th>حداقل سفارش</th>
  311. <th>تاریخ ایجاد</th>
  312. <th>تاریخ انقضا</th>
  313. <th>عملیات</th>
  314. </tr>
  315. </thead>
  316. <tbody>
  317. <tr v-for="discount in discounts" :key="discount.id">
  318. <td>{{ discount?.title }}</td>
  319. <td v-if="discount.type === 'const'">مبلغی</td>
  320. <td v-if="discount.type === 'percentage'">درصدی</td>
  321. <td>{{ discount.amount }}</td>
  322. <td>{{ discount.min_order }}</td>
  323. <td>{{ convertToJalali(discount?.starts_at) }}</td>
  324. <td>{{ convertToJalali(discount?.expires_at) }}</td>
  325. <td>
  326. <router-link
  327. :to="`/editDiscount/${discount?.id}`"
  328. class="btn btn-sm btn-outline-warning me-1"
  329. >
  330. ویرایش
  331. </router-link>
  332. <button
  333. @click="deleteDiscount(discount?.id, discount?.title)"
  334. class="btn btn-sm btn-outline-danger"
  335. >
  336. حذف
  337. </button>
  338. </td>
  339. </tr>
  340. </tbody>
  341. </table>
  342. </div>
  343. </div>
  344. <div
  345. v-else
  346. class="filter-loader card table-card user-profile-list"
  347. ></div>
  348. </div>
  349. </div>
  350. <showDescription :desc="brandDescription" />
  351. </BRow>
  352. <BRow>
  353. <BCol sm="12">
  354. <div class="d-flex justify-content-center">
  355. <nav aria-label="Page navigation">
  356. <ul class="pagination">
  357. <li class="page-item" :class="{ disabled: currentPage === 1 }">
  358. <span class="page-link" @click="prevPage">قبلی</span>
  359. </li>
  360. <li v-if="currentPage > 2" class="page-item" @click="page = 1">
  361. <a class="page-link" href="javascript:void(0)">1</a>
  362. </li>
  363. <li v-if="currentPage > 3" class="page-item" disabled>
  364. <span class="page-link">...</span>
  365. </li>
  366. <li
  367. v-for="n in visiblePages"
  368. :key="n"
  369. class="page-item"
  370. :class="{ active: currentPage === n }"
  371. >
  372. <a
  373. class="page-link"
  374. href="javascript:void(0)"
  375. @click="page = n"
  376. >
  377. {{ n }}
  378. </a>
  379. </li>
  380. <li
  381. v-if="currentPage < totalPages - 2"
  382. class="page-item"
  383. disabled
  384. >
  385. <span class="page-link">...</span>
  386. </li>
  387. <li
  388. v-if="currentPage < totalPages - 1"
  389. class="page-item"
  390. @click="page = totalPages"
  391. >
  392. <a class="page-link" href="javascript:void(0)">{{
  393. totalPages
  394. }}</a>
  395. </li>
  396. <li
  397. class="page-item"
  398. :class="{ disabled: currentPage === totalPages }"
  399. >
  400. <span class="page-link" @click="nextPage">بعدی</span>
  401. </li>
  402. </ul>
  403. </nav>
  404. </div>
  405. </BCol>
  406. <BCol sm="4">
  407. <div class="ms-0 search-number">
  408. <input
  409. v-model="searchPage"
  410. type="text"
  411. class="form-control"
  412. placeholder="برو به صفحه"
  413. :max="totalPages"
  414. min="1"
  415. @input="handlePageInput"
  416. />
  417. </div>
  418. </BCol>
  419. </BRow>
  420. </Layout>
  421. </template>
  422. <style scoped>
  423. .card {
  424. transition: transform 0.3s ease;
  425. }
  426. .card:hover {
  427. transform: translateY(-3px);
  428. }
  429. .table th,
  430. .table td {
  431. vertical-align: middle;
  432. text-align: center;
  433. }
  434. .filter-loader {
  435. border: 4px solid rgba(0, 123, 255, 0.3);
  436. border-top: 4px solid #007bff;
  437. border-radius: 50%;
  438. width: 40px;
  439. height: 40px;
  440. animation: spin 1s linear infinite;
  441. margin: 20px auto;
  442. }
  443. .subject-box {
  444. padding: 8px 14px;
  445. background: linear-gradient(135deg, #fff3e0, #ffe0b2);
  446. color: #ef6c00;
  447. font-weight: 600;
  448. border-radius: 10px;
  449. box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
  450. display: inline-flex;
  451. align-items: center;
  452. gap: 8px;
  453. transition: transform 0.2s ease, box-shadow 0.2s ease;
  454. }
  455. .subject-box i {
  456. color: #ef6c00;
  457. font-size: 1rem;
  458. }
  459. .subject-box:hover {
  460. transform: translateY(-2px);
  461. box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
  462. }
  463. .search-number {
  464. display: flex;
  465. align-items: center;
  466. }
  467. .search-number input {
  468. width: 150px;
  469. padding: 0.5rem;
  470. font-size: 1rem;
  471. border-radius: 0.375rem;
  472. margin-bottom: 7px;
  473. border: 1px solid #ced4da;
  474. transition: border-color 0.3s ease, box-shadow 0.3s ease;
  475. }
  476. .search-number input:focus {
  477. border-color: #007bff;
  478. box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25);
  479. }
  480. .search-number input::placeholder {
  481. color: #6c757d;
  482. }
  483. .search-number input:disabled {
  484. background-color: #f8f9fa;
  485. cursor: not-allowed;
  486. }
  487. .pagination {
  488. display: flex;
  489. flex-wrap: wrap;
  490. gap: 5px;
  491. }
  492. .page-item {
  493. flex: 0 0 auto;
  494. }
  495. .page-link {
  496. cursor: pointer;
  497. user-select: none;
  498. }
  499. </style>