|
@@ -1,12 +1,25 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="admin--branch-list">
|
|
|
|
|
- <!-- 상단 버튼 -->
|
|
|
|
|
|
|
+ <div class="admin--field-list">
|
|
|
|
|
+ <!-- 상단 검색/액션 영역 -->
|
|
|
<div class="admin--search-box">
|
|
<div class="admin--search-box">
|
|
|
- <div class="admin--search-form"></div>
|
|
|
|
|
|
|
+ <div class="admin--search-form">
|
|
|
|
|
+ <select v-model="filterStatus" @change="onSearch" class="admin--form-select admin--search-select">
|
|
|
|
|
+ <option value="">전체 상태</option>
|
|
|
|
|
+ <option value="Y">사용중</option>
|
|
|
|
|
+ <option value="N">미사용</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ <input
|
|
|
|
|
+ v-model="searchQuery"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ placeholder="분야명으로 검색"
|
|
|
|
|
+ @keyup.enter="onSearch"
|
|
|
|
|
+ class="admin--form-input admin--search-input"
|
|
|
|
|
+ />
|
|
|
|
|
+ <button @click="onSearch" class="admin--btn-small admin--btn-small-primary">검색</button>
|
|
|
|
|
+ <button @click="resetSearch" class="admin--btn-small admin--btn-small-secondary">초기화</button>
|
|
|
|
|
+ </div>
|
|
|
<div class="admin--search-actions">
|
|
<div class="admin--search-actions">
|
|
|
- <button class="admin--btn-add" @click="goToCreate">
|
|
|
|
|
- + 새 분야 추가
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <button class="admin--btn-add" @click="goToCreate">+ 새 분야 추가</button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -15,49 +28,42 @@
|
|
|
<table class="admin--table">
|
|
<table class="admin--table">
|
|
|
<thead>
|
|
<thead>
|
|
|
<tr>
|
|
<tr>
|
|
|
- <th></th>
|
|
|
|
|
- <th style="">번호</th>
|
|
|
|
|
- <th style="width: 70%">분야</th>
|
|
|
|
|
|
|
+ <th style="width: 80px;">번호</th>
|
|
|
|
|
+ <th style="width: 40%;">분야</th>
|
|
|
<th>가중치</th>
|
|
<th>가중치</th>
|
|
|
<th>등록일</th>
|
|
<th>등록일</th>
|
|
|
- <th>관리</th>
|
|
|
|
|
|
|
+ <th>상태</th>
|
|
|
|
|
+ <th style="width: 120px;">관리</th>
|
|
|
</tr>
|
|
</tr>
|
|
|
</thead>
|
|
</thead>
|
|
|
<tbody>
|
|
<tbody>
|
|
|
<tr v-if="isLoading">
|
|
<tr v-if="isLoading">
|
|
|
<td colspan="6" class="admin--table-loading">데이터를 불러오는 중...</td>
|
|
<td colspan="6" class="admin--table-loading">데이터를 불러오는 중...</td>
|
|
|
</tr>
|
|
</tr>
|
|
|
- <tr v-else-if="!branches || branches.length === 0">
|
|
|
|
|
|
|
+ <tr v-else-if="!fields || fields.length === 0">
|
|
|
<td colspan="6" class="admin--table-empty">등록된 낚시분야가 없습니다.</td>
|
|
<td colspan="6" class="admin--table-empty">등록된 낚시분야가 없습니다.</td>
|
|
|
</tr>
|
|
</tr>
|
|
|
- <tr v-else v-for="(branch, index) in branches" :key="branch.id">
|
|
|
|
|
- <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
|
|
|
|
|
- <td class="admin--table-title">{{ branch.name }}</td>
|
|
|
|
|
- <!-- <td>{{ branch.main_phone }}</td>
|
|
|
|
|
- <td>{{ branch.address }}</td> -->
|
|
|
|
|
|
|
+ <tr
|
|
|
|
|
+ v-else
|
|
|
|
|
+ v-for="(field, index) in fields"
|
|
|
|
|
+ :key="field.id"
|
|
|
|
|
+ class="admin--table-row-clickable"
|
|
|
|
|
+ @click="goToDetail(field.id)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <td class="date">{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
|
|
|
|
|
+ <td class="admin--table-title">{{ field.name }}</td>
|
|
|
|
|
+ <td class="color--yellow">{{ field.weight }}</td>
|
|
|
|
|
+ <td class="date">{{ formatDate(field.created_at) }}</td>
|
|
|
<td>
|
|
<td>
|
|
|
- <button
|
|
|
|
|
- class="admin--toggle-btn"
|
|
|
|
|
- :class="{ 'is-active': branch.is_active == 1 }"
|
|
|
|
|
- @click="toggleActive(branch.id, branch.is_active)"
|
|
|
|
|
- >
|
|
|
|
|
- {{ branch.is_active == 1 ? "사용" : "비사용" }}
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <span :class="['admin--badge', getStatusBadgeClass(field.status_YN)]">
|
|
|
|
|
+ {{ getStatusLabel(field.status_YN) }}
|
|
|
|
|
+ </span>
|
|
|
</td>
|
|
</td>
|
|
|
<td>
|
|
<td>
|
|
|
<div class="admin--table-actions">
|
|
<div class="admin--table-actions">
|
|
|
- <button
|
|
|
|
|
- class="admin--btn-small admin--btn-small-primary"
|
|
|
|
|
- @click="goToEdit(branch.id)"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <button class="admin--btn-small admin--btn-blue" @click.stop="goToEdit(field.id)">
|
|
|
수정
|
|
수정
|
|
|
</button>
|
|
</button>
|
|
|
- <button
|
|
|
|
|
- class="admin--btn-small admin--btn-small-danger"
|
|
|
|
|
- @click="deleteBranch(branch.id)"
|
|
|
|
|
- >
|
|
|
|
|
- 삭제
|
|
|
|
|
- </button>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</td>
|
|
</td>
|
|
|
</tr>
|
|
</tr>
|
|
@@ -91,24 +97,12 @@
|
|
|
다음
|
|
다음
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <!-- 알림 모달 -->
|
|
|
|
|
- <AdminAlertModal
|
|
|
|
|
- v-if="alertModal.show"
|
|
|
|
|
- :title="alertModal.title"
|
|
|
|
|
- :message="alertModal.message"
|
|
|
|
|
- :type="alertModal.type"
|
|
|
|
|
- @confirm="handleAlertConfirm"
|
|
|
|
|
- @cancel="handleAlertCancel"
|
|
|
|
|
- @close="closeAlertModal"
|
|
|
|
|
- />
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref, computed, onMounted } from "vue";
|
|
import { ref, computed, onMounted } from "vue";
|
|
|
import { useRouter } from "vue-router";
|
|
import { useRouter } from "vue-router";
|
|
|
- import AdminAlertModal from "~/components/admin/AdminAlertModal.vue";
|
|
|
|
|
|
|
|
|
|
definePageMeta({
|
|
definePageMeta({
|
|
|
layout: "admin",
|
|
layout: "admin",
|
|
@@ -116,63 +110,17 @@
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
- const { get, del, post } = useApi();
|
|
|
|
|
|
|
+ const { get } = useApi();
|
|
|
|
|
|
|
|
const isLoading = ref(false);
|
|
const isLoading = ref(false);
|
|
|
- const branches = ref([]);
|
|
|
|
|
|
|
+ const fields = ref([]);
|
|
|
const currentPage = ref(1);
|
|
const currentPage = ref(1);
|
|
|
const perPage = ref(10);
|
|
const perPage = ref(10);
|
|
|
const totalCount = ref(0);
|
|
const totalCount = ref(0);
|
|
|
const totalPages = ref(0);
|
|
const totalPages = ref(0);
|
|
|
|
|
|
|
|
- // 알림 모달
|
|
|
|
|
- const alertModal = ref({
|
|
|
|
|
- show: false,
|
|
|
|
|
- title: "알림",
|
|
|
|
|
- message: "",
|
|
|
|
|
- type: "alert",
|
|
|
|
|
- onConfirm: null,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 알림 모달 표시
|
|
|
|
|
- const showAlert = (message, title = "알림") => {
|
|
|
|
|
- alertModal.value = {
|
|
|
|
|
- show: true,
|
|
|
|
|
- title,
|
|
|
|
|
- message,
|
|
|
|
|
- type: "alert",
|
|
|
|
|
- onConfirm: null,
|
|
|
|
|
- };
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 확인 모달 표시
|
|
|
|
|
- const showConfirm = (message, onConfirm, title = "확인") => {
|
|
|
|
|
- alertModal.value = {
|
|
|
|
|
- show: true,
|
|
|
|
|
- title,
|
|
|
|
|
- message,
|
|
|
|
|
- type: "confirm",
|
|
|
|
|
- onConfirm,
|
|
|
|
|
- };
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 알림 모달 닫기
|
|
|
|
|
- const closeAlertModal = () => {
|
|
|
|
|
- alertModal.value.show = false;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 알림 모달 확인
|
|
|
|
|
- const handleAlertConfirm = () => {
|
|
|
|
|
- if (alertModal.value.onConfirm) {
|
|
|
|
|
- alertModal.value.onConfirm();
|
|
|
|
|
- }
|
|
|
|
|
- closeAlertModal();
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 알림 모달 취소
|
|
|
|
|
- const handleAlertCancel = () => {
|
|
|
|
|
- closeAlertModal();
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const searchQuery = ref("");
|
|
|
|
|
+ const filterStatus = ref("");
|
|
|
|
|
|
|
|
// 보이는 페이지 번호 계산
|
|
// 보이는 페이지 번호 계산
|
|
|
const visiblePages = computed(() => {
|
|
const visiblePages = computed(() => {
|
|
@@ -184,105 +132,84 @@
|
|
|
if (end - start < maxVisible - 1) {
|
|
if (end - start < maxVisible - 1) {
|
|
|
start = Math.max(1, end - maxVisible + 1);
|
|
start = Math.max(1, end - maxVisible + 1);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
for (let i = start; i <= end; i++) {
|
|
|
pages.push(i);
|
|
pages.push(i);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return pages;
|
|
return pages;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// 데이터 로드
|
|
// 데이터 로드
|
|
|
- const loadBranches = async () => {
|
|
|
|
|
|
|
+ const loadFields = async () => {
|
|
|
isLoading.value = true;
|
|
isLoading.value = true;
|
|
|
|
|
|
|
|
const params = {
|
|
const params = {
|
|
|
page: currentPage.value,
|
|
page: currentPage.value,
|
|
|
per_page: perPage.value,
|
|
per_page: perPage.value,
|
|
|
};
|
|
};
|
|
|
|
|
+ if (searchQuery.value) params.search = searchQuery.value;
|
|
|
|
|
+ if (filterStatus.value) params.status = filterStatus.value;
|
|
|
|
|
|
|
|
const { data, error } = await get("/field/list", { params });
|
|
const { data, error } = await get("/field/list", { params });
|
|
|
|
|
|
|
|
- console.log("[BranchList] API 응답:", { data, error });
|
|
|
|
|
-
|
|
|
|
|
- // API 응답: { success: true, data: { items, total }, message }
|
|
|
|
|
- if (data?.success && data?.data) {
|
|
|
|
|
- branches.value = data.data.items || [];
|
|
|
|
|
|
|
+ if (error) {
|
|
|
|
|
+ console.error("[FieldList] 목록 로드 실패:", error);
|
|
|
|
|
+ fields.value = [];
|
|
|
|
|
+ totalCount.value = 0;
|
|
|
|
|
+ totalPages.value = 0;
|
|
|
|
|
+ } else if (data?.success && data?.data) {
|
|
|
|
|
+ fields.value = data.data.items || [];
|
|
|
totalCount.value = data.data.total || 0;
|
|
totalCount.value = data.data.total || 0;
|
|
|
- totalPages.value = Math.ceil(totalCount.value / perPage.value);
|
|
|
|
|
- console.log("[BranchList] 로드 성공:", branches.value.length);
|
|
|
|
|
|
|
+ totalPages.value = data.data.total_pages || 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
isLoading.value = false;
|
|
isLoading.value = false;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 검색
|
|
|
|
|
+ const onSearch = () => {
|
|
|
|
|
+ currentPage.value = 1;
|
|
|
|
|
+ loadFields();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 검색 초기화
|
|
|
|
|
+ const resetSearch = () => {
|
|
|
|
|
+ searchQuery.value = "";
|
|
|
|
|
+ filterStatus.value = "";
|
|
|
|
|
+ currentPage.value = 1;
|
|
|
|
|
+ loadFields();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
// 페이지 변경
|
|
// 페이지 변경
|
|
|
const changePage = (page) => {
|
|
const changePage = (page) => {
|
|
|
if (page < 1 || page > totalPages.value) return;
|
|
if (page < 1 || page > totalPages.value) return;
|
|
|
currentPage.value = page;
|
|
currentPage.value = page;
|
|
|
- loadBranches();
|
|
|
|
|
|
|
+ loadFields();
|
|
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // 낚시분야 등록 페이지로 이동
|
|
|
|
|
- const goToCreate = () => {
|
|
|
|
|
- router.push("/site-manager/field/create");
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 낚시분야 수정 페이지로 이동
|
|
|
|
|
- const goToEdit = (id) => {
|
|
|
|
|
- router.push(`/site-manager/field/edit/${id}`);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 낚시분야 삭제
|
|
|
|
|
- const deleteBranch = (id) => {
|
|
|
|
|
- showConfirm(
|
|
|
|
|
- "정말 삭제하시겠습니까?",
|
|
|
|
|
- async () => {
|
|
|
|
|
- const { data, error } = await del(`/field/${id}`);
|
|
|
|
|
-
|
|
|
|
|
- if (error || !data?.success) {
|
|
|
|
|
- showAlert(error?.message || data?.message || "삭제에 실패했습니다.", "오류");
|
|
|
|
|
- } else {
|
|
|
|
|
- showAlert(data.message || "낚시분야가 삭제되었습니다.", "성공");
|
|
|
|
|
- loadBranches();
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- "낚시분야 삭제"
|
|
|
|
|
- );
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 사용/비사용 토글
|
|
|
|
|
- const toggleActive = (id, currentStatus) => {
|
|
|
|
|
- console.log("[toggleActive] 호출:", { id, currentStatus });
|
|
|
|
|
- const statusText = currentStatus == 1 ? "비사용" : "사용";
|
|
|
|
|
-
|
|
|
|
|
- showConfirm(
|
|
|
|
|
- `낚시분야을 ${statusText} 상태로 변경하시겠습니까?`,
|
|
|
|
|
- async () => {
|
|
|
|
|
- console.log("[toggleActive] API 호출:", `/field/${id}/toggle-active`);
|
|
|
|
|
-
|
|
|
|
|
- // POST 방식으로 변경 (PATCH 대신)
|
|
|
|
|
- const { data, error } = await post(`/field/${id}/toggle-active`);
|
|
|
|
|
-
|
|
|
|
|
- console.log("[toggleActive] API 응답:", { data, error });
|
|
|
|
|
-
|
|
|
|
|
- if (error || !data?.success) {
|
|
|
|
|
- console.error("[toggleActive] 에러 상세:", error);
|
|
|
|
|
- showAlert(
|
|
|
|
|
- error?.message || data?.message || "상태 변경에 실패했습니다.",
|
|
|
|
|
- "오류"
|
|
|
|
|
- );
|
|
|
|
|
- } else {
|
|
|
|
|
- showAlert(data.message || "낚시분야 상태가 변경되었습니다.", "성공");
|
|
|
|
|
- loadBranches();
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- "상태 변경"
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // 이동
|
|
|
|
|
+ const goToCreate = () => router.push("/site-manager/field/create");
|
|
|
|
|
+ const goToDetail = (id) => router.push(`/site-manager/field/detail/${id}`);
|
|
|
|
|
+ const goToEdit = (id) => router.push(`/site-manager/field/edit/${id}`);
|
|
|
|
|
+
|
|
|
|
|
+ // 상태 라벨 / 뱃지 클래스
|
|
|
|
|
+ const getStatusLabel = (status) => (status === "Y" ? "사용중" : "미사용");
|
|
|
|
|
+ const getStatusBadgeClass = (status) =>
|
|
|
|
|
+ status === "Y" ? "admin--badge-active" : "admin--badge-ended";
|
|
|
|
|
+
|
|
|
|
|
+ // 날짜 포맷
|
|
|
|
|
+ const formatDate = (dateString) => {
|
|
|
|
|
+ if (!dateString) return "-";
|
|
|
|
|
+ const date = new Date(dateString.replace(" ", "T"));
|
|
|
|
|
+ if (isNaN(date.getTime())) return dateString;
|
|
|
|
|
+ return date.toLocaleDateString("ko-KR", {
|
|
|
|
|
+ year: "numeric",
|
|
|
|
|
+ month: "2-digit",
|
|
|
|
|
+ day: "2-digit",
|
|
|
|
|
+ });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
- loadBranches();
|
|
|
|
|
|
|
+ loadFields();
|
|
|
});
|
|
});
|
|
|
-</script>
|
|
|
|
|
|
|
+</script>
|