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.
 
 
 

1116 line
34 KiB

  1. <script>
  2. import Layout from "@/layout/custom.vue";
  3. import addPrivateMeet from "../../../components/modals/addPrivateMeet.vue";
  4. import privateMeetAgenda from "@/components/modals/privateMeetAgenda.vue";
  5. import ShowAddress from "@/components/modals/ShowAddress.vue";
  6. import showSubject from "@/components/modals/showSubject.vue";
  7. import showVisitor from "@/components/modals/showVisitor.vue";
  8. import DatePicker from "vue3-persian-datetime-picker";
  9. import Swal from "sweetalert2";
  10. import axios from "axios";
  11. import moment from "jalali-moment";
  12. import { onMounted, ref, watch } from "vue";
  13. import { toast } from "vue3-toastify";
  14. import { useRouter } from "vue-router";
  15. import "vue3-toastify/dist/index.css";
  16. const token = localStorage.getItem("token");
  17. const url = process.env.VUE_APP_ROOT_URL;
  18. export default {
  19. name: "BORDER",
  20. components: {
  21. Layout,
  22. addPrivateMeet,
  23. ShowAddress,
  24. showSubject,
  25. privateMeetAgenda,
  26. showVisitor,
  27. DatePicker,
  28. },
  29. setup() {
  30. const filterLoading = ref(false);
  31. const filter_locations = ref();
  32. const filter_dates = ref([]);
  33. const filter_subject = ref();
  34. const filter_status = ref();
  35. const router = useRouter();
  36. const managers = ref();
  37. const users = ref();
  38. const searchQuery = ref("");
  39. const locations = ref();
  40. const subjects = ref();
  41. const meetings = ref();
  42. const address = ref();
  43. const en_address = ref();
  44. const locationId = ref();
  45. const ShowAddress = ref();
  46. const showSubject = ref();
  47. const showDescription = ref();
  48. const meetId = ref();
  49. const show_visit_name = ref();
  50. const show_visit_mobile = ref();
  51. const show_visit_role = ref();
  52. const show_visit_company = ref();
  53. const csvLoading = ref(false);
  54. const pdfLoading = ref(false);
  55. const editMinute = (id) => {
  56. router.push({ name: "editMinute", params: { type: "private", id: id } });
  57. };
  58. const convertToJalali = (date) => {
  59. return moment(date, "YYYY-MM-DD HH:mm:ss")
  60. .locale("fa")
  61. .format("YYYY/MM/DD");
  62. };
  63. const getMeetings = (date) => {
  64. console.log(filter_locations.value);
  65. filterLoading.value = true;
  66. axios
  67. .get(
  68. `${url}/private_meetings?subject=${
  69. filter_subject?.value || ""
  70. }&date_meeting_az=${date?.[0] || ""}&date_meeting_ta=${
  71. date?.[1] || ""
  72. }&status=${filter_status?.value || ""}&location=${
  73. filter_locations?.value || ""
  74. }`,
  75. {
  76. headers: {
  77. "Content-Type": "application/json",
  78. Authorization: `Bearer ${token} `,
  79. },
  80. }
  81. )
  82. .then((resp) => {
  83. filterLoading.value = false;
  84. console.log(resp.data.data);
  85. meetings.value = resp.data.data;
  86. console.log(meetings.value);
  87. })
  88. .catch(() => {
  89. filterLoading.value = false;
  90. });
  91. };
  92. const getSubjects = () => {
  93. axios
  94. .get(`${url}/admin/subjects`, {
  95. headers: {
  96. "Content-Type": "application/json",
  97. Authorization: `Bearer ${token} `,
  98. },
  99. })
  100. .then((resp) => {
  101. subjects.value = resp.data;
  102. });
  103. };
  104. const editPage = (id) => {
  105. router.push({ name: "editPrivateMeet", params: { id } });
  106. };
  107. const getUsers = () => {
  108. axios
  109. .get(`${url}/admin/users?is_active=1`, {
  110. headers: {
  111. "Content-Type": "application/json",
  112. Authorization: `Bearer ${token} `,
  113. },
  114. })
  115. .then((resp) => {
  116. console.log(resp);
  117. users.value = resp.data;
  118. })
  119. .catch((error) => {
  120. console.error("Error fetching users:", error);
  121. });
  122. };
  123. const getLocations = () => {
  124. axios
  125. .get(`${url}/admin/locations`, {
  126. headers: {
  127. "Content-Type": "application/json",
  128. Authorization: `Bearer ${token} `,
  129. },
  130. })
  131. .then((resp) => {
  132. locations.value = resp.data;
  133. });
  134. };
  135. const getManagers = () => {
  136. axios
  137. .get(`${url}/meeting-manager`, {
  138. headers: {
  139. "Content-Type": "application/json",
  140. Authorization: `Bearer ${token} `,
  141. },
  142. })
  143. .then((resp) => {
  144. console.log(resp.data);
  145. managers.value = resp.data;
  146. });
  147. };
  148. const handleMeetUpdated = () => {
  149. window.location.reload();
  150. };
  151. const deleteLocation = (id, address) => {
  152. Swal.fire({
  153. text: `می خواهید موقعیت ${address} را حذف کنید ؟`,
  154. icon: "warning",
  155. showCancelButton: true,
  156. confirmButtonColor: "#3085d6",
  157. cancelButtonColor: "#d33",
  158. confirmButtonText: "بله!",
  159. cancelButtonText: "خیر",
  160. }).then((result) => {
  161. if (result.isConfirmed) {
  162. axios
  163. .delete(`${url}/admin/delete-location/${id}`, {
  164. headers: {
  165. "Content-Type": "application/json",
  166. Authorization: `Bearer ${token} `,
  167. },
  168. })
  169. .then(() => {
  170. toast.success("موقعیت با موفقیت حذف شد.", {
  171. position: "top-right",
  172. autoClose: 3000,
  173. });
  174. meetings.value = meetings.value.filter(
  175. (meeting) => meeting.id !== id
  176. );
  177. })
  178. .catch((err) => {
  179. console.log(err);
  180. toast.error("مشکلی در حذف کردن موقعیت پیش آمد", {
  181. position: "top-right",
  182. autoClose: 3000,
  183. });
  184. });
  185. }
  186. });
  187. };
  188. const addressModal = (address) => {
  189. ShowAddress.value = address;
  190. console.log(ShowAddress.value);
  191. };
  192. const subjectModal = (subject) => {
  193. showSubject.value = subject;
  194. console.log(showSubject.value);
  195. };
  196. const agendaModal = (desc, id) => {
  197. showDescription.value = desc;
  198. meetId.value = id;
  199. };
  200. const visitorModal = (name, mobile, role, company) => {
  201. show_visit_name.value = name;
  202. show_visit_role.value = role;
  203. show_visit_mobile.value = mobile;
  204. show_visit_company.value = company;
  205. };
  206. const acceptMeeting = (id) => {
  207. console.log(id);
  208. Swal.fire({
  209. text: "? آیا می خواهید این ملاقات را بپذیرید",
  210. icon: "warning",
  211. showCancelButton: true,
  212. confirmButtonColor: "#3085d6",
  213. cancelButtonColor: "#d33",
  214. confirmButtonText: "بله!",
  215. cancelButtonText: "خیر",
  216. }).then((result) => {
  217. if (result.isConfirmed) {
  218. const fd = new FormData();
  219. fd.append("meeting_id", id);
  220. axios
  221. .post(`${url}/accept-private-meeting`, fd, {
  222. headers: {
  223. "Content-Type": "application/json",
  224. Authorization: `Bearer ${token} `,
  225. },
  226. })
  227. .then(() => {
  228. getMeetings();
  229. toast.success("ملاقات با موفقیت پذیرفته شد", {
  230. position: "top-right",
  231. autoClose: 3000,
  232. });
  233. })
  234. .catch((err) => {
  235. console.log(err);
  236. toast.error("مشکلی در پذیرفتن ملاقات پیش آمد", {
  237. position: "top-right",
  238. autoClose: 3000,
  239. });
  240. });
  241. }
  242. });
  243. };
  244. const cancelMeeting = (id) => {
  245. console.log(id);
  246. Swal.fire({
  247. text: "? آیا می خواهید این ملاقات را لغو کنید",
  248. icon: "warning",
  249. showCancelButton: true,
  250. confirmButtonColor: "#3085d6",
  251. cancelButtonColor: "#d33",
  252. confirmButtonText: "بله!",
  253. cancelButtonText: "خیر",
  254. }).then((result) => {
  255. if (result.isConfirmed) {
  256. const fd = new FormData();
  257. fd.append("meeting_id", id);
  258. axios
  259. .post(`${url}/cancel-private-meeting`, fd, {
  260. headers: {
  261. "Content-Type": "application/json",
  262. Authorization: `Bearer ${token} `,
  263. },
  264. })
  265. .then(() => {
  266. toast.success("ملاقات با موفقیت لغو شد", {
  267. position: "top-right",
  268. autoClose: 3000,
  269. });
  270. getMeetings();
  271. })
  272. .catch((err) => {
  273. console.log(err);
  274. toast.error("مشکلی در لغو کردن ملاقات پیش آمد", {
  275. position: "top-right",
  276. autoClose: 3000,
  277. });
  278. });
  279. }
  280. });
  281. };
  282. const exportFile = (param) => {
  283. if (param === "pdf") {
  284. console.log("pdf");
  285. pdfLoading.value = true;
  286. axios
  287. .get(
  288. `${url}/statistic?subject=${
  289. filter_subject?.value || ""
  290. }&date_meeting_az=${
  291. filter_dates.value?.[0] || ""
  292. }&date_meeting_ta=${filter_dates.value?.[1] || ""}&status=${
  293. filter_status?.value || ""
  294. }&location=${filter_locations?.value || ""}&format=${param}`,
  295. {
  296. headers: {
  297. "Content-Type": "application/json",
  298. Authorization: `Bearer ${token}`,
  299. },
  300. responseType: "blob", // This is crucial for handling binary data
  301. }
  302. )
  303. .then((resp) => {
  304. const blob = new Blob([resp.data], {
  305. type: "application/pdf",
  306. });
  307. const url = window.URL.createObjectURL(blob);
  308. const a = document.createElement("a");
  309. a.href = url;
  310. a.download = "exported_auth_logs.pdf";
  311. a.click();
  312. window.URL.revokeObjectURL(url);
  313. pdfLoading.value = false;
  314. })
  315. .catch((error) => {
  316. pdfLoading.value = false;
  317. console.error("Error exporting data:", error);
  318. });
  319. } else {
  320. csvLoading.value = true;
  321. axios
  322. .get(
  323. `${url}/statistic?subject=${
  324. filter_subject?.value || ""
  325. }&date_meeting_az=${
  326. filter_dates.value?.[0] || ""
  327. }&date_meeting_ta=${filter_dates.value?.[1] || ""}&status=${
  328. filter_status?.value || ""
  329. }&location=${filter_locations?.value || ""}`,
  330. {
  331. headers: {
  332. "Content-Type": "application/json",
  333. Authorization: `Bearer ${token}`,
  334. },
  335. responseType: "blob",
  336. }
  337. )
  338. .then((resp) => {
  339. const blob = new Blob([resp.data], {
  340. type: "application/vnd.ms-excel",
  341. });
  342. const url = window.URL.createObjectURL(blob);
  343. const a = document.createElement("a");
  344. a.href = url;
  345. a.download = "exported_auth_logs.xlsx";
  346. a.click();
  347. window.URL.revokeObjectURL(url);
  348. csvLoading.value = false;
  349. })
  350. .catch((error) => {
  351. csvLoading.value = false;
  352. console.error("Error exporting data:", error);
  353. });
  354. }
  355. };
  356. const deletePrivateMeet = (id) => {
  357. console.log(id);
  358. Swal.fire({
  359. text: "آیا می خواهید این ملاقات را حذف کنید ؟",
  360. icon: "warning",
  361. showCancelButton: true,
  362. confirmButtonColor: "#3085d6",
  363. cancelButtonColor: "#d33",
  364. confirmButtonText: "بله!",
  365. cancelButtonText: "خیر",
  366. }).then((result) => {
  367. if (result.isConfirmed) {
  368. axios
  369. .post(`${url}/admin/delete-private-meeting/${id}`, {} , {
  370. headers: {
  371. "Content-Type": "application/json",
  372. Authorization: `Bearer ${token} `,
  373. },
  374. })
  375. .then(() => {
  376. toast.success("ملاقات با موفقیت حذف شد", {
  377. position: "top-right",
  378. autoClose: 3000,
  379. });
  380. meetings.value = meetings.value.filter((meet) => meet.id != id);
  381. })
  382. .catch((err) => {
  383. console.log(err);
  384. toast.error("مشکلی در حذف کردن ملاقات پیش آمد", {
  385. position: "top-right",
  386. autoClose: 3000,
  387. });
  388. });
  389. }
  390. });
  391. };
  392. watch(filter_subject, () => {
  393. getMeetings();
  394. });
  395. watch(filter_dates, () => {
  396. getMeetings();
  397. });
  398. watch(filter_status, () => {
  399. getMeetings();
  400. });
  401. watch(filter_locations, () => {
  402. getMeetings();
  403. });
  404. onMounted(() => {
  405. getMeetings();
  406. getSubjects();
  407. getLocations();
  408. getUsers();
  409. getManagers();
  410. });
  411. return {
  412. meetings,
  413. convertToJalali,
  414. handleMeetUpdated,
  415. address,
  416. en_address,
  417. locationId,
  418. deleteLocation,
  419. searchQuery,
  420. subjects,
  421. locations,
  422. users,
  423. managers,
  424. editPage,
  425. addressModal,
  426. ShowAddress,
  427. showSubject,
  428. subjectModal,
  429. agendaModal,
  430. showDescription,
  431. meetId,
  432. visitorModal,
  433. show_visit_name,
  434. show_visit_mobile,
  435. show_visit_role,
  436. show_visit_company,
  437. acceptMeeting,
  438. cancelMeeting,
  439. filter_subject,
  440. filter_dates,
  441. filter_status,
  442. filter_locations,
  443. filterLoading,
  444. editMinute,
  445. pdfLoading,
  446. csvLoading,
  447. exportFile,
  448. deletePrivateMeet,
  449. };
  450. },
  451. };
  452. </script>
  453. <template>
  454. <Layout>
  455. <BRow>
  456. <div class="col-md-12">
  457. <div class="card shadow-sm border-0 rounded">
  458. <div class="container p-4 shadow bg-light rounded mb-3">
  459. <!-- Top Controls Section -->
  460. <div
  461. class="d-flex justify-content-between align-items-center mb-2 gap-3"
  462. >
  463. <!-- Search Bar -->
  464. <div>
  465. <BFormSelect
  466. class="shadow-sm border rounded-lg"
  467. v-model="filter_subject"
  468. style="
  469. width: 160px;
  470. padding: 10px;
  471. border-color: #ddd;
  472. border-radius: 20px;
  473. transition: border 0.3s ease;
  474. "
  475. >
  476. <option disabled value="">انتخاب موضوع</option>
  477. <option value="">همه</option>
  478. <option
  479. v-for="subject in subjects"
  480. :key="subject.id"
  481. :value="subject.id"
  482. >
  483. {{ subject?.subject }}
  484. </option>
  485. </BFormSelect>
  486. </div>
  487. <!-- Date Picker -->
  488. <div class="position-relative">
  489. <date-picker
  490. :popover="true"
  491. v-model="filter_dates"
  492. multiple
  493. clearable
  494. class="shadow-sm border rounded-lg p-2"
  495. style="
  496. padding-left: 30px;
  497. border: 1px solid #ddd;
  498. border-radius: 20px;
  499. transition: border 0.3s ease;
  500. "
  501. placeholder="انتخاب تاریخ"
  502. />
  503. </div>
  504. <!-- Location Selector -->
  505. <div>
  506. <BFormSelect
  507. class="shadow-sm border rounded-lg"
  508. style="
  509. width: 160px;
  510. padding: 10px;
  511. border-color: #ddd;
  512. border-radius: 20px;
  513. transition: border 0.3s ease;
  514. "
  515. v-model="filter_locations"
  516. >
  517. <option disabled value="">انتخاب موقعیت</option>
  518. <option value="">همه</option>
  519. <option
  520. v-for="location in locations"
  521. :key="location.id"
  522. :value="location.id"
  523. >
  524. {{ location?.address }}
  525. </option>
  526. </BFormSelect>
  527. </div>
  528. <!-- Status Selector -->
  529. <div>
  530. <BFormSelect
  531. class="shadow-sm border rounded-lg"
  532. v-model="filter_status"
  533. style="
  534. width: 160px;
  535. padding: 10px;
  536. background-color: #fff;
  537. border-color: #ddd;
  538. border-radius: 20px;
  539. transition: border 0.3s ease;
  540. "
  541. >
  542. <option disabled value="">انتخاب وضعیت</option>
  543. <option value="">همه</option>
  544. <option value="1">برگزار شده</option>
  545. <option value="2">موکول شده</option>
  546. <option value="3">لغو شده</option>
  547. <option value="4">منتظر برگزاری</option>
  548. </BFormSelect>
  549. </div>
  550. <!-- Add User Button -->
  551. <button
  552. class="btn btn-primary add-user-btn d-flex align-items-center"
  553. type="button"
  554. data-bs-toggle="modal"
  555. data-bs-target="#addPrivateMeet"
  556. style="
  557. padding: 10px 20px;
  558. border-radius: 20px;
  559. transition: background-color 0.3s ease;
  560. "
  561. >
  562. <i class="fas fa-plus-circle me-2"></i>
  563. افزودن ملاقات
  564. </button>
  565. <!-- Export Buttons -->
  566. <div class="d-flex gap-3">
  567. <button
  568. class="btn btn-success d-flex align-items-center"
  569. type="button"
  570. :disabled="csvLoading"
  571. @click="exportFile"
  572. style="
  573. padding: 10px 20px;
  574. border-radius: 20px;
  575. transition: background-color 0.3s ease;
  576. "
  577. >
  578. <i v-if="csvLoading" class="fas fa-spinner fa-spin me-2"></i>
  579. <i v-else class="fas fa-file-csv me-2"></i>
  580. {{ csvLoading ? "خروجی..." : "خروجی CSV" }}
  581. </button>
  582. <button
  583. class="btn btn-danger d-flex align-items-center"
  584. type="button"
  585. :disabled="pdfLoading"
  586. @click="exportFile(`pdf`)"
  587. style="
  588. padding: 10px 20px;
  589. border-radius: 20px;
  590. transition: background-color 0.3s ease;
  591. "
  592. >
  593. <i v-if="pdfLoading" class="fas fa-spinner fa-spin me-2"></i>
  594. <i v-else class="fas fa-file-pdf me-2"></i>
  595. {{ pdfLoading ? "خروجی..." : "خروجی PDF" }}
  596. </button>
  597. </div>
  598. </div>
  599. </div>
  600. <div
  601. class="card-body table-border-style p-0"
  602. style="overflow: hidden"
  603. >
  604. <!-- Hide overflow here -->
  605. <div v-if="!filterLoading" class="table-responsive">
  606. <table class="table table-hover table-bordered m-0" dir="rtl">
  607. <thead class="table-light">
  608. <tr>
  609. <th>شناسه</th>
  610. <th>موضوع ملاقات</th>
  611. <th>زمان برگزاری</th>
  612. <th>تاریخ برگزاری</th>
  613. <th>آدرس ملاقات</th>
  614. <th>وضعیت</th>
  615. <th>مدیر ملاقات</th>
  616. <th>طرف ملاقات</th>
  617. <th>عملیات</th>
  618. </tr>
  619. </thead>
  620. <tbody>
  621. <tr v-for="(meet, index) in meetings" :key="index">
  622. <td>{{ meet?.id }}</td>
  623. <td>
  624. <div
  625. type="button"
  626. data-bs-target="#showSubject"
  627. data-bs-toggle="modal"
  628. @click="subjectModal(meet?.subject?.subject)"
  629. class="subject-box"
  630. >
  631. <i class="fas fa-comments subject-icon"></i>
  632. <span class="subject-text"
  633. >{{ meet?.subject?.subject.slice(0, 10)
  634. }}{{
  635. meet?.subject?.subject.length > 10 ? "..." : ""
  636. }}</span
  637. >
  638. </div>
  639. </td>
  640. <td>
  641. <div class="time-box">
  642. <i class="fas fa-clock"></i>
  643. {{ meet?.az_hour }} تا {{ meet?.ta_hour }}
  644. </div>
  645. </td>
  646. <td>
  647. <div class="date-box">
  648. <i class="fas fa-calendar-alt"></i>
  649. {{ convertToJalali(meet?.date_meeting) }}
  650. </div>
  651. </td>
  652. <td>
  653. <div
  654. type="button"
  655. data-bs-target="#showAddress"
  656. data-bs-toggle="modal"
  657. @click="addressModal(meet?.location?.address)"
  658. class="address-box"
  659. >
  660. <i class="fas fa-map-marker-alt"></i>
  661. {{ meet?.location?.address }}
  662. </div>
  663. </td>
  664. <td>
  665. <div
  666. class="status-box"
  667. :class="{
  668. 'status-canceled': meet?.status === 3,
  669. 'status-rescheduled': meet?.status === 2,
  670. 'status-completed': meet?.status === 1,
  671. 'status-pending': meet?.status === 4,
  672. }"
  673. >
  674. <i
  675. :class="{
  676. 'fas fa-times-circle': meet?.status === 3,
  677. 'fas fa-clock': meet?.status === 2,
  678. 'fas fa-check-circle': meet?.status === 1,
  679. 'fas fa-hourglass-half': meet?.status === 4,
  680. }"
  681. ></i>
  682. {{
  683. meet?.status === 1
  684. ? "برگزار شده"
  685. : meet?.status === 2
  686. ? "موکول شده"
  687. : meet?.status === 3
  688. ? "لغو شده"
  689. : meet?.status === 4
  690. ? "منتظر برگزاری"
  691. : ""
  692. }}
  693. </div>
  694. </td>
  695. <td style="width: 9%" class="manager-box">
  696. <div class="manager-content">
  697. <div class="name-container">
  698. {{ meet?.manager?.name }}
  699. <div class="phone-overlay">
  700. <span>{{ meet?.manager?.mobile }}</span>
  701. </div>
  702. </div>
  703. </div>
  704. </td>
  705. <td
  706. style="width: 9%; cursor: pointer"
  707. data-bs-target="#showVisitor"
  708. data-bs-toggle="modal"
  709. @click="
  710. visitorModal(
  711. meet?.visit_name,
  712. meet?.visit_mobile,
  713. meet?.visit_role,
  714. meet?.visit_company
  715. )
  716. "
  717. class="manager-box"
  718. >
  719. <div class="manager-content">
  720. <div class="name-container">
  721. {{ meet?.visit_name }}
  722. <div class="phone-overlay">
  723. <span>{{ meet?.visit_mobile }}</span>
  724. </div>
  725. </div>
  726. </div>
  727. </td>
  728. <td>
  729. <div class="dropdown">
  730. <span
  731. style="
  732. cursor: pointer;
  733. font-size: 1.5rem;
  734. color: #007bff;
  735. "
  736. @click="toggleDropdown"
  737. class="three-dots-icon"
  738. type="button"
  739. id="dropdownMenuButton"
  740. data-bs-toggle="dropdown"
  741. aria-expanded="false"
  742. >
  743. &#8230;
  744. </span>
  745. <div
  746. class="dropdown-menu dropdown-menu-end"
  747. aria-labelledby="dropdownMenuButton"
  748. >
  749. <a
  750. @click="editPage(meet?.id)"
  751. style="cursor: pointer"
  752. class="dropdown-item"
  753. ><i
  754. class="ph-duotone ph-pencil"
  755. style="color: #007bff"
  756. ></i>
  757. ویرایش</a
  758. >
  759. <span v-if="meet?.accepted == 0 && meet?.status != 3">
  760. <a
  761. @click="acceptMeeting(meet?.id)"
  762. style="cursor: pointer"
  763. class="dropdown-item"
  764. ><i
  765. class="ph-duotone ph-check-circle"
  766. style="color: #2ecc71"
  767. ></i>
  768. قبول جلسه</a
  769. >
  770. <a
  771. @click="cancelMeeting(meet?.id)"
  772. style="cursor: pointer"
  773. class="dropdown-item"
  774. ><i
  775. class="ph-duotone ph-x-circle"
  776. style="color: #e74c3c"
  777. ></i>
  778. لغو جلسه</a
  779. >
  780. </span>
  781. <a
  782. @click="agendaModal(meet?.description, meet?.id)"
  783. data-bs-target="#privateMeetAgenda"
  784. data-bs-toggle="modal"
  785. style="cursor: pointer"
  786. class="dropdown-item"
  787. ><i
  788. class="ph-duotone ph-file-text"
  789. style="color: #007bff"
  790. ></i>
  791. صورت جلسه</a
  792. >
  793. <a
  794. @click="editMinute(meet?.id)"
  795. style="cursor: pointer"
  796. class="dropdown-item"
  797. >
  798. <i
  799. class="ph-duotone ph-pencil-simple"
  800. style="color: #007bff"
  801. ></i>
  802. ویرایش صورت جلسه
  803. </a>
  804. <a
  805. v-if="!meet.deleted_at"
  806. @click="deletePrivateMeet(meet?.id)"
  807. style="cursor: pointer"
  808. class="dropdown-item"
  809. >
  810. <i
  811. class="ph-duotone ph-trash-simple"
  812. style="color: #e74c3c"
  813. ></i>
  814. حذف ملاقات
  815. </a>
  816. </div>
  817. </div>
  818. </td>
  819. </tr>
  820. </tbody>
  821. </table>
  822. </div>
  823. <div
  824. v-else
  825. class="filter-loader card table-card user-profile-list"
  826. ></div>
  827. </div>
  828. </div>
  829. </div>
  830. <addPrivateMeet
  831. :subjects="subjects"
  832. :locations="locations"
  833. :users="users"
  834. :managers="managers"
  835. @meet-updated="handleMeetUpdated()"
  836. />
  837. <editMeet
  838. :subjects="subjects"
  839. :locations="locations"
  840. :users="users"
  841. :managers="managers"
  842. @meet-updated="handleMeetUpdated()"
  843. />
  844. <ShowAddress :address="ShowAddress" />
  845. <showSubject :subject="showSubject" />
  846. <showVisitor
  847. :name="show_visit_name"
  848. :role="show_visit_role"
  849. :mobile="show_visit_mobile"
  850. :company="show_visit_company"
  851. />
  852. <privateMeetAgenda :description="showDescription" :id="meetId" />
  853. </BRow>
  854. </Layout>
  855. </template>
  856. <style scoped>
  857. .card {
  858. transition: transform 0.3s ease;
  859. }
  860. .card:hover {
  861. transform: translateY(-3px);
  862. }
  863. .table th,
  864. .table td {
  865. vertical-align: middle;
  866. text-align: center;
  867. }
  868. .time-box {
  869. padding: 8px 14px;
  870. background: linear-gradient(135deg, #e3f2fd, #bbdefb);
  871. color: #1a73e8;
  872. font-weight: 600;
  873. border-radius: 10px;
  874. box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.15);
  875. display: inline-flex;
  876. align-items: center;
  877. gap: 8px;
  878. transition: transform 0.2s ease, box-shadow 0.2s ease;
  879. }
  880. .time-box i {
  881. color: #1a73e8;
  882. font-size: 1rem;
  883. }
  884. .time-box:hover {
  885. transform: translateY(-2px);
  886. box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.2);
  887. }
  888. .date-box {
  889. padding: 8px 14px;
  890. background: linear-gradient(135deg, #f9fbe7, #f0f4c3);
  891. color: #558b2f;
  892. font-weight: 600;
  893. border-radius: 10px;
  894. box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
  895. display: inline-flex;
  896. align-items: center;
  897. gap: 8px;
  898. transition: transform 0.2s ease, box-shadow 0.2s ease;
  899. }
  900. .date-box i {
  901. color: #558b2f;
  902. font-size: 1rem;
  903. }
  904. .date-box:hover {
  905. transform: translateY(-2px);
  906. box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
  907. }
  908. .address-box {
  909. padding: 8px 14px;
  910. background: linear-gradient(135deg, #e0f7fa, #b2ebf2);
  911. color: #00796b;
  912. font-weight: 600;
  913. border-radius: 10px;
  914. box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
  915. display: inline-flex;
  916. align-items: center;
  917. gap: 8px;
  918. transition: transform 0.2s ease, box-shadow 0.2s ease;
  919. }
  920. .address-box i {
  921. color: #00796b;
  922. font-size: 1rem;
  923. }
  924. .address-box:hover {
  925. transform: translateY(-2px);
  926. box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.2);
  927. }
  928. .status-box {
  929. padding: 8px 14px;
  930. font-weight: 600;
  931. border-radius: 10px;
  932. box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
  933. display: inline-flex;
  934. align-items: center;
  935. gap: 8px;
  936. transition: transform 0.2s ease, box-shadow 0.2s ease;
  937. }
  938. .status-canceled {
  939. background: linear-gradient(135deg, #ffe0e0, #ffb2b2);
  940. color: #d32f2f;
  941. }
  942. .status-pending {
  943. background: linear-gradient(135deg, #fff3e0, #ffe0b2);
  944. color: #ef6c00;
  945. }
  946. .status-completed {
  947. background: linear-gradient(135deg, #e0ffe0, #b2ffb2);
  948. color: #388e3c;
  949. }
  950. .status-box i {
  951. font-size: 1rem;
  952. }
  953. .subject-box {
  954. padding: 8px 14px;
  955. background: linear-gradient(135deg, #fff3e0, #ffe0b2);
  956. color: #ef6c00;
  957. font-weight: 600;
  958. border-radius: 10px;
  959. box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
  960. display: inline-flex;
  961. align-items: center;
  962. gap: 8px;
  963. transition: transform 0.2s ease, box-shadow 0.2s ease;
  964. }
  965. .subject-box i {
  966. color: #ef6c00;
  967. font-size: 1rem;
  968. }
  969. .subject-box:hover {
  970. transform: translateY(-2px);
  971. box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);
  972. }
  973. .manager-box {
  974. position: relative;
  975. font-weight: 600;
  976. color: #1a73e8;
  977. overflow: hidden;
  978. }
  979. .name-container {
  980. position: relative;
  981. }
  982. .phone-overlay {
  983. position: absolute;
  984. top: 0;
  985. left: 50%;
  986. transform: translateX(-50%);
  987. width: 150%;
  988. height: 100%;
  989. background-color: #6495ed;
  990. color: #fff;
  991. display: flex;
  992. justify-content: center;
  993. align-items: center;
  994. opacity: 0;
  995. transform: translateY(-20px);
  996. transition: opacity 0.3s ease, transform 0.3s ease;
  997. border-radius: 8px;
  998. box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.3);
  999. padding: 15px;
  1000. text-align: center;
  1001. font-size: 1rem;
  1002. white-space: nowrap;
  1003. overflow: hidden;
  1004. text-overflow: ellipsis;
  1005. }
  1006. .manager-box:hover .phone-overlay {
  1007. opacity: 1;
  1008. transform: translate(-50%, 0);
  1009. }
  1010. .btn-custom {
  1011. background-color: #007bff;
  1012. color: #fff;
  1013. border: none;
  1014. border-radius: 30px;
  1015. padding: 10px 20px;
  1016. font-weight: bold;
  1017. transition: background-color 0.3s ease, transform 0.2s ease;
  1018. box-shadow: 0px 4px 15px rgba(0, 123, 255, 0.4);
  1019. }
  1020. .btn-custom:hover {
  1021. background-color: #0056b3;
  1022. transform: translateY(-2px);
  1023. }
  1024. .btn-custom:focus {
  1025. outline: none;
  1026. box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
  1027. }
  1028. .btn-cancel-meeting {
  1029. color: #d32f2f;
  1030. background: #ffebee;
  1031. border-color: #d32f2f;
  1032. font-weight: bold;
  1033. transition: background 0.3s, transform 0.2s;
  1034. }
  1035. .btn-cancel-meeting:hover {
  1036. background: #ffcdd2;
  1037. transform: scale(1.05);
  1038. }
  1039. .btn-accept-meeting {
  1040. color: #388e3c;
  1041. background: #e8f5e9;
  1042. border-color: #388e3c;
  1043. font-weight: bold;
  1044. transition: background 0.3s, transform 0.2s;
  1045. }
  1046. .btn-accept-meeting:hover {
  1047. background: #c8e6c9;
  1048. transform: scale(1.05);
  1049. }
  1050. .filter-loader {
  1051. border: 4px solid rgba(0, 123, 255, 0.3); /* Light blue */
  1052. border-top: 4px solid #007bff; /* Blue */
  1053. border-radius: 50%;
  1054. width: 40px;
  1055. height: 40px;
  1056. animation: spin 1s linear infinite;
  1057. margin: 20px auto; /* Center the loader */
  1058. }
  1059. </style>