You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

548 regels
15 KiB

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