list.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <template>
  2. <div class="admin--branch-list">
  3. <!-- 상단 버튼 -->
  4. <div class="admin--search-box">
  5. <div class="admin--search-form"></div>
  6. <div class="admin--search-actions">
  7. <button class="admin--btn-add" @click="goToCreate">
  8. + 새 분야 추가
  9. </button>
  10. </div>
  11. </div>
  12. <!-- 테이블 -->
  13. <div class="admin--table-wrapper">
  14. <table class="admin--table">
  15. <thead>
  16. <tr>
  17. <th></th>
  18. <th style="">번호</th>
  19. <th style="width: 70%">분야</th>
  20. <th>가중치</th>
  21. <th>등록일</th>
  22. <th>관리</th>
  23. </tr>
  24. </thead>
  25. <tbody>
  26. <tr v-if="isLoading">
  27. <td colspan="6" class="admin--table-loading">데이터를 불러오는 중...</td>
  28. </tr>
  29. <tr v-else-if="!branches || branches.length === 0">
  30. <td colspan="6" class="admin--table-empty">등록된 낚시분야가 없습니다.</td>
  31. </tr>
  32. <tr v-else v-for="(branch, index) in branches" :key="branch.id">
  33. <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
  34. <td class="admin--table-title">{{ branch.name }}</td>
  35. <!-- <td>{{ branch.main_phone }}</td>
  36. <td>{{ branch.address }}</td> -->
  37. <td>
  38. <button
  39. class="admin--toggle-btn"
  40. :class="{ 'is-active': branch.is_active == 1 }"
  41. @click="toggleActive(branch.id, branch.is_active)"
  42. >
  43. {{ branch.is_active == 1 ? "사용" : "비사용" }}
  44. </button>
  45. </td>
  46. <td>
  47. <div class="admin--table-actions">
  48. <button
  49. class="admin--btn-small admin--btn-small-primary"
  50. @click="goToEdit(branch.id)"
  51. >
  52. 수정
  53. </button>
  54. <button
  55. class="admin--btn-small admin--btn-small-danger"
  56. @click="deleteBranch(branch.id)"
  57. >
  58. 삭제
  59. </button>
  60. </div>
  61. </td>
  62. </tr>
  63. </tbody>
  64. </table>
  65. </div>
  66. <!-- 페이지네이션 -->
  67. <div v-if="totalPages > 1" class="admin--pagination">
  68. <button
  69. class="admin--pagination-btn"
  70. :disabled="currentPage === 1"
  71. @click="changePage(currentPage - 1)"
  72. >
  73. 이전
  74. </button>
  75. <button
  76. v-for="page in visiblePages"
  77. :key="page"
  78. class="admin--pagination-btn"
  79. :class="{ 'is-active': page === currentPage }"
  80. @click="changePage(page)"
  81. >
  82. {{ page }}
  83. </button>
  84. <button
  85. class="admin--pagination-btn"
  86. :disabled="currentPage === totalPages"
  87. @click="changePage(currentPage + 1)"
  88. >
  89. 다음
  90. </button>
  91. </div>
  92. <!-- 알림 모달 -->
  93. <AdminAlertModal
  94. v-if="alertModal.show"
  95. :title="alertModal.title"
  96. :message="alertModal.message"
  97. :type="alertModal.type"
  98. @confirm="handleAlertConfirm"
  99. @cancel="handleAlertCancel"
  100. @close="closeAlertModal"
  101. />
  102. </div>
  103. </template>
  104. <script setup>
  105. import { ref, computed, onMounted } from "vue";
  106. import { useRouter } from "vue-router";
  107. import AdminAlertModal from "~/components/admin/AdminAlertModal.vue";
  108. definePageMeta({
  109. layout: "admin",
  110. middleware: ["auth"],
  111. });
  112. const router = useRouter();
  113. const { get, del, post } = useApi();
  114. const isLoading = ref(false);
  115. const branches = ref([]);
  116. const currentPage = ref(1);
  117. const perPage = ref(10);
  118. const totalCount = ref(0);
  119. const totalPages = ref(0);
  120. // 알림 모달
  121. const alertModal = ref({
  122. show: false,
  123. title: "알림",
  124. message: "",
  125. type: "alert",
  126. onConfirm: null,
  127. });
  128. // 알림 모달 표시
  129. const showAlert = (message, title = "알림") => {
  130. alertModal.value = {
  131. show: true,
  132. title,
  133. message,
  134. type: "alert",
  135. onConfirm: null,
  136. };
  137. };
  138. // 확인 모달 표시
  139. const showConfirm = (message, onConfirm, title = "확인") => {
  140. alertModal.value = {
  141. show: true,
  142. title,
  143. message,
  144. type: "confirm",
  145. onConfirm,
  146. };
  147. };
  148. // 알림 모달 닫기
  149. const closeAlertModal = () => {
  150. alertModal.value.show = false;
  151. };
  152. // 알림 모달 확인
  153. const handleAlertConfirm = () => {
  154. if (alertModal.value.onConfirm) {
  155. alertModal.value.onConfirm();
  156. }
  157. closeAlertModal();
  158. };
  159. // 알림 모달 취소
  160. const handleAlertCancel = () => {
  161. closeAlertModal();
  162. };
  163. // 보이는 페이지 번호 계산
  164. const visiblePages = computed(() => {
  165. const pages = [];
  166. const maxVisible = 5;
  167. let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
  168. let end = Math.min(totalPages.value, start + maxVisible - 1);
  169. if (end - start < maxVisible - 1) {
  170. start = Math.max(1, end - maxVisible + 1);
  171. }
  172. for (let i = start; i <= end; i++) {
  173. pages.push(i);
  174. }
  175. return pages;
  176. });
  177. // 데이터 로드
  178. const loadBranches = async () => {
  179. isLoading.value = true;
  180. const params = {
  181. page: currentPage.value,
  182. per_page: perPage.value,
  183. };
  184. const { data, error } = await get("/field/list", { params });
  185. console.log("[BranchList] API 응답:", { data, error });
  186. // API 응답: { success: true, data: { items, total }, message }
  187. if (data?.success && data?.data) {
  188. branches.value = data.data.items || [];
  189. totalCount.value = data.data.total || 0;
  190. totalPages.value = Math.ceil(totalCount.value / perPage.value);
  191. console.log("[BranchList] 로드 성공:", branches.value.length);
  192. }
  193. isLoading.value = false;
  194. };
  195. // 페이지 변경
  196. const changePage = (page) => {
  197. if (page < 1 || page > totalPages.value) return;
  198. currentPage.value = page;
  199. loadBranches();
  200. window.scrollTo({ top: 0, behavior: "smooth" });
  201. };
  202. // 낚시분야 등록 페이지로 이동
  203. const goToCreate = () => {
  204. router.push("/site-manager/field/create");
  205. };
  206. // 낚시분야 수정 페이지로 이동
  207. const goToEdit = (id) => {
  208. router.push(`/site-manager/field/edit/${id}`);
  209. };
  210. // 낚시분야 삭제
  211. const deleteBranch = (id) => {
  212. showConfirm(
  213. "정말 삭제하시겠습니까?",
  214. async () => {
  215. const { data, error } = await del(`/field/${id}`);
  216. if (error || !data?.success) {
  217. showAlert(error?.message || data?.message || "삭제에 실패했습니다.", "오류");
  218. } else {
  219. showAlert(data.message || "낚시분야가 삭제되었습니다.", "성공");
  220. loadBranches();
  221. }
  222. },
  223. "낚시분야 삭제"
  224. );
  225. };
  226. // 사용/비사용 토글
  227. const toggleActive = (id, currentStatus) => {
  228. console.log("[toggleActive] 호출:", { id, currentStatus });
  229. const statusText = currentStatus == 1 ? "비사용" : "사용";
  230. showConfirm(
  231. `낚시분야을 ${statusText} 상태로 변경하시겠습니까?`,
  232. async () => {
  233. console.log("[toggleActive] API 호출:", `/field/${id}/toggle-active`);
  234. // POST 방식으로 변경 (PATCH 대신)
  235. const { data, error } = await post(`/field/${id}/toggle-active`);
  236. console.log("[toggleActive] API 응답:", { data, error });
  237. if (error || !data?.success) {
  238. console.error("[toggleActive] 에러 상세:", error);
  239. showAlert(
  240. error?.message || data?.message || "상태 변경에 실패했습니다.",
  241. "오류"
  242. );
  243. } else {
  244. showAlert(data.message || "낚시분야 상태가 변경되었습니다.", "성공");
  245. loadBranches();
  246. }
  247. },
  248. "상태 변경"
  249. );
  250. };
  251. onMounted(() => {
  252. loadBranches();
  253. });
  254. </script>