소스 검색

낚시분야 등록 진행중

DESKTOP-T61HUSC\user 1 개월 전
부모
커밋
a1182612a1
33개의 변경된 파일390개의 추가작업 그리고 5303개의 파일을 삭제
  1. 113 31
      app/assets/scss/admin.scss
  2. 25 29
      app/layouts/admin.vue
  3. 0 487
      app/pages/site-manager/basic/popup/create.vue
  4. 0 558
      app/pages/site-manager/basic/popup/edit/[id].vue
  5. 0 319
      app/pages/site-manager/basic/popup/index.vue
  6. 0 324
      app/pages/site-manager/basic/site-info.vue
  7. 0 202
      app/pages/site-manager/branch/create.vue
  8. 143 0
      app/pages/site-manager/field/create.vue
  9. 0 0
      app/pages/site-manager/field/edit/[id].vue
  10. 22 70
      app/pages/site-manager/field/list.vue
  11. 0 0
      app/pages/site-manager/field/manager/create.vue
  12. 0 0
      app/pages/site-manager/field/manager/edit/[id].vue
  13. 0 0
      app/pages/site-manager/field/manager/index.vue
  14. 0 0
      app/pages/site-manager/fishing/create.vue
  15. 0 0
      app/pages/site-manager/fishing/edit/[id].vue
  16. 0 0
      app/pages/site-manager/fishing/list.vue
  17. 3 117
      backend/app/Config/Routes.php
  18. 0 197
      backend/app/Controllers/Api/AdvisorController.php
  19. 0 86
      backend/app/Controllers/Api/BasicController.php
  20. 0 235
      backend/app/Controllers/Api/BranchController.php
  21. 0 215
      backend/app/Controllers/Api/BranchManagerController.php
  22. 0 87
      backend/app/Controllers/Api/BrochureController.php
  23. 0 291
      backend/app/Controllers/Api/EventController.php
  24. 0 135
      backend/app/Controllers/Api/IrController.php
  25. 0 238
      backend/app/Controllers/Api/NewsController.php
  26. 0 261
      backend/app/Controllers/Api/NoticeController.php
  27. 0 199
      backend/app/Controllers/Api/PopupController.php
  28. 0 191
      backend/app/Controllers/Api/PopupController.php.bak
  29. 0 383
      backend/app/Controllers/Api/SalesStaffController.php
  30. 0 304
      backend/app/Controllers/Api/ServiceCenterController.php
  31. 0 306
      backend/app/Controllers/Api/ShowroomController.php
  32. 0 20
      backend/app/Controllers/Api/TestController.php
  33. 84 18
      db.vuerd.json

+ 113 - 31
app/assets/scss/admin.scss

@@ -1240,6 +1240,7 @@ footer {
 // Light Theme Color Variables
 :root {
   --admin-yellow: #e8b546;
+  --admin-red: #E85D3F;
   --admin-bg-navy: #1a2332;
   --admin-bg-primary: rgba(246, 247, 248, 1);
   --admin-bg-secondary: #ffffff;
@@ -1602,26 +1603,13 @@ footer {
 
   .admin--header-left {
     display: flex;
-    align-items: center;
-  }
-
-  .admin--logo {
-    display: flex;
-    align-items: baseline;
-    gap: 12px;
-
+    flex-direction: column;
     h1 {
       font-size: 18px;
       font-weight: 700;
       color: var(--admin-text-primary);
       margin: 0;
     }
-
-    .admin--logo-sub {
-      font-size: 16px;
-      color: var(--admin-text-secondary);
-      font-weight: 400;
-    }
   }
 
   .admin--header-right {
@@ -1868,9 +1856,6 @@ footer {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 32px;
-  padding-bottom: 16px;
-  border-bottom: 1px solid var(--admin-border-color);
 }
 
 .admin--page-title {
@@ -1884,8 +1869,7 @@ footer {
 .admin--breadcrumb {
   display: flex;
   align-items: center;
-  gap: 8px;
-  font-size: 14px;
+  font-size: 12px;
 
   .admin--breadcrumb-link {
     color: var(--admin-text-secondary);
@@ -1898,12 +1882,13 @@ footer {
   }
 
   .admin--breadcrumb-current {
-    color: var(--admin-text-primary);
-    font-weight: 500;
+    color: #666b75;
+    font-weight: 400;
   }
 
   .admin--breadcrumb-separator {
-    color: var(--admin-text-muted);
+    color: #666b75;
+    font-weight: 400;
   }
 }
 
@@ -2003,7 +1988,6 @@ footer {
 
 // Admin Form Styles
 .admin--form {
-  max-width: 800px;
 
   .admin--form-group {
     margin-bottom: 24px;
@@ -2127,14 +2111,16 @@ footer {
   }
 
   .admin--btn {
-    padding: 12px 32px;
+    padding: 12px;
     border: none;
-    border-radius: 6px;
-    font-size: 14px;
+    border-radius: 4px;
+    font-size: 13px;
     font-weight: 600;
     cursor: pointer;
     transition: all 0.3s ease;
-    font-family: 'FORDKOREAType', sans-serif;
+    min-width: 100px;
+    color: #666b75;
+    border: 1px solid #e8eaef;
 
     &:disabled {
       opacity: 0.5;
@@ -2142,6 +2128,23 @@ footer {
     }
   }
 
+  .ml--auto{
+    margin-left: auto;
+  }
+  .mr--auto{
+    margin-right: auto;
+  }
+
+  .admin--btn-red{
+    background-color: #e85d3f;
+    color: #fff;
+  }
+
+  .admin--btn-red-border{
+    border-color: #e85d3f;
+    color: #e85d3f;
+  }
+
   .admin--btn-primary {
     background: var(--admin-accent-primary);
     color: #ffffff;
@@ -2273,6 +2276,17 @@ footer {
     flex: 1;
     max-width: 400px;
   }
+
+  .admin--btn-add{
+    width: 140px;
+    background-color: #e85d3f;
+    border-radius: 6px;
+    color: #fff;
+    font-weight: 600;
+    font-size: 13px;
+    padding: 12px;
+    cursor: pointer;
+  }
 }
 
 // 날짜 입력 필드
@@ -7592,10 +7606,78 @@ footer {
   }
 }
 
-.lincoln {
-  .service--center--ford {
-    a {
-      background-color: #DB784D;
+.admin--form--table{
+  width: 100%;
+  p{
+    color: #666b75;
+    font-weight: 400;
+    font-size: 12px;
+  }
+  tr{
+    border-top: 1px solid #F0F2F6;
+    &:last-child{
+      border-bottom: 1px solid #F0F2F6;
+    }
+  }
+  th{
+    padding: 20px 16px;
+    text-align: left;
+    color: #1a2b4a;
+    font-weight: 700;
+    font-size: 13px;
+    background-color: #F8f9fb;
+    .admin--required{
+      color: var(--admin-red);
+    }
+  }
+  td{
+    padding: 12px 16px;
+    .input--wrap{
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      input{
+        width: 320px;
+        &.w--full{
+          width: 100%;
+        }
+        &.w--120{
+          width: 120px;
+        }
+      }
+    }
+  }
+}
+
+.admin--info--box{
+  background-color: #fff8de;
+  border-radius: 6px;
+  margin-top: 32px;
+  padding: 20px;
+  h2{
+    color: #e8b547;
+    font-size: 14px;
+    font-weight: 600;
+    letter-spacing: 0.55px;
+    margin-bottom: 10px;
+  }
+  ul{
+    li{
+      position: relative;
+      color: #1a2b4a;
+      font-size: 13px;
+      font-weight: 400;
+      padding-left: 14px;
+      &::before{
+        content: '';
+        position: absolute;
+        width: 4px;
+        height: 4px;
+        border-radius: 50%;
+        background-color: #1a2b4a;
+        left: 4px;
+        top: 8px;
+      }
     }
   }
 }

+ 25 - 29
app/layouts/admin.vue

@@ -23,13 +23,13 @@
       path: "/site-manager/admins",
     },
     {
-      id: "branch",
+      id: "field",
       title: "🗺️ 분야 및 지역관리",
       children: [
         {
           title: "낚시분야",
-          path: "/site-manager/branch/list",
-          pattern: /^\/site-manager\/branch\/(list|create|edit)/,
+          path: "/site-manager/field/list",
+          pattern: /^\/site-manager\/field\/(list|create|edit)/,
         },
         {
           title: "지역관리",
@@ -44,13 +44,13 @@
       children: [
         {
           title: "선상관리",
-          path: "/site-manager/board/event",
-          pattern: /^\/site-manager\/board\/event/,
+          path: "/site-manager/fishing/list",
+          pattern: /^\/site-manager\/fishing\/list|create|edit/,
         },
         {
           title: "낚시터관리",
-          path: "/site-manager/board/event",
-          pattern: /^\/site-manager\/board\/event/,
+          path: "/site-manager/fishing/event",
+          pattern: /^\/site-manager\/fishing\/event/,
         }
       ],
     },
@@ -108,7 +108,7 @@
 
     // 대시보드
     if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
-      return "대시보드";
+      return "📊 대시보드";
     }
 
     const { child } = findCurrentMenu();
@@ -127,13 +127,13 @@
       return child.title;
     }
 
-    return "대시보드";
+    return "📊 대시보드";
   });
 
   // Breadcrumb 계산
   const breadcrumbs = computed(() => {
     const currentPath = route.path;
-    const crumbs = [{ title: "Home", path: "/site-manager/dashboard" }];
+    const crumbs = [{ title: "대시보드", path: "/site-manager/dashboard" }];
 
     // 대시보드인 경우 Home만 표시
     if (currentPath === "/site-manager/dashboard" || currentPath === "/site-manager") {
@@ -297,9 +297,22 @@
     <!-- Header -->
     <header class="admin--header">
       <div class="admin--header-left">
-        <div class="admin--logo">
-          <h1>{{ pageTitle }}</h1>
+        <div class="admin--page-header">
+          <div class="admin--breadcrumb">
+            <span v-for="(crumb, index) in breadcrumbs" :key="index">
+              <NuxtLink v-if="crumb.path" :to="crumb.path" class="admin--breadcrumb-link">
+                {{ crumb.title }}
+              </NuxtLink>
+              <span v-else class="admin--breadcrumb-current">{{ crumb.title }}</span>
+              <span
+                v-if="index < breadcrumbs.length - 1"
+                class="admin--breadcrumb-separator"
+                >ㆍ</span
+              >
+            </span>
+          </div>
         </div>
+        <h1>{{ pageTitle }}</h1>
       </div>
       <div class="admin--header-right">
         <button class="admin--header-btn" @click="goToProfile">정보수정</button>
@@ -368,23 +381,6 @@
 
       <!-- Content Area -->
       <main class="admin--main">
-        <!-- Breadcrumb & Title -->
-        <!-- <div class="admin--page-header">
-          <div class="admin--breadcrumb">
-            <span v-for="(crumb, index) in breadcrumbs" :key="index">
-              <NuxtLink v-if="crumb.path" :to="crumb.path" class="admin--breadcrumb-link">
-                {{ crumb.title }}
-              </NuxtLink>
-              <span v-else class="admin--breadcrumb-current">{{ crumb.title }}</span>
-              <span
-                v-if="index < breadcrumbs.length - 1"
-                class="admin--breadcrumb-separator"
-                >/</span
-              >
-            </span>
-          </div>
-        </div> -->
-
         <!-- Page Content -->
         <div class="admin--page-content">
           <slot />

+ 0 - 487
app/pages/site-manager/basic/popup/create.vue

@@ -1,487 +0,0 @@
-<template>
-  <div class="admin--popup-form">
-    <form @submit.prevent="handleSubmit" class="admin--form">
-      <!-- 형태 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >형태 <span class="admin--required">*</span></label
-        >
-        <div class="admin--radio-group">
-          <label class="admin--radio-label">
-            <input v-model="formData.type" type="radio" value="html" name="type" />
-            <span>HTML</span>
-          </label>
-          <label class="admin--radio-label">
-            <input v-model="formData.type" type="radio" value="image" name="type" />
-            <span>단일페이지</span>
-          </label>
-        </div>
-      </div>
-
-      <!-- 사이트 선택 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >사이트 <span class="admin--required">*</span></label
-        >
-        <select v-model="formData.site" class="admin--form-select" required>
-          <option value="ford">포드</option>
-          <option value="lincoln">링컨</option>
-        </select>
-      </div>
-
-      <!-- 제목 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >제목 <span class="admin--required">*</span></label
-        >
-        <input
-          v-model="formData.title"
-          type="text"
-          class="admin--form-input"
-          placeholder="팝업 제목을 입력하세요"
-          required
-        />
-      </div>
-
-      <!-- 시작일/종료일 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >시작일/종료일 <span class="admin--required">*</span></label
-        >
-        <div class="admin--date-range">
-          <DatePicker
-            v-model="formData.start_date"
-            placeholder="시작일 선택"
-            :max-date="formData.end_date"
-            required
-          />
-          <span class="admin--date-separator">~</span>
-          <DatePicker
-            v-model="formData.end_date"
-            placeholder="종료일 선택"
-            :min-date="formData.start_date"
-            required
-          />
-        </div>
-      </div>
-
-      <!-- 시작시간 (선택) -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">시작시간 <span class="admin--hint">(미입력 시 시작일 00:00부터 바로 출력)</span></label>
-        <div class="admin--time-group">
-          <label class="admin--radio-label">
-            <input v-model="formData.start_period" type="radio" value="AM" name="start_period" />
-            <span>오전</span>
-          </label>
-          <label class="admin--radio-label">
-            <input v-model="formData.start_period" type="radio" value="PM" name="start_period" />
-            <span>오후</span>
-          </label>
-          <input
-            v-model.number="formData.start_hour"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="시"
-            min="1"
-            max="12"
-          />
-          <span>:</span>
-          <input
-            v-model.number="formData.start_minute"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="분"
-            min="0"
-            max="59"
-          />
-        </div>
-      </div>
-
-      <!-- 종료시간 (선택) -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">종료시간 <span class="admin--hint">(미입력 시 종료일 자정까지 노출)</span></label>
-        <div class="admin--time-group">
-          <label class="admin--radio-label">
-            <input v-model="formData.end_period" type="radio" value="AM" name="end_period" />
-            <span>오전</span>
-          </label>
-          <label class="admin--radio-label">
-            <input v-model="formData.end_period" type="radio" value="PM" name="end_period" />
-            <span>오후</span>
-          </label>
-          <input
-            v-model.number="formData.end_hour"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="시"
-            min="1"
-            max="12"
-          />
-          <span>:</span>
-          <input
-            v-model.number="formData.end_minute"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="분"
-            min="0"
-            max="59"
-          />
-        </div>
-      </div>
-
-      <!-- 팝업창 사이즈 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >팝업창 사이즈 <span class="admin--required">*</span></label
-        >
-        <div class="admin--size-group">
-          <div class="admin--size-item">
-            <label>가로</label>
-            <input
-              v-model.number="formData.width"
-              type="number"
-              class="admin--form-input"
-              placeholder="800"
-              min="100"
-              required
-            />
-            <span>px</span>
-          </div>
-          <div class="admin--size-item">
-            <label>세로</label>
-            <input
-              v-model.number="formData.height"
-              type="number"
-              class="admin--form-input"
-              placeholder="600"
-              min="100"
-              required
-            />
-            <span>px</span>
-          </div>
-        </div>
-      </div>
-
-      <!-- 팝업창 위치 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >팝업창 위치 <span class="admin--required">*</span></label
-        >
-        <div class="admin--size-group">
-          <div class="admin--size-item">
-            <label>TOP</label>
-            <input
-              v-model.number="formData.position_top"
-              type="number"
-              class="admin--form-input"
-              placeholder="100"
-              min="0"
-              required
-            />
-            <span>px</span>
-          </div>
-          <div class="admin--size-item">
-            <label>LEFT</label>
-            <input
-              v-model.number="formData.position_left"
-              type="number"
-              class="admin--form-input"
-              placeholder="100"
-              min="0"
-              required
-            />
-            <span>px</span>
-          </div>
-        </div>
-      </div>
-
-      <!-- 쿠키설정 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">쿠키설정</label>
-        <div class="admin--radio-group">
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.cookie_setting"
-              type="radio"
-              value="today"
-              name="cookie_setting"
-            />
-            <span>오늘 하루 창 띄우지 않음</span>
-          </label>
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.cookie_setting"
-              type="radio"
-              value="forever"
-              name="cookie_setting"
-            />
-            <span>다시는 창을 띄우지 않음</span>
-          </label>
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.cookie_setting"
-              type="radio"
-              value="none"
-              name="cookie_setting"
-            />
-            <span>사용 안 함</span>
-          </label>
-        </div>
-      </div>
-
-      <!-- 출력내용 (HTML) -->
-      <div v-if="formData.type === 'html'" class="admin--form-group">
-        <label class="admin--form-label"
-          >출력내용 <span class="admin--required">*</span></label
-        >
-        <SunEditor
-          v-model="formData.content"
-          height="400px"
-          placeholder="팝업 내용을 입력하세요"
-        />
-      </div>
-
-      <!-- 출력내용 (이미지) -->
-      <div v-if="formData.type === 'image'" class="admin--form-group">
-        <label class="admin--form-label"
-          >이미지 첨부 <span class="admin--required">*</span></label
-        >
-        <input
-          type="file"
-          accept="image/*"
-          class="admin--form-file"
-          @change="handleImageUpload"
-        />
-        <div v-if="imagePreview" class="admin--image-preview">
-          <img :src="imagePreview" alt="미리보기" />
-          <button type="button" class="admin--btn-remove-image" @click="removeImage">
-            삭제
-          </button>
-        </div>
-      </div>
-
-      <!-- 링크 URL (단일페이지일 경우) -->
-      <div v-if="formData.type === 'image'" class="admin--form-group">
-        <label class="admin--form-label">링크 URL</label>
-        <input
-          v-model="formData.link_url"
-          type="url"
-          class="admin--form-input"
-          placeholder="https://example.com"
-        />
-      </div>
-
-      <!-- 링크 타겟 (단일페이지일 경우) -->
-      <div v-if="formData.type === 'image'" class="admin--form-group">
-        <label class="admin--form-label">링크 열기 방식</label>
-        <div class="admin--radio-group">
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.link_target"
-              type="radio"
-              value="_blank"
-              name="link_target"
-            />
-            <span>새창</span>
-          </label>
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.link_target"
-              type="radio"
-              value="_self"
-              name="link_target"
-            />
-            <span>현재창</span>
-          </label>
-        </div>
-      </div>
-
-      <!-- 버튼 영역 -->
-      <div class="admin--form-actions">
-        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
-          {{ isSaving ? "저장 중..." : "확인" }}
-        </button>
-        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
-          목록
-        </button>
-      </div>
-
-      <!-- 성공/에러 메시지 -->
-      <div v-if="successMessage" class="admin--alert admin--alert-success">
-        {{ successMessage }}
-      </div>
-      <div v-if="errorMessage" class="admin--alert admin--alert-error">
-        {{ errorMessage }}
-      </div>
-    </form>
-  </div>
-</template>
-
-<script setup>
-  import { ref } from "vue";
-  import { useRouter } from "vue-router";
-  import SunEditor from "~/components/admin/SunEditor.vue";
-  import DatePicker from "~/components/admin/DatePicker.vue";
-
-  definePageMeta({
-    layout: "admin",
-    middleware: ["auth"],
-  });
-
-  const router = useRouter();
-  const { post, upload } = useApi();
-
-  const isSaving = ref(false);
-  const successMessage = ref("");
-  const errorMessage = ref("");
-  const imagePreview = ref(null);
-  const imageFile = ref(null);
-
-  const formData = ref({
-    type: "html",
-    site: "ford",
-    title: "",
-    start_date: "",
-    end_date: "",
-    start_period: "AM",
-    start_hour: null,
-    start_minute: null,
-    end_period: "AM",
-    end_hour: null,
-    end_minute: null,
-    width: 800,
-    height: 600,
-    position_top: 100,
-    position_left: 100,
-    cookie_setting: "none",
-    content: "",
-    image_url: "",
-    link_url: "",
-    link_target: "_blank",
-  });
-
-  // 12시간제 → 24시간 "HH:MM:SS" (입력이 비어있으면 null)
-  const toTime24 = (period, hour, minute) => {
-    if (hour === null || hour === undefined || hour === "") return null;
-    let h = Number(hour);
-    const m = Number(minute || 0);
-    if (isNaN(h) || h < 1 || h > 12) return null;
-    if (period === "PM" && h < 12) h += 12;
-    if (period === "AM" && h === 12) h = 0;
-    return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`;
-  };
-
-  // 이미지 업로드
-  const handleImageUpload = (event) => {
-    const file = event.target.files[0];
-    if (!file) return;
-
-    if (!file.type.startsWith("image/")) {
-      alert("이미지 파일만 업로드 가능합니다.");
-      return;
-    }
-
-    imageFile.value = file;
-
-    // 미리보기
-    const reader = new FileReader();
-    reader.onload = (e) => {
-      imagePreview.value = e.target.result;
-    };
-    reader.readAsDataURL(file);
-  };
-
-  // 이미지 삭제
-  const removeImage = () => {
-    imagePreview.value = null;
-    imageFile.value = null;
-    formData.value.image_url = "";
-  };
-
-  // 폼 제출
-  const handleSubmit = async () => {
-    successMessage.value = "";
-    errorMessage.value = "";
-
-    // 유효성 검사
-    if (!formData.value.title) {
-      errorMessage.value = "제목을 입력하세요.";
-      return;
-    }
-
-    if (!formData.value.start_date || !formData.value.end_date) {
-      errorMessage.value = "시작일과 종료일을 선택하세요.";
-      return;
-    }
-
-    if (formData.value.type === "html" && !formData.value.content) {
-      errorMessage.value = "출력내용을 입력하세요.";
-      return;
-    }
-
-    if (
-      formData.value.type === "image" &&
-      !imageFile.value &&
-      !formData.value.image_url
-    ) {
-      errorMessage.value = "이미지를 첨부하세요.";
-      return;
-    }
-
-    isSaving.value = true;
-
-    try {
-      let imageUrl = formData.value.image_url;
-
-      // 이미지 업로드 (단일페이지인 경우)
-      if (formData.value.type === "image" && imageFile.value) {
-        const formDataImage = new FormData();
-        formDataImage.append("file", imageFile.value);
-
-        const { data: uploadData, error: uploadError } = await upload(
-          "/upload/image",
-          formDataImage
-        );
-
-        console.log("[Popup Create] 이미지 업로드 응답:", { uploadData, uploadError });
-
-        if (uploadError || !uploadData?.success) {
-          errorMessage.value = uploadError?.message || "이미지 업로드에 실패했습니다.";
-          isSaving.value = false;
-          return;
-        }
-
-        imageUrl = uploadData.data?.url || uploadData.data;
-      }
-
-      // 팝업 등록
-      const submitData = {
-        ...formData.value,
-        image_url: imageUrl,
-        start_time: toTime24(formData.value.start_period, formData.value.start_hour, formData.value.start_minute),
-        end_time: toTime24(formData.value.end_period, formData.value.end_hour, formData.value.end_minute),
-      };
-
-      const { data, error } = await post("/basic/popup", submitData);
-
-      if (error || !data?.success) {
-        errorMessage.value = error?.message || data?.message || "등록에 실패했습니다.";
-      } else {
-        successMessage.value = data.message || "팝업이 등록되었습니다.";
-        setTimeout(() => {
-          router.push("/site-manager/basic/popup");
-        }, 1000);
-      }
-    } catch (error) {
-      errorMessage.value = "서버 오류가 발생했습니다.";
-      console.error("Save error:", error);
-    } finally {
-      isSaving.value = false;
-    }
-  };
-
-  // 목록으로 이동
-  const goToList = () => {
-    router.push("/site-manager/basic/popup");
-  };
-</script>

+ 0 - 558
app/pages/site-manager/basic/popup/edit/[id].vue

@@ -1,558 +0,0 @@
-<template>
-  <div class="admin--popup-form">
-    <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
-
-    <form v-else @submit.prevent="handleSubmit" class="admin--form">
-      <!-- 형태 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >형태 <span class="admin--required">*</span></label
-        >
-        <div class="admin--radio-group">
-          <label class="admin--radio-label">
-            <input v-model="formData.type" type="radio" value="html" name="type" />
-            <span>HTML</span>
-          </label>
-          <label class="admin--radio-label">
-            <input v-model="formData.type" type="radio" value="image" name="type" />
-            <span>단일페이지</span>
-          </label>
-        </div>
-      </div>
-
-      <!-- 사이트 선택 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >사이트 <span class="admin--required">*</span></label
-        >
-        <select v-model="formData.site" class="admin--form-select" required>
-          <option value="ford">포드</option>
-          <option value="lincoln">링컨</option>
-        </select>
-      </div>
-
-      <!-- 제목 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >제목 <span class="admin--required">*</span></label
-        >
-        <input
-          v-model="formData.title"
-          type="text"
-          class="admin--form-input"
-          placeholder="팝업 제목을 입력하세요"
-          required
-        />
-      </div>
-
-      <!-- 시작일/종료일 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >시작일/종료일 <span class="admin--required">*</span></label
-        >
-        <div class="admin--date-range">
-          <DatePicker
-            v-model="formData.start_date"
-            placeholder="시작일 선택"
-            :max-date="formData.end_date"
-            required
-          />
-          <span class="admin--date-separator">~</span>
-          <DatePicker
-            v-model="formData.end_date"
-            placeholder="종료일 선택"
-            :min-date="formData.start_date"
-            required
-          />
-        </div>
-      </div>
-
-      <!-- 시작시간 (선택) -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">시작시간 <span class="admin--hint">(미입력 시 시작일 00:00부터 바로 출력)</span></label>
-        <div class="admin--time-group">
-          <label class="admin--radio-label">
-            <input v-model="formData.start_period" type="radio" value="AM" name="start_period" />
-            <span>오전</span>
-          </label>
-          <label class="admin--radio-label">
-            <input v-model="formData.start_period" type="radio" value="PM" name="start_period" />
-            <span>오후</span>
-          </label>
-          <input
-            v-model.number="formData.start_hour"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="시"
-            min="1"
-            max="12"
-          />
-          <span>:</span>
-          <input
-            v-model.number="formData.start_minute"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="분"
-            min="0"
-            max="59"
-          />
-        </div>
-      </div>
-
-      <!-- 종료시간 (선택) -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">종료시간 <span class="admin--hint">(미입력 시 종료일 자정까지 노출)</span></label>
-        <div class="admin--time-group">
-          <label class="admin--radio-label">
-            <input v-model="formData.end_period" type="radio" value="AM" name="end_period" />
-            <span>오전</span>
-          </label>
-          <label class="admin--radio-label">
-            <input v-model="formData.end_period" type="radio" value="PM" name="end_period" />
-            <span>오후</span>
-          </label>
-          <input
-            v-model.number="formData.end_hour"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="시"
-            min="1"
-            max="12"
-          />
-          <span>:</span>
-          <input
-            v-model.number="formData.end_minute"
-            type="number"
-            class="admin--form-input admin--time-input"
-            placeholder="분"
-            min="0"
-            max="59"
-          />
-        </div>
-      </div>
-
-      <!-- 팝업창 사이즈 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >팝업창 사이즈 <span class="admin--required">*</span></label
-        >
-        <div class="admin--size-group">
-          <div class="admin--size-item">
-            <label>가로</label>
-            <input
-              v-model.number="formData.width"
-              type="number"
-              class="admin--form-input"
-              placeholder="800"
-              min="100"
-              required
-            />
-            <span>px</span>
-          </div>
-          <div class="admin--size-item">
-            <label>세로</label>
-            <input
-              v-model.number="formData.height"
-              type="number"
-              class="admin--form-input"
-              placeholder="600"
-              min="100"
-              required
-            />
-            <span>px</span>
-          </div>
-        </div>
-      </div>
-
-      <!-- 팝업창 위치 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >팝업창 위치 <span class="admin--required">*</span></label
-        >
-        <div class="admin--size-group">
-          <div class="admin--size-item">
-            <label>TOP</label>
-            <input
-              v-model.number="formData.position_top"
-              type="number"
-              class="admin--form-input"
-              placeholder="100"
-              min="0"
-              required
-            />
-            <span>px</span>
-          </div>
-          <div class="admin--size-item">
-            <label>LEFT</label>
-            <input
-              v-model.number="formData.position_left"
-              type="number"
-              class="admin--form-input"
-              placeholder="100"
-              min="0"
-              required
-            />
-            <span>px</span>
-          </div>
-        </div>
-      </div>
-
-      <!-- 쿠키설정 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">쿠키설정</label>
-        <div class="admin--radio-group">
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.cookie_setting"
-              type="radio"
-              value="today"
-              name="cookie_setting"
-            />
-            <span>오늘 하루 창 띄우지 않음</span>
-          </label>
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.cookie_setting"
-              type="radio"
-              value="forever"
-              name="cookie_setting"
-            />
-            <span>다시는 창을 띄우지 않음</span>
-          </label>
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.cookie_setting"
-              type="radio"
-              value="none"
-              name="cookie_setting"
-            />
-            <span>사용 안 함</span>
-          </label>
-        </div>
-      </div>
-
-      <!-- 출력내용 (HTML) -->
-      <div v-if="formData.type === 'html'" class="admin--form-group">
-        <label class="admin--form-label"
-          >출력내용 <span class="admin--required">*</span></label
-        >
-        <SunEditor
-          v-model="formData.content"
-          height="400px"
-          placeholder="팝업 내용을 입력하세요"
-        />
-      </div>
-
-      <!-- 출력내용 (이미지) -->
-      <div v-if="formData.type === 'image'" class="admin--form-group">
-        <label class="admin--form-label"
-          >이미지 첨부 <span class="admin--required">*</span></label
-        >
-        <input
-          type="file"
-          accept="image/*"
-          class="admin--form-file"
-          @change="handleImageUpload"
-        />
-        <div v-if="imagePreview || formData.image_url" class="admin--image-preview">
-          <img :src="imagePreview || formData.image_url" alt="미리보기" />
-          <button type="button" class="admin--btn-remove-image" @click="removeImage">
-            삭제
-          </button>
-        </div>
-      </div>
-
-      <!-- 링크 URL (단일페이지일 경우) -->
-      <div v-if="formData.type === 'image'" class="admin--form-group">
-        <label class="admin--form-label">링크 URL</label>
-        <input
-          v-model="formData.link_url"
-          type="url"
-          class="admin--form-input"
-          placeholder="https://example.com"
-        />
-      </div>
-
-      <!-- 링크 타겟 (단일페이지일 경우) -->
-      <div v-if="formData.type === 'image'" class="admin--form-group">
-        <label class="admin--form-label">링크 열기 방식</label>
-        <div class="admin--radio-group">
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.link_target"
-              type="radio"
-              value="_blank"
-              name="link_target"
-            />
-            <span>새창</span>
-          </label>
-          <label class="admin--radio-label">
-            <input
-              v-model="formData.link_target"
-              type="radio"
-              value="_self"
-              name="link_target"
-            />
-            <span>현재창</span>
-          </label>
-        </div>
-      </div>
-
-      <!-- 버튼 영역 -->
-      <div class="admin--form-actions">
-        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
-          {{ isSaving ? "저장 중..." : "확인" }}
-        </button>
-        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
-          목록
-        </button>
-      </div>
-
-      <!-- 성공/에러 메시지 -->
-      <div v-if="successMessage" class="admin--alert admin--alert-success">
-        {{ successMessage }}
-      </div>
-      <div v-if="errorMessage" class="admin--alert admin--alert-error">
-        {{ errorMessage }}
-      </div>
-    </form>
-  </div>
-</template>
-
-<script setup>
-  import { ref, onMounted } from "vue";
-  import { useRoute, useRouter } from "vue-router";
-  import SunEditor from "~/components/admin/SunEditor.vue";
-  import DatePicker from "~/components/admin/DatePicker.vue";
-
-  definePageMeta({
-    layout: "admin",
-    middleware: ["auth"],
-  });
-
-  const route = useRoute();
-  const router = useRouter();
-  const { get, put, upload } = useApi();
-  const { getImageUrl } = useImage();
-
-  const isLoading = ref(true);
-  const isSaving = ref(false);
-  const successMessage = ref("");
-  const errorMessage = ref("");
-  const imagePreview = ref(null);
-  const imageFile = ref(null);
-
-  const formData = ref({
-    type: "html",
-    site: "ford",
-    title: "",
-    start_date: "",
-    end_date: "",
-    start_period: "AM",
-    start_hour: null,
-    start_minute: null,
-    end_period: "AM",
-    end_hour: null,
-    end_minute: null,
-    width: 800,
-    height: 600,
-    position_top: 100,
-    position_left: 100,
-    cookie_setting: "none",
-    content: "",
-    image_url: "",
-    link_url: "",
-    link_target: "_blank",
-  });
-
-  // 12시간제 ↔ 24시간 "HH:MM:SS" 변환
-  const toTime24 = (period, hour, minute) => {
-    if (hour === null || hour === undefined || hour === "") return null;
-    let h = Number(hour);
-    const m = Number(minute || 0);
-    if (isNaN(h) || h < 1 || h > 12) return null;
-    if (period === "PM" && h < 12) h += 12;
-    if (period === "AM" && h === 12) h = 0;
-    return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`;
-  };
-
-  const fromTime24 = (timeStr) => {
-    if (!timeStr) return { period: "AM", hour: null, minute: null };
-    const [hStr, mStr] = String(timeStr).split(":");
-    let h = Number(hStr);
-    const m = Number(mStr || 0);
-    if (isNaN(h)) return { period: "AM", hour: null, minute: null };
-    const period = h >= 12 ? "PM" : "AM";
-    const hour12 = h % 12 === 0 ? 12 : h % 12;
-    return { period, hour: hour12, minute: m };
-  };
-
-  // 데이터 로드
-  const loadPopup = async () => {
-    isLoading.value = true;
-
-    const id = route.params.id;
-    const { data, error } = await get(`/basic/popup/${id}`);
-
-    console.log("[Popup Edit] 데이터 로드 응답:", { data, error });
-
-    // API 응답: { success: true, data: {...}, message }
-    if (data?.success && data?.data) {
-      const popup = data.data;
-      const startParts = fromTime24(popup.start_time);
-      const endParts = fromTime24(popup.end_time);
-      formData.value = {
-        type: popup.type || "html",
-        site: popup.site || "ford",
-        title: popup.title || "",
-        start_date: popup.start_date || "",
-        end_date: popup.end_date || "",
-        start_period: startParts.period,
-        start_hour: startParts.hour,
-        start_minute: startParts.minute,
-        end_period: endParts.period,
-        end_hour: endParts.hour,
-        end_minute: endParts.minute,
-        width: popup.width || 800,
-        height: popup.height || 600,
-        position_top: popup.position_top || 100,
-        position_left: popup.position_left || 100,
-        cookie_setting: popup.cookie_setting || "none",
-        content: popup.content || "",
-        image_url: popup.image_url || "",
-        link_url: popup.link_url || "",
-        link_target: popup.link_target || "_blank",
-      };
-
-      // 이미지 미리보기는 새 이미지 업로드시에만 사용
-      // 기존 이미지는 formData.image_url을 통해 getImageUrl()로 표시
-      imagePreview.value = null;
-      console.log("[Popup Edit] 이미지 URL:", popup.image_url);
-
-      console.log("[Popup Edit] 데이터 로드 성공:", formData.value);
-    } else {
-      console.log("[Popup Edit] 데이터 로드 실패");
-    }
-
-    isLoading.value = false;
-  };
-
-  // 이미지 업로드
-  const handleImageUpload = (event) => {
-    const file = event.target.files[0];
-    if (!file) return;
-
-    if (!file.type.startsWith("image/")) {
-      alert("이미지 파일만 업로드 가능합니다.");
-      return;
-    }
-
-    imageFile.value = file;
-
-    // 미리보기
-    const reader = new FileReader();
-    reader.onload = (e) => {
-      imagePreview.value = e.target.result;
-    };
-    reader.readAsDataURL(file);
-  };
-
-  // 이미지 삭제
-  const removeImage = () => {
-    imagePreview.value = null;
-    imageFile.value = null;
-    formData.value.image_url = "";
-  };
-
-  // 폼 제출
-  const handleSubmit = async () => {
-    successMessage.value = "";
-    errorMessage.value = "";
-
-    // 유효성 검사
-    if (!formData.value.title) {
-      errorMessage.value = "제목을 입력하세요.";
-      return;
-    }
-
-    if (!formData.value.start_date || !formData.value.end_date) {
-      errorMessage.value = "시작일과 종료일을 선택하세요.";
-      return;
-    }
-
-    if (formData.value.type === "html" && !formData.value.content) {
-      errorMessage.value = "출력내용을 입력하세요.";
-      return;
-    }
-
-    if (
-      formData.value.type === "image" &&
-      !imageFile.value &&
-      !formData.value.image_url
-    ) {
-      errorMessage.value = "이미지를 첨부하세요.";
-      return;
-    }
-
-    isSaving.value = true;
-
-    try {
-      let imageUrl = formData.value.image_url;
-
-      // 이미지 업로드 (새로운 이미지가 있는 경우)
-      if (formData.value.type === "image" && imageFile.value) {
-        const formDataImage = new FormData();
-        formDataImage.append("file", imageFile.value);
-
-        const { data: uploadData, error: uploadError } = await upload(
-          "/upload/image",
-          formDataImage
-        );
-
-        console.log("[Popup Edit] 이미지 업로드 응답:", { uploadData, uploadError });
-
-        if (uploadError || !uploadData?.success) {
-          errorMessage.value = uploadError?.message || "이미지 업로드에 실패했습니다.";
-          isSaving.value = false;
-          return;
-        }
-
-        imageUrl = uploadData.data?.url || uploadData.data;
-      }
-
-      // 팝업 수정
-      const submitData = {
-        ...formData.value,
-        image_url: imageUrl,
-        start_time: toTime24(formData.value.start_period, formData.value.start_hour, formData.value.start_minute),
-        end_time: toTime24(formData.value.end_period, formData.value.end_hour, formData.value.end_minute),
-      };
-
-      const id = route.params.id;
-      const { data, error } = await put(`/basic/popup/${id}`, submitData);
-
-      if (error || !data?.success) {
-        errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
-      } else {
-        successMessage.value = data.message || "팝업이 수정되었습니다.";
-        setTimeout(() => {
-          router.push("/site-manager/basic/popup");
-        }, 1000);
-      }
-    } catch (error) {
-      errorMessage.value = "서버 오류가 발생했습니다.";
-      console.error("Save error:", error);
-    } finally {
-      isSaving.value = false;
-    }
-  };
-
-  // 목록으로 이동
-  const goToList = () => {
-    router.push("/site-manager/basic/popup");
-  };
-
-  onMounted(() => {
-    loadPopup();
-  });
-</script>

+ 0 - 319
app/pages/site-manager/basic/popup/index.vue

@@ -1,319 +0,0 @@
-<template>
-  <div class="admin--popup-list">
-    <!-- 검색 영역 -->
-    <div class="admin--search-box">
-      <div class="admin--search-form">
-        <select v-model="searchType" class="admin--form-select admin--search-select">
-          <option value="title">제목</option>
-          <option value="status">상태</option>
-        </select>
-        <input
-          v-model="searchKeyword"
-          type="text"
-          class="admin--form-input admin--search-input"
-          placeholder="검색어를 입력하세요"
-          @keyup.enter="handleSearch"
-        >
-        <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">
-          검색
-        </button>
-        <button class="admin--btn-small admin--btn-small-secondary" @click="handleReset">
-          초기화
-        </button>
-      </div>
-      <div class="admin--search-actions">
-        <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">
-          + 팝업 등록
-        </button>
-      </div>
-    </div>
-
-    <!-- 테이블 -->
-    <div class="admin--table-wrapper">
-      <table class="admin--table">
-        <thead>
-          <tr>
-            <th>NO</th>
-            <th>출력형태</th>
-            <th>제목</th>
-            <th>시작일</th>
-            <th>종료일</th>
-            <th>상태</th>
-            <th>관리</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr v-if="!popups || popups.length === 0">
-            <td colspan="7" class="admin--table-empty">
-              등록된 팝업이 없습니다.
-            </td>
-          </tr>
-          <tr v-else v-for="(popup, index) in popups" :key="popup.id">
-            <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
-            <td>
-              <span class="admin--badge" :class="`admin--badge-${popup.type}`">
-                {{ popup.type === 'html' ? 'HTML' : '단일페이지' }}
-              </span>
-            </td>
-            <td class="admin--table-title">{{ popup.title }}</td>
-            <td>{{ formatDateTime(popup.start_date, popup.start_time) }}</td>
-            <td>{{ formatDateTime(popup.end_date, popup.end_time) }}</td>
-            <td>
-              <span class="admin--badge" :class="getStatusClass(popup)">
-                {{ getStatusText(popup) }}
-              </span>
-            </td>
-            <td>
-              <div class="admin--table-actions">
-                <button
-                  class="admin--btn-small admin--btn-small-primary"
-                  @click="goToEdit(popup.id)"
-                >
-                  수정
-                </button>
-                <button
-                  class="admin--btn-small admin--btn-small-danger"
-                  @click="handleDelete(popup.id)"
-                >
-                  삭제
-                </button>
-              </div>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    </div>
-
-    <!-- 페이지네이션 -->
-    <div v-if="totalPages > 1" class="admin--pagination">
-      <button
-        class="admin--pagination-btn"
-        :disabled="currentPage === 1"
-        @click="changePage(1)"
-        title="처음"
-      >
-        ⏮
-      </button>
-      <button
-        class="admin--pagination-btn"
-        :disabled="currentPage === 1"
-        @click="changePage(currentPage - 1)"
-        title="이전"
-      >
-        ◀
-      </button>
-      <button
-        v-for="page in visiblePages"
-        :key="page"
-        class="admin--pagination-btn"
-        :class="{ 'is-active': page === currentPage }"
-        @click="changePage(page)"
-      >
-        {{ page }}
-      </button>
-      <button
-        class="admin--pagination-btn"
-        :disabled="currentPage === totalPages"
-        @click="changePage(currentPage + 1)"
-        title="다음"
-      >
-        ▶
-      </button>
-      <button
-        class="admin--pagination-btn"
-        :disabled="currentPage === totalPages"
-        @click="changePage(totalPages)"
-        title="끝"
-      >
-        ⏭
-      </button>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref, computed, onMounted } from 'vue'
-import { useRouter } from 'vue-router'
-
-definePageMeta({
-  layout: 'admin',
-  middleware: ['auth']
-})
-
-const router = useRouter()
-const { get, del } = useApi()
-const popups = ref([])
-const searchType = ref('title')
-const searchKeyword = ref('')
-const currentPage = ref(1)
-const perPage = ref(10)
-const totalCount = ref(0)
-const totalPages = ref(0)
-
-// 보이는 페이지 번호 계산
-const visiblePages = computed(() => {
-  const pages = []
-  const maxVisible = 5
-  let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2))
-  let end = Math.min(totalPages.value, start + maxVisible - 1)
-
-  if (end - start < maxVisible - 1) {
-    start = Math.max(1, end - maxVisible + 1)
-  }
-
-  for (let i = start; i <= end; i++) {
-    pages.push(i)
-  }
-
-  return pages
-})
-
-// 데이터 로드
-const loadPopups = async () => {
-  const params = {
-    page: currentPage.value,
-    per_page: perPage.value
-  }
-
-  if (searchKeyword.value) {
-    params.search_type = searchType.value
-    params.search_keyword = searchKeyword.value
-  }
-
-  const { data, error } = await get('/basic/popup', { params })
-
-  console.log('[Popup List] API 응답:', { data, error })
-
-  // API 응답: { success: true, data: { items, total, page }, message }
-  if (data?.success && data?.data) {
-    popups.value = data.data.items || []
-    totalCount.value = data.data.total || 0
-    totalPages.value = Math.ceil(totalCount.value / perPage.value)
-    console.log('[Popup List] 로드 성공:', {
-      items: popups.value.length,
-      total: totalCount.value,
-      pages: totalPages.value
-    })
-  } else {
-    console.log('[Popup List] 데이터 없음 또는 에러')
-  }
-}
-
-// 검색
-const handleSearch = () => {
-  currentPage.value = 1
-  loadPopups()
-}
-
-// 초기화
-const handleReset = () => {
-  searchType.value = 'title'
-  searchKeyword.value = ''
-  currentPage.value = 1
-  loadPopups()
-}
-
-// 페이지 변경
-const changePage = (page) => {
-  if (page < 1 || page > totalPages.value) return
-  currentPage.value = page
-  loadPopups()
-  window.scrollTo({ top: 0, behavior: 'smooth' })
-}
-
-// 등록 페이지로 이동
-const goToCreate = () => {
-  router.push('/site-manager/basic/popup/create')
-}
-
-// 수정 페이지로 이동
-const goToEdit = (id) => {
-  router.push(`/site-manager/basic/popup/edit/${id}`)
-}
-
-// 삭제
-const handleDelete = async (id) => {
-  if (!confirm('정말 삭제하시겠습니까?')) return
-
-  const { data, error } = await del(`/basic/popup/${id}`)
-
-  console.log('[Popup Delete] API 응답:', { data, error })
-
-  if (error || !data?.success) {
-    alert(error?.message || '삭제에 실패했습니다.')
-  } else {
-    alert(data.message || '삭제되었습니다.')
-    loadPopups()
-  }
-}
-
-// 날짜 포맷
-const formatDate = (dateString) => {
-  if (!dateString) return '-'
-  const date = new Date(dateString)
-  return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`
-}
-
-// 날짜+시간 포맷 ("HH:MM:SS" → "오전/오후 H:MM")
-const formatDateTime = (dateString, timeString) => {
-  const d = formatDate(dateString)
-  if (!timeString) return d
-  const [hStr, mStr] = String(timeString).split(':')
-  let h = Number(hStr)
-  const m = Number(mStr || 0)
-  if (isNaN(h)) return d
-  const period = h >= 12 ? '오후' : '오전'
-  const hour12 = h % 12 === 0 ? 12 : h % 12
-  return `${d} ${period} ${hour12}:${String(m).padStart(2, '0')}`
-}
-
-// 팝업 시작/종료 Date 계산 (시간 null 처리 포함)
-const buildStart = (popup) => new Date(`${popup.start_date}T${popup.start_time || '00:00:00'}`)
-const buildEnd = (popup) => new Date(`${popup.end_date}T${popup.end_time || '23:59:59'}`)
-
-// 상태 클래스
-const getStatusClass = (popup) => {
-  const now = new Date()
-  const start = buildStart(popup)
-  const end = buildEnd(popup)
-
-  if (now < start) return 'admin--badge-scheduled'
-  if (now > end) return 'admin--badge-ended'
-  return 'admin--badge-active'
-}
-
-// 상태 텍스트
-const getStatusText = (popup) => {
-  const now = new Date()
-  const start = buildStart(popup)
-  const end = buildEnd(popup)
-
-  if (now < start) return '예정'
-  if (now > end) return '종료'
-  return '진행중'
-}
-
-onMounted(() => {
-  loadPopups()
-})
-</script>
-
-<style scoped>
-.admin--search-actions .admin--btn-primary {
-  background: var(--admin-accent-primary);
-  color: white;
-  border-color: var(--admin-accent-primary);
-  font-weight: 500;
-  padding: 8px 18px;
-  font-size: 13px;
-  border-radius: 8px;
-  transition: all 0.3s ease;
-}
-
-.admin--search-actions .admin--btn-primary:hover {
-  background: var(--admin-accent-hover);
-  border-color: var(--admin-accent-hover);
-  transform: translateY(-1px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-}
-</style>

+ 0 - 324
app/pages/site-manager/basic/site-info.vue

@@ -1,324 +0,0 @@
-<template>
-  <div class="admin--site-info">
-    <div v-if="isLoading" class="admin--loading">
-      데이터를 불러오는 중...
-    </div>
-
-    <form v-else @submit.prevent="handleSubmit" class="admin--form">
-      <!-- 사이트명 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">사이트명 <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.site_name"
-          type="text"
-          class="admin--form-input"
-          placeholder="사이트명을 입력하세요"
-          required
-        >
-      </div>
-
-      <!-- 사이트 URL -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">사이트 URL <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.site_url"
-          type="url"
-          class="admin--form-input"
-          placeholder="https://example.com"
-          required
-        >
-      </div>
-
-      <!-- 대표명 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">대표명 <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.ceo_name"
-          type="text"
-          class="admin--form-input"
-          placeholder="대표명을 입력하세요"
-          required
-        >
-      </div>
-
-      <!-- 대표 이메일 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">대표 이메일 <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.ceo_email"
-          type="email"
-          class="admin--form-input"
-          placeholder="email@example.com"
-          required
-        >
-      </div>
-
-      <!-- 전화번호 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">전화번호 <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.phone"
-          type="tel"
-          class="admin--form-input"
-          placeholder="02-1234-5678"
-          required
-        >
-      </div>
-
-      <!-- FAX번호 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">FAX번호</label>
-        <input
-          v-model="formData.fax"
-          type="tel"
-          class="admin--form-input"
-          placeholder="02-1234-5679"
-        >
-      </div>
-
-      <!-- 회사주소 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">회사주소 <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.address"
-          type="text"
-          class="admin--form-input"
-          placeholder="회사 주소를 입력하세요"
-          required
-        >
-      </div>
-
-      <!-- SMS발신번호 (다중) -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">SMS발신번호</label>
-        <div class="admin--multi-input-wrapper">
-          <div
-            v-for="(item, index) in formData.sms_sender_numbers"
-            :key="index"
-            class="admin--multi-input-item"
-          >
-            <div class="admin--sender-row">
-              <input
-                v-model="formData.sms_sender_numbers[index].name"
-                type="text"
-                class="admin--form-input"
-                placeholder="지점명"
-                style="flex: 1; margin-right: 10px;"
-              >
-              <input
-                v-model="formData.sms_sender_numbers[index].number"
-                type="tel"
-                class="admin--form-input"
-                placeholder="010-1234-5678"
-                style="flex: 1;"
-              >
-            </div>
-            <button
-              v-if="formData.sms_sender_numbers.length > 1"
-              type="button"
-              class="admin--btn-remove"
-              @click="removeSenderNumber(index)"
-            >
-              삭제
-            </button>
-          </div>
-          <button
-            type="button"
-            class="admin--btn-add"
-            @click="addSenderNumber"
-          >
-            + 발신번호 추가
-          </button>
-        </div>
-      </div>
-
-      <!-- SMS수신번호 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label">SMS수신번호</label>
-        <input
-          v-model="formData.sms_receiver_number"
-          type="tel"
-          class="admin--form-input"
-          placeholder="010-9876-5432"
-        >
-      </div>
-
-      <!-- 버튼 영역 -->
-      <div class="admin--form-actions">
-        <button
-          type="submit"
-          class="admin--btn admin--btn-primary"
-          :disabled="isSaving"
-        >
-          {{ isSaving ? '저장 중...' : '저장' }}
-        </button>
-      </div>
-
-      <!-- 성공/에러 메시지 -->
-      <div v-if="successMessage" class="admin--alert admin--alert-success">
-        {{ successMessage }}
-      </div>
-      <div v-if="errorMessage" class="admin--alert admin--alert-error">
-        {{ errorMessage }}
-      </div>
-    </form>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-
-definePageMeta({
-  layout: 'admin',
-  middleware: ['auth']
-})
-
-const { get, post } = useApi()
-
-const isLoading = ref(true)
-const isSaving = ref(false)
-const successMessage = ref('')
-const errorMessage = ref('')
-const siteInfoId = ref(null)
-
-const formData = ref({
-  site_name: '',
-  site_url: '',
-  ceo_name: '',
-  ceo_email: '',
-  phone: '',
-  fax: '',
-  address: '',
-  sms_sender_numbers: [{ name: '', number: '' }],
-  sms_receiver_number: ''
-})
-
-// 데이터 로드
-const loadSiteInfo = async () => {
-  isLoading.value = true
-
-  const { data, error } = await get('/basic/site-info')
-
-  console.log('[SiteInfo] API 응답:', { data, error })
-
-  // API 응답: { success: true, data: {...}, message }
-  if (data?.success && data?.data) {
-    const siteData = data.data
-    siteInfoId.value = siteData.id
-
-    // SMS 발신번호 데이터 변환 (기존 문자열 배열 → 객체 배열)
-    let senderNumbers = [{ name: '', number: '' }]
-    if (siteData.sms_sender_numbers && siteData.sms_sender_numbers.length > 0) {
-      senderNumbers = siteData.sms_sender_numbers.map(item => {
-        // 이미 객체 형태인 경우
-        if (typeof item === 'object' && item.name !== undefined) {
-          return item
-        }
-        // 문자열인 경우 (기존 데이터)
-        return { name: '', number: item }
-      })
-    }
-
-    formData.value = {
-      site_name: siteData.site_name || '',
-      site_url: siteData.site_url || '',
-      ceo_name: siteData.ceo_name || '',
-      ceo_email: siteData.ceo_email || '',
-      phone: siteData.phone || '',
-      fax: siteData.fax || '',
-      address: siteData.address || '',
-      sms_sender_numbers: senderNumbers,
-      sms_receiver_number: siteData.sms_receiver_number || ''
-    }
-  }
-
-  isLoading.value = false
-}
-
-// 발신번호 추가
-const addSenderNumber = () => {
-  formData.value.sms_sender_numbers.push({ name: '', number: '' })
-}
-
-// 발신번호 삭제
-const removeSenderNumber = (index) => {
-  formData.value.sms_sender_numbers.splice(index, 1)
-}
-
-// 폼 제출
-const handleSubmit = async () => {
-  successMessage.value = ''
-  errorMessage.value = ''
-  isSaving.value = true
-
-  try {
-    // 빈 발신번호 제거 (지점명 또는 번호가 비어있지 않은 것만)
-    const cleanedSenderNumbers = formData.value.sms_sender_numbers.filter(
-      sender => (sender.name && sender.name.trim() !== '') || (sender.number && sender.number.trim() !== '')
-    )
-
-    const submitData = {
-      ...formData.value,
-      sms_sender_numbers: cleanedSenderNumbers
-    }
-
-    // POST로 통일 (등록/수정 모두)
-    const { data, error } = await post('/basic/site-info', submitData)
-
-    console.log('[SiteInfo] 저장 응답:', { data, error })
-
-    if (error) {
-      errorMessage.value = error.message || '저장에 실패했습니다.'
-    } else if (data?.success) {
-      successMessage.value = data.message || '사이트 정보가 저장되었습니다.'
-
-      // 저장된 데이터로 업데이트
-      if (data.data) {
-        const siteData = data.data
-        siteInfoId.value = siteData.id
-
-        // SMS 발신번호 데이터 변환 (기존 문자열 배열 → 객체 배열)
-        let senderNumbers = [{ name: '', number: '' }]
-        if (siteData.sms_sender_numbers && siteData.sms_sender_numbers.length > 0) {
-          senderNumbers = siteData.sms_sender_numbers.map(item => {
-            // 이미 객체 형태인 경우
-            if (typeof item === 'object' && item.name !== undefined) {
-              return item
-            }
-            // 문자열인 경우 (기존 데이터)
-            return { name: '', number: item }
-          })
-        }
-
-        formData.value = {
-          site_name: siteData.site_name || '',
-          site_url: siteData.site_url || '',
-          ceo_name: siteData.ceo_name || '',
-          ceo_email: siteData.ceo_email || '',
-          phone: siteData.phone || '',
-          fax: siteData.fax || '',
-          address: siteData.address || '',
-          sms_sender_numbers: senderNumbers,
-          sms_receiver_number: siteData.sms_receiver_number || ''
-        }
-      }
-
-      // 3초 후 메시지 자동 제거
-      setTimeout(() => {
-        successMessage.value = ''
-      }, 3000)
-    } else {
-      errorMessage.value = '저장에 실패했습니다.'
-    }
-  } catch (error) {
-    errorMessage.value = '서버 오류가 발생했습니다.'
-    console.error('Save error:', error)
-  } finally {
-    isSaving.value = false
-  }
-}
-
-onMounted(() => {
-  loadSiteInfo()
-})
-</script>

+ 0 - 202
app/pages/site-manager/branch/create.vue

@@ -1,202 +0,0 @@
-<template>
-  <div class="admin--branch-form">
-    <form @submit.prevent="handleSubmit" class="admin--form">
-      <!-- 지점명 -->
-      <div class="admin--form-group">
-        <label class="admin--form-label"
-          >딜러사 명 <span class="admin--required">*</span></label
-        >
-        <input
-          v-model="formData.name"
-          type="text"
-          class="admin--form-input"
-          placeholder="지점명을 입력하세요"
-          required
-        />
-      </div>
-
-      <!-- 대표번호 -->
-      <!-- <div class="admin--form-group">
-        <label class="admin--form-label">대표번호 <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.phone"
-          type="tel"
-          class="admin--form-input"
-          placeholder="02-1234-5678"
-          required
-        >
-      </div> -->
-
-      <!-- 주소 -->
-      <!-- <div class="admin--form-group">
-        <label class="admin--form-label">주소 <span class="admin--required">*</span></label>
-        <input
-          v-model="formData.address"
-          type="text"
-          class="admin--form-input"
-          placeholder="주소를 입력하세요"
-          required
-        >
-      </div> -->
-
-      <!-- 상세주소 -->
-      <!-- <div class="admin--form-group">
-        <label class="admin--form-label">상세주소</label>
-        <input
-          v-model="formData.detail_address"
-          type="text"
-          class="admin--form-input"
-          placeholder="상세주소를 입력하세요"
-        >
-      </div> -->
-
-      <!-- 위도/경도 -->
-      <!-- <div class="admin--form-group">
-        <label class="admin--form-label">위치 좌표</label>
-        <div class="admin--coordinate-group">
-          <div class="admin--coordinate-item">
-            <label>위도</label>
-            <input
-              v-model.number="formData.latitude"
-              type="number"
-              step="any"
-              class="admin--form-input"
-              placeholder="37.5665"
-            >
-          </div>
-          <div class="admin--coordinate-item">
-            <label>경도</label>
-            <input
-              v-model.number="formData.longitude"
-              type="number"
-              step="any"
-              class="admin--form-input"
-              placeholder="126.9780"
-            >
-          </div>
-        </div>
-      </div> -->
-
-      <!-- 영업시간 -->
-      <!-- <div class="admin--form-group">
-        <label class="admin--form-label">영업시간</label>
-        <textarea
-          v-model="formData.business_hours"
-          class="admin--form-textarea"
-          rows="3"
-          placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
-        ></textarea>
-      </div> -->
-
-      <!-- 버튼 영역 -->
-      <div class="admin--form-actions">
-        <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
-          {{ isSaving ? "저장 중..." : "확인" }}
-        </button>
-        <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
-          목록
-        </button>
-      </div>
-
-      <!-- 성공/에러 메시지 -->
-      <div v-if="successMessage" class="admin--alert admin--alert-success">
-        {{ successMessage }}
-      </div>
-      <div v-if="errorMessage" class="admin--alert admin--alert-error">
-        {{ errorMessage }}
-      </div>
-    </form>
-  </div>
-</template>
-
-<script setup>
-  import { ref } from "vue";
-  import { useRouter } from "vue-router";
-
-  definePageMeta({
-    layout: "admin",
-    middleware: ["auth"],
-  });
-
-  const router = useRouter();
-  const { post } = useApi();
-
-  const isSaving = ref(false);
-  const successMessage = ref("");
-  const errorMessage = ref("");
-
-  const formData = ref({
-    name: "",
-    // phone: "",
-    // address: "",
-    // detail_address: "",
-    //latitude: null,
-    //longitude: null,
-    //business_hours: "",
-  });
-
-  // 폼 제출
-  const handleSubmit = async () => {
-    successMessage.value = "";
-    errorMessage.value = "";
-
-    // 유효성 검사
-    if (!formData.value.name) {
-      errorMessage.value = "딜러사 명을 입력하세요.";
-      return;
-    }
-
-    // if (!formData.value.phone) {
-    //   errorMessage.value = "대표번호를 입력하세요.";
-    //   return;
-    // }
-
-    // if (!formData.value.address) {
-    //   errorMessage.value = "주소를 입력하세요.";
-    //   return;
-    // }
-
-    isSaving.value = true;
-
-    try {
-      const { data, error } = await post("/branch", formData.value);
-
-      if (error || !data?.success) {
-        errorMessage.value = error?.message || data?.message || "등록에 실패했습니다.";
-      } else {
-        successMessage.value = data.message || "지점이 등록되었습니다.";
-        setTimeout(() => {
-          router.push("/site-manager/branch/list");
-        }, 1000);
-      }
-    } catch (error) {
-      errorMessage.value = "서버 오류가 발생했습니다.";
-      console.error("Save error:", error);
-    } finally {
-      isSaving.value = false;
-    }
-  };
-
-  // 목록으로 이동
-  const goToList = () => {
-    router.push("/site-manager/branch/list");
-  };
-</script>
-
-<style scoped>
-  .admin--coordinate-group {
-    display: flex;
-    gap: 16px;
-  }
-
-  .admin--coordinate-item {
-    flex: 1;
-  }
-
-  .admin--coordinate-item label {
-    display: block;
-    margin-bottom: 8px;
-    font-size: 14px;
-    color: #666;
-  }
-</style>

+ 143 - 0
app/pages/site-manager/field/create.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="">
+    <form @submit.prevent="handleSubmit" class="admin--form">
+      <!-- 낚시분야 -->
+      <table class="admin--form--table">
+        <colgroup>
+          <col style="width: 120px;">
+          <col>
+        </colgroup>
+        <tbody>
+          <tr>
+            <th>
+              <div>
+                분야 <span class="admin--required">*</span>
+              </div>
+            </th>
+            <td>
+              <div class="input--wrap">
+                <input
+                v-model="formData.name"
+                type="text"
+                class="admin--form-input"
+                placeholder=""
+                required
+                />
+                <p>낚시 종류 분류명. 1~30자 이내. 중복 불가</p>
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <th>
+              <div>
+                가중치 <span class="admin--required">*</span>
+              </div>
+            </th>
+            <td>
+              <div class="input--wrap">
+                <input
+                v-model="formData.weight"
+                type="text"
+                class="admin--form-input"
+                placeholder=""
+                required
+                />
+                <p>0.0 ~ 1.0 사이 소수점 1자리 (최대값 1.0 = 100% 확률). 물고기 잡을 때 아이템 지급 확률</p>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+
+      <div class="admin--info--box">
+        <h2>💡 가중치 안내</h2>
+        <ul>
+          <li>가중치 = 해당 분야에서 물고기 잡았을 때 아이템이 지급될 확률 (최대값 1.0 = 100%)</li>
+          <li>예: 0.8 = 80% 확률로 아이템 드롭. 분야별 난이도/희소성에 맞춰 신중히 설정</li>
+        </ul>
+      </div>
+
+      <!-- 버튼 영역 -->
+      <div class="admin--form-actions">
+        <button type="button" class="admin--btn" @click="goToList">
+          ← 목록으로
+        </button>
+        <!-- <button type="button" class="admin--btn admin--btn-red-border ml--auto" @click="">
+          삭제
+        </button> -->
+        <button type="submit" class="admin--btn admin--btn-red ml--auto" :disabled="isSaving">
+          {{ isSaving ? "저장 중..." : "저장" }}
+        </button>
+      </div>
+
+      <!-- 성공/에러 메시지 -->
+      <div v-if="successMessage" class="admin--alert admin--alert-success">
+        {{ successMessage }}
+      </div>
+      <div v-if="errorMessage" class="admin--alert admin--alert-error">
+        {{ errorMessage }}
+      </div>
+    </form>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from "vue";
+  import { useRouter } from "vue-router";
+
+  definePageMeta({
+    layout: "admin",
+    middleware: ["auth"],
+  });
+
+  const router = useRouter();
+  const { post } = useApi();
+
+  const isSaving = ref(false);
+  const successMessage = ref("");
+  const errorMessage = ref("");
+
+  const formData = ref({
+    name: "",
+  });
+
+  // 폼 제출
+  const handleSubmit = async () => {
+    successMessage.value = "";
+    errorMessage.value = "";
+
+    // 유효성 검사
+    if (!formData.value.name) {
+      errorMessage.value = "분야명을 입력하세요.";
+      return;
+    }
+    if (!formData.value.weight) {
+      errorMessage.value = "가중치를 입력하세요.";
+      return;
+    }
+    isSaving.value = true;
+
+    try {
+      const { data, error } = await post("/branch", formData.value);
+
+      if (error || !data?.success) {
+        errorMessage.value = error?.message || data?.message || "등록에 실패했습니다.";
+      } else {
+        successMessage.value = data.message || "낚시분야가 등록되었습니다.";
+        setTimeout(() => {
+          router.push("/site-manager/branch/list");
+        }, 1000);
+      }
+    } catch (error) {
+      errorMessage.value = "서버 오류가 발생했습니다.";
+      console.error("Save error:", error);
+    } finally {
+      isSaving.value = false;
+    }
+  };
+
+  // 목록으로 이동
+  const goToList = () => {
+    router.push("/site-manager/field/list");
+  };
+</script>

+ 0 - 0
app/pages/site-manager/branch/edit/[id].vue → app/pages/site-manager/field/edit/[id].vue


+ 22 - 70
app/pages/site-manager/branch/list.vue → app/pages/site-manager/field/list.vue

@@ -4,8 +4,8 @@
     <div class="admin--search-box">
       <div class="admin--search-form"></div>
       <div class="admin--search-actions">
-        <button class="admin--btn admin--btn-primary" @click="goToCreate">
-          + 지점 등록
+        <button class="admin--btn-add" @click="goToCreate">
+          + 새 분야 추가
         </button>
       </div>
     </div>
@@ -15,11 +15,11 @@
       <table class="admin--table">
         <thead>
           <tr>
-            <th>NO</th>
-            <th style="width: 70%">딜러사 명</th>
-            <!-- <th>대표번호</th>
-            <th>주소</th> -->
-            <th>상태</th>
+            <th></th>
+            <th style="">번호</th>
+            <th style="width: 70%">분야</th>
+            <th>가중치</th>
+            <th>등록일</th>
             <th>관리</th>
           </tr>
         </thead>
@@ -28,7 +28,7 @@
             <td colspan="6" class="admin--table-loading">데이터를 불러오는 중...</td>
           </tr>
           <tr v-else-if="!branches || branches.length === 0">
-            <td colspan="6" class="admin--table-empty">등록된 지점이 없습니다.</td>
+            <td colspan="6" class="admin--table-empty">등록된 낚시분야가 없습니다.</td>
           </tr>
           <tr v-else v-for="(branch, index) in branches" :key="branch.id">
             <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
@@ -201,7 +201,7 @@
       per_page: perPage.value,
     };
 
-    const { data, error } = await get("/branch/list", { params });
+    const { data, error } = await get("/field/list", { params });
 
     console.log("[BranchList] API 응답:", { data, error });
 
@@ -224,31 +224,31 @@
     window.scrollTo({ top: 0, behavior: "smooth" });
   };
 
-  // 지점 등록 페이지로 이동
+  // 낚시분야 등록 페이지로 이동
   const goToCreate = () => {
-    router.push("/site-manager/branch/create");
+    router.push("/site-manager/field/create");
   };
 
-  // 지점 수정 페이지로 이동
+  // 낚시분야 수정 페이지로 이동
   const goToEdit = (id) => {
-    router.push(`/site-manager/branch/edit/${id}`);
+    router.push(`/site-manager/field/edit/${id}`);
   };
 
-  // 지점 삭제
+  // 낚시분야 삭제
   const deleteBranch = (id) => {
     showConfirm(
       "정말 삭제하시겠습니까?",
       async () => {
-        const { data, error } = await del(`/branch/${id}`);
+        const { data, error } = await del(`/field/${id}`);
 
         if (error || !data?.success) {
           showAlert(error?.message || data?.message || "삭제에 실패했습니다.", "오류");
         } else {
-          showAlert(data.message || "지점이 삭제되었습니다.", "성공");
+          showAlert(data.message || "낚시분야가 삭제되었습니다.", "성공");
           loadBranches();
         }
       },
-      "지점 삭제"
+      "낚시분야 삭제"
     );
   };
 
@@ -258,12 +258,12 @@
     const statusText = currentStatus == 1 ? "비사용" : "사용";
 
     showConfirm(
-      `지점을 ${statusText} 상태로 변경하시겠습니까?`,
+      `낚시분야을 ${statusText} 상태로 변경하시겠습니까?`,
       async () => {
-        console.log("[toggleActive] API 호출:", `/branch/${id}/toggle-active`);
+        console.log("[toggleActive] API 호출:", `/field/${id}/toggle-active`);
 
         // POST 방식으로 변경 (PATCH 대신)
-        const { data, error } = await post(`/branch/${id}/toggle-active`);
+        const { data, error } = await post(`/field/${id}/toggle-active`);
 
         console.log("[toggleActive] API 응답:", { data, error });
 
@@ -274,7 +274,7 @@
             "오류"
           );
         } else {
-          showAlert(data.message || "지점 상태가 변경되었습니다.", "성공");
+          showAlert(data.message || "낚시분야 상태가 변경되었습니다.", "성공");
           loadBranches();
         }
       },
@@ -285,52 +285,4 @@
   onMounted(() => {
     loadBranches();
   });
-</script>
-
-<style scoped>
-  .admin--search-actions .admin--btn-primary {
-    background: var(--admin-accent-primary);
-    color: white;
-    border-color: var(--admin-accent-primary);
-    font-weight: 500;
-    padding: 8px 18px;
-    font-size: 13px;
-    border-radius: 8px;
-    transition: all 0.3s ease;
-  }
-
-  .admin--search-actions .admin--btn-primary:hover {
-    background: var(--admin-accent-hover);
-    border-color: var(--admin-accent-hover);
-    transform: translateY(-1px);
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-  }
-
-  .admin--toggle-btn {
-    padding: 6px 16px;
-    font-size: 12px;
-    border-radius: 20px;
-    border: 1px solid #ddd;
-    background: #f5f5f5;
-    color: #666;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    font-weight: 500;
-  }
-
-  .admin--toggle-btn:hover {
-    border-color: #bbb;
-    background: #e8e8e8;
-  }
-
-  .admin--toggle-btn.is-active {
-    background: var(--admin-accent-primary);
-    color: white;
-    border-color: var(--admin-accent-primary);
-  }
-
-  .admin--toggle-btn.is-active:hover {
-    background: var(--admin-accent-hover);
-    border-color: var(--admin-accent-hover);
-  }
-</style>
+</script>

+ 0 - 0
app/pages/site-manager/branch/manager/create.vue → app/pages/site-manager/field/manager/create.vue


+ 0 - 0
app/pages/site-manager/branch/manager/edit/[id].vue → app/pages/site-manager/field/manager/edit/[id].vue


+ 0 - 0
app/pages/site-manager/branch/manager/index.vue → app/pages/site-manager/field/manager/index.vue


+ 0 - 0
app/pages/site-manager/showroom/create.vue → app/pages/site-manager/fishing/create.vue


+ 0 - 0
app/pages/site-manager/showroom/edit/[id].vue → app/pages/site-manager/fishing/edit/[id].vue


+ 0 - 0
app/pages/site-manager/showroom/list.vue → app/pages/site-manager/fishing/list.vue


+ 3 - 117
backend/app/Config/Routes.php

@@ -8,8 +8,10 @@ use CodeIgniter\Router\RouteCollection;
 $routes->get('/', 'Home::index');
 
 // API Routes - 그룹화 없이 직접 경로 지정
-// Authentication
+// Health check
 $routes->get('api/ping', 'Api\PingController::index');
+
+// Authentication
 $routes->post('api/auth/login', 'Api\AuthController::login');
 $routes->post('api/auth/logout', 'Api\AuthController::logout');
 $routes->get('api/auth/check', 'Api\AuthController::check');
@@ -17,116 +19,6 @@ $routes->get('api/auth/check', 'Api\AuthController::check');
 // Dashboard
 $routes->get('api/dashboard/stats', 'Api\DashboardController::getStats');
 
-// Site Info
-$routes->get('api/basic/site-info', 'Api\BasicController::getSiteInfo');
-$routes->post('api/basic/site-info', 'Api\BasicController::updateSiteInfo');
-
-// Popup Management
-$routes->get('api/popup/active', 'Api\PopupController::getActive'); // Public API
-$routes->get('api/basic/popup', 'Api\PopupController::index');
-$routes->get('api/basic/popup/(:num)', 'Api\PopupController::show/$1');
-$routes->post('api/basic/popup', 'Api\PopupController::create');
-$routes->put('api/basic/popup/(:num)', 'Api\PopupController::update/$1');
-$routes->delete('api/basic/popup/(:num)', 'Api\PopupController::delete/$1');
-
-// Branch List
-$routes->get('api/branch/public', 'Api\BranchController::publicList'); // Public API
-$routes->get('api/branch/public/(:num)', 'Api\BranchController::publicShow/$1'); // Public API
-$routes->get('api/branch/list', 'Api\BranchController::index');
-$routes->get('api/branch/(:num)', 'Api\BranchController::show/$1');
-$routes->post('api/branch', 'Api\BranchController::create');
-$routes->put('api/branch/(:num)', 'Api\BranchController::update/$1');
-$routes->delete('api/branch/(:num)', 'Api\BranchController::delete/$1');
-$routes->patch('api/branch/(:num)/toggle-active', 'Api\BranchController::toggleActive/$1');
-$routes->post('api/branch/(:num)/toggle-active', 'Api\BranchController::toggleActive/$1');
-
-// Showroom
-$routes->get('api/showroom/public', 'Api\ShowroomController::publicList'); // Public API
-$routes->get('api/showroom/public/(:num)', 'Api\ShowroomController::publicShow/$1'); // Public API
-$routes->get('api/showroom/list', 'Api\ShowroomController::index');
-$routes->get('api/showroom/(:num)', 'Api\ShowroomController::show/$1');
-$routes->post('api/showroom', 'Api\ShowroomController::create');
-$routes->put('api/showroom/(:num)', 'Api\ShowroomController::update/$1');
-$routes->delete('api/showroom/(:num)', 'Api\ShowroomController::delete/$1');
-$routes->patch('api/showroom/(:num)/toggle-active', 'Api\ShowroomController::toggleActive/$1');
-$routes->post('api/showroom/(:num)/toggle-active', 'Api\ShowroomController::toggleActive/$1');
-
-// Service Center
-$routes->get('api/service-center/public', 'Api\ServiceCenterController::publicList'); // Public API
-$routes->get('api/service-center/public/(:num)', 'Api\ServiceCenterController::publicShow/$1'); // Public API
-$routes->get('api/service-center/list', 'Api\ServiceCenterController::index');
-$routes->get('api/service-center/(:num)', 'Api\ServiceCenterController::show/$1');
-$routes->post('api/service-center', 'Api\ServiceCenterController::create');
-$routes->put('api/service-center/(:num)', 'Api\ServiceCenterController::update/$1');
-$routes->delete('api/service-center/(:num)', 'Api\ServiceCenterController::delete/$1');
-$routes->patch('api/service-center/(:num)/toggle-active', 'Api\ServiceCenterController::toggleActive/$1');
-$routes->post('api/service-center/(:num)/toggle-active', 'Api\ServiceCenterController::toggleActive/$1');
-
-// Branch Manager
-$routes->get('api/branch/manager/public', 'Api\BranchManagerController::publicList'); // Public API
-$routes->get('api/branch/manager/check-userid', 'Api\BranchManagerController::checkUserId'); // Check user_id availability
-$routes->get('api/branch/manager', 'Api\BranchManagerController::index');
-$routes->get('api/branch/manager/(:num)', 'Api\BranchManagerController::show/$1');
-$routes->post('api/branch/manager', 'Api\BranchManagerController::create');
-$routes->put('api/branch/manager/(:num)', 'Api\BranchManagerController::update/$1');
-$routes->delete('api/branch/manager/(:num)', 'Api\BranchManagerController::delete/$1');
-
-// Sales Staff
-$routes->get('api/staff/sales/public', 'Api\SalesStaffController::publicList'); // Public API
-$routes->get('api/staff/sales', 'Api\SalesStaffController::index');
-$routes->get('api/staff/sales/(:num)', 'Api\SalesStaffController::show/$1');
-$routes->post('api/staff/sales', 'Api\SalesStaffController::create');
-$routes->put('api/staff/sales/(:num)', 'Api\SalesStaffController::update/$1');
-$routes->delete('api/staff/sales/(:num)', 'Api\SalesStaffController::delete/$1');
-$routes->post('api/staff/sales/(:num)/toggle-active', 'Api\SalesStaffController::toggleActive/$1');
-
-// Advisor Staff
-$routes->get('api/staff/advisor', 'Api\AdvisorController::index');
-$routes->get('api/staff/advisor/(:num)', 'Api\AdvisorController::show/$1');
-$routes->post('api/staff/advisor', 'Api\AdvisorController::create');
-$routes->put('api/staff/advisor/(:num)', 'Api\AdvisorController::update/$1');
-$routes->delete('api/staff/advisor/(:num)', 'Api\AdvisorController::delete/$1');
-
-// Brochure Requests
-$routes->get('api/service/brochure', 'Api\BrochureController::index');
-$routes->put('api/service/brochure/(:num)/status', 'Api\BrochureController::updateStatus/$1');
-$routes->delete('api/service/brochure/(:num)', 'Api\BrochureController::delete/$1');
-$routes->get('api/service/brochure/excel', 'Api\BrochureController::exportExcel');
-
-// Event Board
-$routes->get('api/event/public', 'Api\EventController::publicList'); // Public API
-$routes->get('api/event/public/(:num)', 'Api\EventController::publicShow/$1'); // Public API
-$routes->get('api/board/event', 'Api\EventController::index');
-$routes->get('api/board/event/(:num)', 'Api\EventController::show/$1');
-$routes->post('api/board/event', 'Api\EventController::create');
-$routes->put('api/board/event/(:num)', 'Api\EventController::update/$1');
-$routes->delete('api/board/event/(:num)', 'Api\EventController::delete/$1');
-
-// News Board
-$routes->get('api/news/public', 'Api\NewsController::publicList'); // Public API
-$routes->get('api/news/public/(:num)', 'Api\NewsController::publicShow/$1'); // Public API
-$routes->get('api/board/news', 'Api\NewsController::index');
-$routes->get('api/board/news/(:num)', 'Api\NewsController::show/$1');
-$routes->post('api/board/news', 'Api\NewsController::create');
-$routes->put('api/board/news/(:num)', 'Api\NewsController::update/$1');
-$routes->delete('api/board/news/(:num)', 'Api\NewsController::delete/$1');
-
-// Notice Board
-$routes->get('api/notice/public', 'Api\NoticeController::publicList'); // Public API
-$routes->get('api/notice/public/(:num)', 'Api\NoticeController::publicShow/$1'); // Public API
-$routes->get('api/board/notice', 'Api\NoticeController::index');
-$routes->get('api/board/notice/(:num)', 'Api\NoticeController::show/$1');
-$routes->post('api/board/notice', 'Api\NoticeController::create');
-$routes->put('api/board/notice/(:num)', 'Api\NoticeController::update/$1');
-$routes->delete('api/board/notice/(:num)', 'Api\NoticeController::delete/$1');
-
-// IR Board
-$routes->get('api/board/ir', 'Api\IrController::index');
-$routes->get('api/board/ir/(:num)', 'Api\IrController::show/$1');
-$routes->post('api/board/ir', 'Api\IrController::create');
-$routes->put('api/board/ir/(:num)', 'Api\IrController::update/$1');
-$routes->delete('api/board/ir/(:num)', 'Api\IrController::delete/$1');
-
 // Admin Management
 $routes->get('api/admin/check-username', 'Api\AdminController::checkUsername'); // Check username availability
 $routes->get('api/admin', 'Api\AdminController::index');
@@ -140,9 +32,3 @@ $routes->post('api/admin/(:num)/unlock', 'Api\AdminController::unlockAccount/$1'
 // File Upload
 $routes->post('api/upload/file', 'Api\UploadController::uploadFile');
 $routes->post('api/upload/image', 'Api\UploadController::uploadImage');
-$routes->post('api/upload/staff-image', 'Api\UploadController::uploadStaffImage');
-$routes->post('api/upload/advisor-image', 'Api\UploadController::uploadAdvisorImage');
-$routes->post('api/upload/bmanager-image', 'Api\UploadController::uploadBManagerImage');
-$routes->post('api/upload/board-file', 'Api\UploadController::uploadBoardFile');
-$routes->post('api/upload/event-file', 'Api\UploadController::uploadEventFile');
-$routes->post('api/upload/news-file', 'Api\UploadController::uploadNewsFile');

+ 0 - 197
backend/app/Controllers/Api/AdvisorController.php

@@ -1,197 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class AdvisorController extends BaseApiController
-{
-    /**
-     * Get service center name from code
-     */
-    private function getServiceCenterName($code)
-    {
-        $centers = [
-            'CA' => '성수서비스센터',
-            'CB' => '수원서비스센터',
-            'CC' => '대전서비스센터',
-            'CD' => '광주서비스센터'
-        ];
-        return $centers[$code] ?? $code;
-    }
-
-    /**
-     * Get service center codes from search keyword
-     */
-    private function getServiceCenterCodes($keyword)
-    {
-        $centers = [
-            'CA' => '성수서비스센터',
-            'CB' => '수원서비스센터',
-            'CC' => '대전서비스센터',
-            'CD' => '광주서비스센터'
-        ];
-
-        $matchedCodes = [];
-        foreach ($centers as $code => $name) {
-            // 코드 또는 이름에 키워드가 포함되어 있으면 추가
-            if (stripos($code, $keyword) !== false || stripos($name, $keyword) !== false) {
-                $matchedCodes[] = $code;
-            }
-        }
-
-        return $matchedCodes;
-    }
-
-    /**
-     * Get advisor list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('advisors');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchKeyword) {
-            if ($searchType === 'name') {
-                $builder->like('name', $searchKeyword);
-            } elseif ($searchType === 'service_center') {
-                // 서비스센터 검색: 코드나 이름으로 검색
-                $matchedCodes = $this->getServiceCenterCodes($searchKeyword);
-                if (!empty($matchedCodes)) {
-                    $builder->whereIn('service_center', $matchedCodes);
-                } else {
-                    // 매칭되는 것이 없으면 결과 없음
-                    $builder->where('1=0');
-                }
-            } elseif ($searchType === 'all' || !$searchType) {
-                // 전체 검색: 이름, 서비스센터(코드+이름), 대표번호에서 검색
-                $matchedCodes = $this->getServiceCenterCodes($searchKeyword);
-
-                $builder->groupStart()
-                    ->like('name', $searchKeyword)
-                    ->orLike('main_phone', $searchKeyword);
-
-                // 서비스센터 코드 매칭이 있으면 추가
-                if (!empty($matchedCodes)) {
-                    $builder->orWhereIn('service_center', $matchedCodes);
-                }
-
-                $builder->groupEnd();
-            }
-        }
-
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Add service_center_name to each item
-        if (isset($result['items'])) {
-            foreach ($result['items'] as &$item) {
-                $item->service_center_name = $this->getServiceCenterName($item->service_center);
-            }
-        }
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single advisor
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('advisors');
-        $advisor = $builder->where('id', $id)->get()->getRow();
-
-        if (!$advisor) {
-            return $this->respondError('어드바이저를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Add service_center_name
-        $advisor->service_center_name = $this->getServiceCenterName($advisor->service_center);
-
-        return $this->respondSuccess($advisor);
-    }
-
-    /**
-     * Create advisor
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'service_center' => $json->service_center ?? '',
-            'name' => $json->name ?? '',
-            'main_phone' => $json->main_phone ?? '',
-            'direct_phone' => $json->direct_phone ?? '',
-            'photo_url' => $json->photo_url ?? '',
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('advisors');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '어드바이저가 등록되었습니다.');
-    }
-
-    /**
-     * Update advisor
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'service_center' => $json->service_center ?? '',
-            'name' => $json->name ?? '',
-            'main_phone' => $json->main_phone ?? '',
-            'direct_phone' => $json->direct_phone ?? '',
-            'photo_url' => $json->photo_url ?? '',
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('advisors');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '어드바이저가 수정되었습니다.');
-    }
-
-    /**
-     * Delete advisor
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('advisors');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '어드바이저가 삭제되었습니다.');
-    }
-}

+ 0 - 86
backend/app/Controllers/Api/BasicController.php

@@ -1,86 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class BasicController extends BaseApiController
-{
-    /**
-     * Get site info
-     */
-    public function getSiteInfo()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('site_info');
-        $siteInfo = $builder->get()->getRow();
-
-        if (!$siteInfo) {
-            // Return default structure if no data
-            return $this->respondSuccess([
-                'site_name' => '',
-                'site_url' => '',
-                'ceo_name' => '',
-                'ceo_email' => '',
-                'phone' => '',
-                'fax' => '',
-                'address' => '',
-                'sms_sender_numbers' => [],
-                'sms_receiver_number' => ''
-            ]);
-        }
-
-        // Parse JSON field
-        $siteInfo->sms_sender_numbers = json_decode($siteInfo->sms_sender_numbers ?? '[]');
-
-        return $this->respondSuccess($siteInfo);
-    }
-
-    /**
-     * Update site info
-     */
-    public function updateSiteInfo()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'site_name' => $json->site_name ?? '',
-            'site_url' => $json->site_url ?? '',
-            'ceo_name' => $json->ceo_name ?? '',
-            'ceo_email' => $json->ceo_email ?? '',
-            'phone' => $json->phone ?? '',
-            'fax' => $json->fax ?? '',
-            'address' => $json->address ?? '',
-            'sms_sender_numbers' => json_encode($json->sms_sender_numbers ?? []),
-            'sms_receiver_number' => $json->sms_receiver_number ?? '',
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('site_info');
-        $existing = $builder->get()->getRow();
-
-        if ($existing) {
-            $builder->where('id', $existing->id)->update($data);
-            $siteInfoId = $existing->id;
-        } else {
-            $data['created_at'] = date('Y-m-d H:i:s');
-            $builder->insert($data);
-            $siteInfoId = $this->getDB()->insertID();
-        }
-
-        // 저장된 데이터 조회하여 반환
-        $savedData = $builder->where('id', $siteInfoId)->get()->getRow();
-        $savedData->sms_sender_numbers = json_decode($savedData->sms_sender_numbers ?? '[]');
-
-        return $this->respondSuccess($savedData, '사이트 정보가 수정되었습니다.');
-    }
-}

+ 0 - 235
backend/app/Controllers/Api/BranchController.php

@@ -1,235 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class BranchController extends BaseApiController
-{
-    /**
-     * Get branch list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('branches');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-        $isActive = $this->request->getGet('is_active');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'name') {
-                $builder->like('name', $searchKeyword);
-            }
-        }
-
-        // is_active 필터링 (파라미터가 있을 때만)
-        if ($isActive !== null && $isActive !== '' && $isActive !== false) {
-            $builder->where('is_active', strval($isActive));
-        }
-
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single branch
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('branches');
-        $branch = $builder->where('id', $id)->get()->getRow();
-
-        if (!$branch) {
-            return $this->respondError('지점을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        return $this->respondSuccess($branch);
-    }
-
-    /**
-     * Create branch
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        try {
-            $json = $this->request->getJSON();
-
-            // Validation
-            if (empty($json->name)) {
-                return $this->respondError('딜러사 명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            // if (empty($json->phone)) {
-            //     return $this->respondError('대표번호를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            // }
-
-            // if (empty($json->address)) {
-            //     return $this->respondError('주소를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            // }
-
-            // 데이터 준비
-            $data = [
-                'name' => $json->name ?? '',
-                // 'main_phone' => $json->phone ?? '',  // DB 컬럼명은 main_phone
-                // 'address' => $json->address ?? '',
-                // 'detail_address' => $json->detail_address ?? '',
-                // 'latitude' => $json->latitude ?? null,
-                // 'longitude' => $json->longitude ?? null,
-                // 'business_hours' => $json->business_hours ?? '',
-                'created_at' => date('Y-m-d H:i:s')
-            ];
-
-            $builder = $this->getDB()->table('branches');
-            $result = $builder->insert($data);
-
-            if (!$result) {
-                return $this->respondError('지점 등록에 실패했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-            }
-
-            return $this->respondSuccess(['id' => $this->getDB()->insertID()], '지점이 등록되었습니다.');
-        } catch (\Exception $e) {
-            log_message('error', 'Branch create error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Update branch
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // 데이터 준비
-        $data = [
-            'name' => $json->name ?? '',
-            // 'main_phone' => $json->phone ?? '',  // DB 컬럼명은 main_phone
-            // 'address' => $json->address ?? '',
-            // 'detail_address' => $json->detail_address ?? '',
-            // 'latitude' => $json->latitude ?? null,
-            // 'longitude' => $json->longitude ?? null,
-            // 'business_hours' => $json->business_hours ?? '',
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('branches');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '지점이 수정되었습니다.');
-    }
-
-    /**
-     * Delete branch
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('branches');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '지점이 삭제되었습니다.');
-    }
-
-    /**
-     * Toggle branch active status
-     */
-    public function toggleActive($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('branches');
-        $branch = $builder->where('id', $id)->get()->getRow();
-
-        if (!$branch) {
-            return $this->respondError('지점을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // 현재 상태의 반대로 변경
-        $newStatus = $branch->is_active == 1 ? 0 : 1;
-
-        $builder->where('id', $id)->update([
-            'is_active' => $newStatus,
-            'updated_at' => date('Y-m-d H:i:s')
-        ]);
-
-        $statusText = $newStatus == 1 ? '사용' : '비사용';
-        return $this->respondSuccess(['is_active' => $newStatus], "지점이 {$statusText} 상태로 변경되었습니다.");
-    }
-
-    /**
-     * Get public branch list (No authentication required)
-     * 공개 API - 인증 없이 활성 지점 목록 조회
-     */
-    public function publicList()
-    {
-        try {
-            $builder = $this->getDB()->table('branches');
-
-            // 활성화된 지점만 조회
-            $builder->where('is_active', 1);
-            $builder->orderBy('id', 'DESC');
-
-            $branches = $builder->get()->getResult();
-
-            return $this->respondSuccess($branches);
-        } catch (\Exception $e) {
-            log_message('error', 'Branch public list error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Get public single branch (No authentication required)
-     * 공개 API - 인증 없이 단일 지점 정보 조회
-     */
-    public function publicShow($id = null)
-    {
-        try {
-            $builder = $this->getDB()->table('branches');
-            $builder->where('id', $id);
-            $builder->where('is_active', 1);
-            $branch = $builder->get()->getRow();
-
-            if (!$branch) {
-                return $this->respondError('지점을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-            }
-
-            return $this->respondSuccess($branch);
-        } catch (\Exception $e) {
-            log_message('error', 'Branch public show error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-}

+ 0 - 215
backend/app/Controllers/Api/BranchManagerController.php

@@ -1,215 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class BranchManagerController extends BaseApiController
-{
-    /**
-     * Get branch manager list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('branch_managers bm');
-        $builder->select('bm.*, b.name as branch_name');
-        $builder->join('branches b', 'b.id = bm.branch_id', 'left');
-
-        // Filter by showroom (branch_id)
-        $showroom = $this->request->getGet('showroom');
-        if ($showroom) {
-            $builder->where('bm.branch_id', $showroom);
-        }
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'branch_name') {
-                $builder->like('b.name', $searchKeyword);
-            } elseif ($searchType === 'name') {
-                $builder->like('bm.name', $searchKeyword);
-            } elseif ($searchType === 'username') {
-                $builder->like('bm.username', $searchKeyword);
-            } elseif ($searchType === 'email') {
-                $builder->like('bm.email', $searchKeyword);
-            }
-        }
-
-        $builder->orderBy('bm.id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single branch manager
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('branch_managers bm');
-        $builder->select('bm.*, b.name as branch_name');
-        $builder->join('branches b', 'b.id = bm.branch_id', 'left');
-        $builder->where('bm.id', $id);
-        $manager = $builder->get()->getRow();
-
-        if (!$manager) {
-            return $this->respondError('지점장을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Remove password from response
-        unset($manager->password);
-
-        return $this->respondSuccess($manager);
-    }
-
-    /**
-     * Check if user_id is available
-     */
-    public function checkUserId()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $userId = $this->request->getGet('user_id');
-
-        if (empty($userId)) {
-            return $this->respondError('아이디를 입력하세요.');
-        }
-
-        $builder = $this->getDB()->table('branch_managers');
-        $existing = $builder->where('username', $userId)->get()->getRow();
-
-        return $this->respondSuccess([
-            'available' => !$existing
-        ]);
-    }
-
-    /**
-     * Create branch manager
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // Check if username already exists
-        $builder = $this->getDB()->table('branch_managers');
-        $existing = $builder->where('username', $json->user_id)->get()->getRow();
-
-        if ($existing) {
-            return $this->respondError('이미 사용 중인 아이디입니다.');
-        }
-
-        $data = [
-            'branch_id' => $json->branch_id ?? null,
-            'username' => $json->user_id ?? '',  // 프론트에서 user_id로 전송
-            'password' => password_hash($json->password ?? '', PASSWORD_DEFAULT),
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'greeting' => $json->greeting ?? '',
-            'photo_url' => $json->photo_url ?? '',
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '지점장이 등록되었습니다.');
-    }
-
-    /**
-     * Update branch manager
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'branch_id' => $json->branch_id ?? null,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'greeting' => $json->greeting ?? '',
-            'photo_url' => $json->photo_url ?? '',
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        // Update password only if provided
-        if (!empty($json->password)) {
-            $data['password'] = password_hash($json->password, PASSWORD_DEFAULT);
-        }
-
-        $builder = $this->getDB()->table('branch_managers');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '지점장이 수정되었습니다.');
-    }
-
-    /**
-     * Delete branch manager
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('branch_managers');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '지점장이 삭제되었습니다.');
-    }
-
-    /**
-     * Get public branch manager list (No authentication required)
-     * 공개 API - 인증 없이 지점장 정보 조회
-     */
-    public function publicList()
-    {
-        try {
-            $builder = $this->getDB()->table('branch_managers bm');
-            $builder->select('bm.*, b.name as branch_name');
-            $builder->join('branches b', 'b.id = bm.branch_id', 'left');
-
-            // Filter by showroom (branch_id)
-            $showroom = $this->request->getGet('showroom');
-            if ($showroom) {
-                $builder->where('bm.branch_id', $showroom);
-            }
-
-            // Only active branches
-            $builder->where('b.is_active', 1);
-            $builder->orderBy('bm.id', 'DESC');
-
-            $managers = $builder->get()->getResult();
-
-            return $this->respondSuccess($managers);
-        } catch (\Exception $e) {
-            log_message('error', 'Branch manager public list error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-}

+ 0 - 87
backend/app/Controllers/Api/BrochureController.php

@@ -1,87 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class BrochureController extends BaseApiController
-{
-    /**
-     * Get brochure request list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('brochure_requests');
-
-        // Search by applicant name
-        $name = $this->request->getGet('name');
-
-        if ($name) {
-            $builder->like('applicant_name', $name);
-        }
-
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Update brochure request status
-     */
-    public function updateStatus($id)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'status' => $json->status ?? '접수',
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('brochure_requests');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '상태가 변경되었습니다.');
-    }
-
-    /**
-     * Delete brochure request
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('brochure_requests');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '브로셔 요청이 삭제되었습니다.');
-    }
-
-    /**
-     * Export to Excel
-     */
-    public function exportExcel()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        // TODO: Implement Excel export logic
-        return $this->respondSuccess(null, 'Excel export endpoint');
-    }
-}

+ 0 - 291
backend/app/Controllers/Api/EventController.php

@@ -1,291 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class EventController extends BaseApiController
-{
-    /**
-     * Get event list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('events');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'title') {
-                $builder->like('title', $searchKeyword);
-            } elseif ($searchType === 'name') {
-                $builder->like('name', $searchKeyword);
-            } elseif ($searchType === 'content') {
-                $builder->like('content', $searchKeyword);
-            }
-        }
-
-        $builder->orderBy('is_notice', 'DESC');
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single event
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('events');
-        $event = $builder->where('id', $id)->get()->getRow();
-
-        if (!$event) {
-            return $this->respondError('이벤트를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Parse file_urls JSON
-        $event->file_urls = $this->normalizeFileUrls($event->file_urls ?? '[]');
-
-        // Fix image paths in content: /event/image.jpg -> /uploads/bbs/event/image.jpg
-        if (!empty($event->content)) {
-            // src="/event/ 형태를 src="/uploads/bbs/event/ 로 변경
-            $event->content = str_replace('src="/event/', 'src="/uploads/bbs/event/', $event->content);
-            // src='/event/ 형태도 처리
-            $event->content = str_replace("src='/event/", "src='/uploads/bbs/event/", $event->content);
-
-            // YouTube iframe 경로 수정: /embed/ID -> https://www.youtube.com/embed/ID
-            $event->content = str_replace('src="/embed/', 'src="https://www.youtube.com/embed/', $event->content);
-            $event->content = str_replace("src='/embed/", "src='https://www.youtube.com/embed/", $event->content);
-
-            // 도메인 추가: src="/uploads -> src="http://도메인/uploads
-            // 단, 이미 http:// 또는 https://로 시작하는 URL은 제외
-            $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
-            $currentDomain = $protocol . ($_SERVER['HTTP_HOST'] ?? 'localhost');
-
-            // 정규표현식으로 /uploads로 시작하고 앞에 http(s)://가 없는 경우만 도메인 추가
-            $event->content = preg_replace(
-                '/src="(?!https?:\/\/)\/uploads\//',
-                'src="' . $currentDomain . '/uploads/',
-                $event->content
-            );
-            $event->content = preg_replace(
-                "/src='(?!https?:\/\/)\/uploads\//",
-                "src='" . $currentDomain . "/uploads/",
-                $event->content
-            );
-        }
-
-        // Increment view count
-        $builder->where('id', $id)->set('views', 'views + 1', false)->update();
-
-        return $this->respondSuccess($event);
-    }
-
-    /**
-     * Create event
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'site' => $json->site ?? 'common',
-            'category' => $json->category ?? '',
-            'allow_comment' => isset($json->allow_comment) ? (int)$json->allow_comment : 0,
-            'is_notice' => isset($json->is_notice) ? (int)$json->is_notice : 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'start_date' => $json->start_date ?? '',
-            'end_date' => $json->end_date ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'views' => 0,
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('events');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '이벤트가 등록되었습니다.');
-    }
-
-    /**
-     * Update event
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'site' => $json->site ?? 'common',
-            'category' => $json->category ?? '',
-            'allow_comment' => isset($json->allow_comment) ? (int)$json->allow_comment : 0,
-            'is_notice' => isset($json->is_notice) ? (int)$json->is_notice : 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'start_date' => $json->start_date ?? '',
-            'end_date' => $json->end_date ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('events');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '이벤트가 수정되었습니다.');
-    }
-
-    /**
-     * Delete event
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('events');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '이벤트가 삭제되었습니다.');
-    }
-
-    /**
-     * Normalize file_urls to always return object array
-     * Handles both old format (string array) and new format (object array)
-     */
-    private function normalizeFileUrls($fileUrlsJson)
-    {
-        $fileUrls = json_decode($fileUrlsJson ?? '[]');
-
-        if (empty($fileUrls) || !is_array($fileUrls)) {
-            return [];
-        }
-
-        $normalized = [];
-        foreach ($fileUrls as $item) {
-            // If already an object with url property, keep it
-            if (is_object($item) && isset($item->url)) {
-                $normalized[] = $item;
-            }
-            // If it's a string (old format), convert to object
-            elseif (is_string($item)) {
-                $filename = basename($item);
-                $normalized[] = (object)[
-                    'name' => $filename,
-                    'url' => $item,
-                    'size' => 0  // Size unknown for migrated data
-                ];
-            }
-        }
-
-        return $normalized;
-    }
-
-    /**
-     * Get public event list (no auth required)
-     */
-    public function publicList()
-    {
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('events');
-
-        // Filter by site (ford or lincoln) + common
-        $site = $this->request->getGet('site');
-        if ($site && in_array($site, ['ford', 'lincoln'])) {
-            $builder->where("(site = '{$site}' OR site = 'common')");
-        }
-
-        // Show all events (including notices)
-        $builder->orderBy('id', 'DESC');
-
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Parse file_urls for each item
-        if (!empty($result['items'])) {
-            foreach ($result['items'] as &$item) {
-                $item->file_urls = $this->normalizeFileUrls($item->file_urls ?? '[]');
-            }
-        }
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get public single event (no auth required)
-     */
-    public function publicShow($id = null)
-    {
-        $builder = $this->getDB()->table('events');
-        $event = $builder->where('id', $id)->get()->getRow();
-
-        if (!$event) {
-            return $this->respondError('이벤트를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Parse file_urls JSON
-        $event->file_urls = $this->normalizeFileUrls($event->file_urls ?? '[]');
-
-        // Fix image paths in content: /event/image.jpg -> /uploads/bbs/event/image.jpg
-        if (!empty($event->content)) {
-            // src="/event/ 형태를 src="/uploads/bbs/event/ 로 변경
-            $event->content = str_replace('src="/event/', 'src="/uploads/bbs/event/', $event->content);
-            // src='/event/ 형태도 처리
-            $event->content = str_replace("src='/event/", "src='/uploads/bbs/event/", $event->content);
-
-            // YouTube iframe 경로 수정: /embed/ID -> https://www.youtube.com/embed/ID
-            $event->content = str_replace('src="/embed/', 'src="https://www.youtube.com/embed/', $event->content);
-            $event->content = str_replace("src='/embed/", "src='https://www.youtube.com/embed/", $event->content);
-
-            // 도메인 추가: src="/uploads -> src="http://도메인/uploads
-            // 단, 이미 http:// 또는 https://로 시작하는 URL은 제외
-            $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
-            $currentDomain = $protocol . ($_SERVER['HTTP_HOST'] ?? 'localhost');
-
-            // 정규표현식으로 /uploads로 시작하고 앞에 http(s)://가 없는 경우만 도메인 추가
-            $event->content = preg_replace(
-                '/src="(?!https?:\/\/)\/uploads\//',
-                'src="' . $currentDomain . '/uploads/',
-                $event->content
-            );
-            $event->content = preg_replace(
-                "/src='(?!https?:\/\/)\/uploads\//",
-                "src='" . $currentDomain . "/uploads/",
-                $event->content
-            );
-        }
-
-        // Increment view count
-        $builder->where('id', $id)->set('views', 'views + 1', false)->update();
-
-        return $this->respondSuccess($event);
-    }
-}

+ 0 - 135
backend/app/Controllers/Api/IrController.php

@@ -1,135 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class IrController extends BaseApiController
-{
-    /**
-     * Get IR list (Public)
-     */
-    public function index()
-    {
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('ir');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'title') {
-                $builder->like('title', $searchKeyword);
-            } elseif ($searchType === 'name') {
-                $builder->like('name', $searchKeyword);
-            } elseif ($searchType === 'content') {
-                $builder->like('content', $searchKeyword);
-            }
-        }
-
-        $builder->orderBy('is_notice', 'DESC');
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single IR (Public)
-     */
-    public function show($id = null)
-    {
-        $builder = $this->getDB()->table('ir');
-        $ir = $builder->where('id', $id)->get()->getRow();
-
-        if (!$ir) {
-            return $this->respondError('IR 자료를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Parse file_urls JSON
-        $ir->file_urls = json_decode($ir->file_urls ?? '[]');
-
-        // Increment view count
-        $builder->where('id', $id)->set('views', 'views + 1', false)->update();
-
-        return $this->respondSuccess($ir);
-    }
-
-    /**
-     * Create IR
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'allow_comment' => $json->allow_comment ?? 0,
-            'is_notice' => $json->is_notice ?? 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'url' => $json->url ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'views' => 0,
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('ir');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], 'IR 자료가 등록되었습니다.');
-    }
-
-    /**
-     * Update IR
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'allow_comment' => $json->allow_comment ?? 0,
-            'is_notice' => $json->is_notice ?? 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'url' => $json->url ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('ir');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, 'IR 자료가 수정되었습니다.');
-    }
-
-    /**
-     * Delete IR
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('ir');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, 'IR 자료가 삭제되었습니다.');
-    }
-}

+ 0 - 238
backend/app/Controllers/Api/NewsController.php

@@ -1,238 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class NewsController extends BaseApiController
-{
-    /**
-     * Get news list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('news');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'title') {
-                $builder->like('title', $searchKeyword);
-            } elseif ($searchType === 'name') {
-                $builder->like('name', $searchKeyword);
-            } elseif ($searchType === 'content') {
-                $builder->like('content', $searchKeyword);
-            }
-        }
-
-        $builder->orderBy('is_notice', 'DESC');
-        $builder->orderBy('created_at', 'DESC');
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single news
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('news');
-        $news = $builder->where('id', $id)->get()->getRow();
-
-        if (!$news) {
-            return $this->respondError('뉴스를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Parse file_urls JSON
-        $news->file_urls = json_decode($news->file_urls ?? '[]');
-
-        // Fix image paths in content
-        if (!empty($news->content)) {
-            // YouTube iframe 경로 수정: /embed/ID -> https://www.youtube.com/embed/ID
-            $news->content = str_replace('src="/embed/', 'src="https://www.youtube.com/embed/', $news->content);
-            $news->content = str_replace("src='/embed/", "src='https://www.youtube.com/embed/", $news->content);
-
-            // 도메인 추가: src="/uploads -> src="http://도메인/uploads
-            // 단, 이미 http:// 또는 https://로 시작하는 URL은 제외
-            $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
-            $currentDomain = $protocol . ($_SERVER['HTTP_HOST'] ?? 'localhost');
-
-            // 정규표현식으로 /uploads로 시작하고 앞에 http(s)://가 없는 경우만 도메인 추가
-            $news->content = preg_replace(
-                '/src="(?!https?:\/\/)\/uploads\//',
-                'src="' . $currentDomain . '/uploads/',
-                $news->content
-            );
-            $news->content = preg_replace(
-                "/src='(?!https?:\/\/)\/uploads\//",
-                "src='" . $currentDomain . "/uploads/",
-                $news->content
-            );
-        }
-
-        // Increment view count
-        $builder->where('id', $id)->set('views', 'views + 1', false)->update();
-
-        return $this->respondSuccess($news);
-    }
-
-    /**
-     * Create news
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'allow_comment' => isset($json->allow_comment) ? (int)$json->allow_comment : 0,
-            'is_notice' => isset($json->is_notice) ? (int)$json->is_notice : 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'url' => $json->url ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'views' => 0,
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('news');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '뉴스가 등록되었습니다.');
-    }
-
-    /**
-     * Update news
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'allow_comment' => isset($json->allow_comment) ? (int)$json->allow_comment : 0,
-            'is_notice' => isset($json->is_notice) ? (int)$json->is_notice : 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'url' => $json->url ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('news');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '뉴스가 수정되었습니다.');
-    }
-
-    /**
-     * Delete news
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('news');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '뉴스가 삭제되었습니다.');
-    }
-
-    /**
-     * Get public news list (no auth required)
-     */
-    public function publicList()
-    {
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('news');
-
-        $builder->orderBy('is_notice', 'DESC');
-        $builder->orderBy('created_at', 'DESC');
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Parse file_urls for each item
-        if (!empty($result['items'])) {
-            foreach ($result['items'] as &$item) {
-                $item->file_urls = json_decode($item->file_urls ?? '[]');
-            }
-        }
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single news (no auth required)
-     */
-    public function publicShow($id = null)
-    {
-        $builder = $this->getDB()->table('news');
-        $news = $builder->where('id', $id)->get()->getRow();
-
-        if (!$news) {
-            return $this->respondError('뉴스를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Parse file_urls JSON
-        $news->file_urls = json_decode($news->file_urls ?? '[]');
-
-        // Fix image paths in content
-        if (!empty($news->content)) {
-            // YouTube iframe 경로 수정: /embed/ID -> https://www.youtube.com/embed/ID
-            $news->content = str_replace('src="/embed/', 'src="https://www.youtube.com/embed/', $news->content);
-            $news->content = str_replace("src='/embed/", "src='https://www.youtube.com/embed/", $news->content);
-
-            // 도메인 추가: src="/uploads -> src="http://도메인/uploads
-            // 단, 이미 http:// 또는 https://로 시작하는 URL은 제외
-            $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
-            $currentDomain = $protocol . ($_SERVER['HTTP_HOST'] ?? 'localhost');
-
-            // 정규표현식으로 /uploads로 시작하고 앞에 http(s)://가 없는 경우만 도메인 추가
-            $news->content = preg_replace(
-                '/src="(?!https?:\/\/)\/uploads\//',
-                'src="' . $currentDomain . '/uploads/',
-                $news->content
-            );
-            $news->content = preg_replace(
-                "/src='(?!https?:\/\/)\/uploads\//",
-                "src='" . $currentDomain . "/uploads/",
-                $news->content
-            );
-        }
-
-        // Increment view count
-        $builder->where('id', $id)->set('views', 'views + 1', false)->update();
-
-        return $this->respondSuccess($news);
-    }
-}

+ 0 - 261
backend/app/Controllers/Api/NoticeController.php

@@ -1,261 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class NoticeController extends BaseApiController
-{
-    /**
-     * Get notice list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('notices');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'title') {
-                $builder->like('title', $searchKeyword);
-            } elseif ($searchType === 'name') {
-                $builder->like('name', $searchKeyword);
-            } elseif ($searchType === 'content') {
-                $builder->like('content', $searchKeyword);
-            }
-        }
-
-        $builder->orderBy('is_notice', 'DESC');
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single notice
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('notices');
-        $notice = $builder->where('id', $id)->get()->getRow();
-
-        if (!$notice) {
-            return $this->respondError('공지사항을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Parse file_urls JSON
-        $notice->file_urls = $this->normalizeFileUrls($notice->file_urls ?? '[]');
-
-        // Fix image paths in content
-        if (!empty($notice->content)) {
-            // 도메인 추가: src="/uploads -> src="http://도메인/uploads
-            // 단, 이미 http:// 또는 https://로 시작하는 URL은 제외
-            $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
-            $currentDomain = $protocol . ($_SERVER['HTTP_HOST'] ?? 'localhost');
-
-            // 정규표현식으로 /uploads로 시작하고 앞에 http(s)://가 없는 경우만 도메인 추가
-            $notice->content = preg_replace(
-                '/src="(?!https?:\/\/)\/uploads\//',
-                'src="' . $currentDomain . '/uploads/',
-                $notice->content
-            );
-            $notice->content = preg_replace(
-                "/src='(?!https?:\/\/)\/uploads\//",
-                "src='" . $currentDomain . "/uploads/",
-                $notice->content
-            );
-        }
-
-        // Increment view count
-        $builder->where('id', $id)->set('views', 'views + 1', false)->update();
-
-        return $this->respondSuccess($notice);
-    }
-
-    /**
-     * Create notice
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'allow_comment' => isset($json->allow_comment) ? (int)$json->allow_comment : 0,
-            'is_notice' => isset($json->is_notice) ? (int)$json->is_notice : 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'url' => $json->url ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'views' => 0,
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('notices');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '공지사항이 등록되었습니다.');
-    }
-
-    /**
-     * Update notice
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'allow_comment' => isset($json->allow_comment) ? (int)$json->allow_comment : 0,
-            'is_notice' => isset($json->is_notice) ? (int)$json->is_notice : 0,
-            'name' => $json->name ?? '',
-            'email' => $json->email ?? '',
-            'url' => $json->url ?? '',
-            'title' => $json->title ?? '',
-            'content' => $json->content ?? '',
-            'file_urls' => json_encode($json->file_urls ?? []),
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('notices');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '공지사항이 수정되었습니다.');
-    }
-
-    /**
-     * Delete notice
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('notices');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '공지사항이 삭제되었습니다.');
-    }
-
-    /**
-     * Normalize file_urls to always return object array
-     * Handles both old format (string array) and new format (object array)
-     */
-    private function normalizeFileUrls($fileUrlsJson)
-    {
-        $fileUrls = json_decode($fileUrlsJson ?? '[]');
-
-        if (empty($fileUrls) || !is_array($fileUrls)) {
-            return [];
-        }
-
-        $normalized = [];
-        foreach ($fileUrls as $item) {
-            // If already an object with url property, keep it
-            if (is_object($item) && isset($item->url)) {
-                $normalized[] = $item;
-            }
-            // If it's a string (old format), convert to object
-            elseif (is_string($item)) {
-                $filename = basename($item);
-                $normalized[] = (object)[
-                    'name' => $filename,
-                    'url' => $item,
-                    'size' => 0  // Size unknown for migrated data
-                ];
-            }
-        }
-
-        return $normalized;
-    }
-
-    /**
-     * Get public notice list (no auth required)
-     */
-    public function publicList()
-    {
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('notices');
-
-        // Show all notices
-        $builder->orderBy('id', 'DESC');
-
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Parse file_urls for each item
-        if (!empty($result['items'])) {
-            foreach ($result['items'] as &$item) {
-                $item->file_urls = $this->normalizeFileUrls($item->file_urls ?? '[]');
-            }
-        }
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get public single notice (no auth required)
-     */
-    public function publicShow($id = null)
-    {
-        $builder = $this->getDB()->table('notices');
-        $notice = $builder->where('id', $id)->get()->getRow();
-
-        if (!$notice) {
-            return $this->respondError('공지사항을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Parse file_urls JSON
-        $notice->file_urls = $this->normalizeFileUrls($notice->file_urls ?? '[]');
-
-        // Fix image paths in content
-        if (!empty($notice->content)) {
-            // 도메인 추가: src="/uploads -> src="http://도메인/uploads
-            // 단, 이미 http:// 또는 https://로 시작하는 URL은 제외
-            $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
-            $currentDomain = $protocol . ($_SERVER['HTTP_HOST'] ?? 'localhost');
-
-            // 정규표현식으로 /uploads로 시작하고 앞에 http(s)://가 없는 경우만 도메인 추가
-            $notice->content = preg_replace(
-                '/src="(?!https?:\/\/)\/uploads\//',
-                'src="' . $currentDomain . '/uploads/',
-                $notice->content
-            );
-            $notice->content = preg_replace(
-                "/src='(?!https?:\/\/)\/uploads\//",
-                "src='" . $currentDomain . "/uploads/",
-                $notice->content
-            );
-        }
-
-        // Increment view count
-        $builder->where('id', $id)->set('views', 'views + 1', false)->update();
-
-        return $this->respondSuccess($notice);
-    }
-}

+ 0 - 199
backend/app/Controllers/Api/PopupController.php

@@ -1,199 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class PopupController extends BaseApiController
-{
-    /**
-     * Get popup list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('popups');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'title') {
-                $builder->like('title', $searchKeyword);
-            } elseif ($searchType === 'status') {
-                $builder->like('status', $searchKeyword);
-            }
-        }
-
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Fix localhost URLs in items
-        $result['items'] = $this->fixUrls($result['items'], ['image_url']);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single popup
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('popups');
-        $popup = $builder->where('id', $id)->get()->getRow();
-
-        if (!$popup) {
-            return $this->respondError('팝업을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Fix localhost URLs
-        $popup = $this->fixUrls($popup, ['image_url']);
-
-        return $this->respondSuccess($popup);
-    }
-
-    /**
-     * Create popup
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // cookie_type 또는 cookie_setting 둘 다 지원
-        $cookieSetting = $json->cookie_setting ?? $json->cookie_type ?? 'none';
-
-        $data = [
-            'type' => $json->type ?? 'html',
-            'title' => $json->title ?? '',
-            'start_date' => $json->start_date ?? '',
-            'end_date' => $json->end_date ?? '',
-            'start_time' => !empty($json->start_time) ? $json->start_time : null,
-            'end_time' => !empty($json->end_time) ? $json->end_time : null,
-            'width' => $json->width ?? 400,
-            'height' => $json->height ?? 500,
-            'position_top' => $json->position_top ?? 100,
-            'position_left' => $json->position_left ?? 100,
-            'cookie_setting' => $cookieSetting,
-            'content' => $json->content ?? '',
-            'image_url' => $json->image_url ?? '',
-            'link_url' => $json->link_url ?? '',
-            'link_target' => $json->link_target ?? '_blank',
-            'is_active' => $json->is_active ?? 1,
-            'site' => $json->site ?? 'ford',
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('popups');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '팝업이 등록되었습니다.');
-    }
-
-    /**
-     * Update popup
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // cookie_type 또는 cookie_setting 둘 다 지원
-        $cookieSetting = $json->cookie_setting ?? $json->cookie_type ?? 'none';
-
-        $data = [
-            'type' => $json->type ?? 'html',
-            'title' => $json->title ?? '',
-            'start_date' => $json->start_date ?? '',
-            'end_date' => $json->end_date ?? '',
-            'start_time' => !empty($json->start_time) ? $json->start_time : null,
-            'end_time' => !empty($json->end_time) ? $json->end_time : null,
-            'width' => $json->width ?? 400,
-            'height' => $json->height ?? 500,
-            'position_top' => $json->position_top ?? 100,
-            'position_left' => $json->position_left ?? 100,
-            'cookie_setting' => $cookieSetting,
-            'content' => $json->content ?? '',
-            'image_url' => $json->image_url ?? '',
-            'link_url' => $json->link_url ?? '',
-            'link_target' => $json->link_target ?? '_blank',
-            'is_active' => $json->is_active ?? 1,
-            'site' => $json->site ?? 'ford',
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('popups');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '팝업이 수정되었습니다.');
-    }
-
-    /**
-     * Delete popup
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('popups');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '팝업이 삭제되었습니다.');
-    }
-
-    /**
-     * Get active popups (Public API - No Auth Required)
-     */
-    public function getActive()
-    {
-        $builder = $this->getDB()->table('popups');
-
-        // 사이트 필터 (GET 파라미터로 전달)
-        $site = $this->request->getGet('site');
-
-        // 활성화되고 기간 내인 팝업만 조회
-        // 시간 고려: start_time NULL → 00:00:00(당일 즉시), end_time NULL → 23:59:59(자정 직전)
-        $now = date('Y-m-d H:i:s');
-        $nowEsc = $this->getDB()->escape($now);
-        $builder->where('is_active', 1)
-            ->where("CONCAT(start_date, ' ', COALESCE(start_time, '00:00:00')) <= $nowEsc", null, false)
-            ->where("CONCAT(end_date, ' ', COALESCE(end_time, '23:59:59')) >= $nowEsc", null, false);
-
-        // 사이트 필터 적용
-        if ($site && in_array($site, ['ford', 'lincoln'])) {
-            $builder->where('site', $site);
-        }
-
-        $popups = $builder
-            ->orderBy('id', 'DESC')
-            ->get()
-            ->getResult();
-
-        // Fix localhost URLs
-        $popups = $this->fixUrls($popups, ['image_url']);
-
-        return $this->respondSuccess($popups);
-    }
-}

+ 0 - 191
backend/app/Controllers/Api/PopupController.php.bak

@@ -1,191 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class PopupController extends BaseApiController
-{
-    /**
-     * Get popup list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('popups');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'title') {
-                $builder->like('title', $searchKeyword);
-            } elseif ($searchType === 'status') {
-                $builder->like('status', $searchKeyword);
-            }
-        }
-
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Fix localhost URLs in items
-        $result['items'] = $this->fixUrls($result['items'], ['image_url']);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single popup
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('popups');
-        $popup = $builder->where('id', $id)->get()->getRow();
-
-        if (!$popup) {
-            return $this->respondError('팝업을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Fix localhost URLs
-        $popup = $this->fixUrls($popup, ['image_url']);
-
-        return $this->respondSuccess($popup);
-    }
-
-    /**
-     * Create popup
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // cookie_type 또는 cookie_setting 둘 다 지원
-        $cookieSetting = $json->cookie_setting ?? $json->cookie_type ?? 'none';
-
-        $data = [
-            'type' => $json->type ?? 'html',
-            'title' => $json->title ?? '',
-            'start_date' => $json->start_date ?? '',
-            'end_date' => $json->end_date ?? '',
-            'width' => $json->width ?? 400,
-            'height' => $json->height ?? 500,
-            'position_top' => $json->position_top ?? 100,
-            'position_left' => $json->position_left ?? 100,
-            'cookie_setting' => $cookieSetting,
-            'content' => $json->content ?? '',
-            'image_url' => $json->image_url ?? '',
-            'link_url' => $json->link_url ?? '',
-            'is_active' => $json->is_active ?? 1,
-            'site' => $json->site ?? 'ford',
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('popups');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '팝업이 등록되었습니다.');
-    }
-
-    /**
-     * Update popup
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // cookie_type 또는 cookie_setting 둘 다 지원
-        $cookieSetting = $json->cookie_setting ?? $json->cookie_type ?? 'none';
-
-        $data = [
-            'type' => $json->type ?? 'html',
-            'title' => $json->title ?? '',
-            'start_date' => $json->start_date ?? '',
-            'end_date' => $json->end_date ?? '',
-            'width' => $json->width ?? 400,
-            'height' => $json->height ?? 500,
-            'position_top' => $json->position_top ?? 100,
-            'position_left' => $json->position_left ?? 100,
-            'cookie_setting' => $cookieSetting,
-            'content' => $json->content ?? '',
-            'image_url' => $json->image_url ?? '',
-            'link_url' => $json->link_url ?? '',
-            'is_active' => $json->is_active ?? 1,
-            'site' => $json->site ?? 'ford',
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('popups');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '팝업이 수정되었습니다.');
-    }
-
-    /**
-     * Delete popup
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('popups');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '팝업이 삭제되었습니다.');
-    }
-
-    /**
-     * Get active popups (Public API - No Auth Required)
-     */
-    public function getActive()
-    {
-        $builder = $this->getDB()->table('popups');
-
-        // 사이트 필터 (GET 파라미터로 전달)
-        $site = $this->request->getGet('site');
-
-        // 활성화되고 기간 내인 팝업만 조회
-        $today = date('Y-m-d');
-        $builder->where('is_active', 1)
-            ->where('start_date <=', $today)
-            ->where('end_date >=', $today);
-
-        // 사이트 필터 적용
-        if ($site && in_array($site, ['ford', 'lincoln'])) {
-            $builder->where('site', $site);
-        }
-
-        $popups = $builder
-            ->orderBy('id', 'DESC')
-            ->get()
-            ->getResult();
-
-        // Fix localhost URLs
-        $popups = $this->fixUrls($popups, ['image_url']);
-
-        return $this->respondSuccess($popups);
-    }
-}

+ 0 - 383
backend/app/Controllers/Api/SalesStaffController.php

@@ -1,383 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class SalesStaffController extends BaseApiController
-{
-    /**
-     * Get sales staff list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('sales_staff');
-
-        // Search filters
-        $showroom = $this->request->getGet('showroom');
-        $team = $this->request->getGet('team');
-        $position = $this->request->getGet('position');
-        $keyword = $this->request->getGet('keyword');
-        $name = $this->request->getGet('name');
-        $isAct = $this->request->getGet('is_act');
-
-        if ($showroom) {
-            $builder->where('showroom', $showroom);
-        }
-
-        if ($team) {
-            $builder->where('team', $team);
-        }
-
-        if ($position) {
-            $builder->where('position', $position);
-        }
-
-        // is_act 필터링 (파라미터가 있을 때만)
-        // 문자열 비교를 위해 형변환 없이 처리
-        if ($isAct !== null && $isAct !== '' && $isAct !== false) {
-            $builder->where('is_act', strval($isAct));
-        }
-
-        // keyword가 있으면 전체 검색 (이름, 직책, 대표번호, 이메일, 전시장, 팀)
-        if ($keyword) {
-            $builder->groupStart()
-                ->like('name', $keyword)
-                ->orLike('position', $keyword)
-                ->orLike('main_phone', $keyword)
-                ->orLike('email', $keyword)
-                ->orLike('showroom', $keyword)
-                ->orLike('team', $keyword)
-                ->groupEnd();
-        } else if ($name) {
-            // name 파라미터는 이름만 검색 (하위 호환성)
-            $builder->like('name', $name);
-        }
-
-        // 최신 등록순으로 정렬 (id 내림차순)
-        $builder->orderBy('id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single sales staff
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('sales_staff');
-        $staff = $builder->where('id', $id)->get()->getRow();
-
-        if (!$staff) {
-            return $this->respondError('영업사원을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        return $this->respondSuccess($staff);
-    }
-
-    /**
-     * Create sales staff
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'showroom' => $json->showroom ?? '',
-            'team' => $json->team ?? '',
-            'name' => $json->name ?? '',
-            'position' => (string)($json->position ?? ''),
-            'main_phone' => $json->main_phone ?? '',
-            'direct_phone' => $json->direct_phone ?? '',
-            'email' => $json->email ?? '',
-            'photo_url' => $json->photo_url ?? '',
-            'is_sact' => $json->is_sact ?? 0,
-            'is_goldplt' => $json->is_goldplt ?? 0,
-            'is_top30' => $json->is_top30 ?? 0,
-            'is_best_advisor' => $json->is_best_advisor ?? 0,
-            'is_best_advisor_year' => $json->is_best_advisor_year ?? 0,
-            'display_order' => $json->display_order ?? 0,
-            'created_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('sales_staff');
-        $builder->insert($data);
-
-        return $this->respondSuccess(['id' => $this->getDB()->insertID()], '영업사원이 등록되었습니다.');
-    }
-
-    /**
-     * Update sales staff
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        $data = [
-            'showroom' => $json->showroom ?? '',
-            'team' => $json->team ?? '',
-            'name' => $json->name ?? '',
-            'position' => (string)($json->position ?? ''),
-            'main_phone' => $json->main_phone ?? '',
-            'direct_phone' => $json->direct_phone ?? '',
-            'email' => $json->email ?? '',
-            'photo_url' => $json->photo_url ?? '',
-            'is_sact' => $json->is_sact ?? 0,
-            'is_goldplt' => $json->is_goldplt ?? 0,
-            'is_top30' => $json->is_top30 ?? 0,
-            'is_best_advisor' => $json->is_best_advisor ?? 0,
-            'is_best_advisor_year' => $json->is_best_advisor_year ?? 0,
-            'display_order' => $json->display_order ?? 0,
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('sales_staff');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '영업사원이 수정되었습니다.');
-    }
-
-    /**
-     * Delete sales staff
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('sales_staff');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '영업사원이 삭제되었습니다.');
-    }
-
-    /**
-     * Print single
-     */
-    public function printSingle($id)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        // TODO: Implement print logic
-        return $this->respondSuccess(null, 'Print endpoint');
-    }
-
-    /**
-     * Toggle is_act status (노출/비노출 토글)
-     */
-    public function toggleActive($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('sales_staff');
-        $staff = $builder->where('id', $id)->get()->getRow();
-
-        if (!$staff) {
-            return $this->respondError('영업사원을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Toggle is_act: 1 -> 0, 0 -> 1
-        $newStatus = $staff->is_act == 1 ? 0 : 1;
-
-        $builder->where('id', $id)->update([
-            'is_act' => $newStatus,
-            'updated_at' => date('Y-m-d H:i:s')
-        ]);
-
-        $statusText = $newStatus == 1 ? '노출' : '비노출';
-        return $this->respondSuccess(
-            ['is_act' => $newStatus],
-            "영업사원이 {$statusText} 상태로 변경되었습니다."
-        );
-    }
-
-    /**
-     * Toggle is_best_advisor status (Best Sales Advisor 토글)
-     */
-    public function toggleBestAdvisor($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('sales_staff');
-        $staff = $builder->where('id', $id)->get()->getRow();
-
-        if (!$staff) {
-            return $this->respondError('영업사원을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Toggle is_best_advisor: 1 -> 0, 0 -> 1
-        $newStatus = $staff->is_best_advisor == 1 ? 0 : 1;
-
-        $builder->where('id', $id)->update([
-            'is_best_advisor' => $newStatus,
-            'updated_at' => date('Y-m-d H:i:s')
-        ]);
-
-        $statusText = $newStatus == 1 ? 'Best Sales Advisor로 지정' : 'Best Sales Advisor 해제';
-        return $this->respondSuccess(
-            ['is_best_advisor' => $newStatus],
-            "{$statusText}되었습니다."
-        );
-    }
-
-    /**
-     * Toggle is_best_advisor_year status (Best Sales Advisor Year 토글)
-     */
-    public function toggleBestAdvisorYear($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('sales_staff');
-        $staff = $builder->where('id', $id)->get()->getRow();
-
-        if (!$staff) {
-            return $this->respondError('영업사원을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Toggle is_best_advisor_year: 1 -> 0, 0 -> 1
-        $newStatus = $staff->is_best_advisor_year == 1 ? 0 : 1;
-
-        $builder->where('id', $id)->update([
-            'is_best_advisor_year' => $newStatus,
-            'updated_at' => date('Y-m-d H:i:s')
-        ]);
-
-        $statusText = $newStatus == 1 ? 'Best Sales Advisor (Year)로 지정' : 'Best Sales Advisor (Year) 해제';
-        return $this->respondSuccess(
-            ['is_best_advisor_year' => $newStatus],
-            "{$statusText}되었습니다."
-        );
-    }
-
-    /**
-     * Get public sales staff list (No authentication required)
-     * 공개 API - 인증 없이 영업사원 목록 조회
-     */
-    public function publicList()
-    {
-        try {
-            $builder = $this->getDB()->table('sales_staff');
-
-            // Filter by showroom
-            $showroom = $this->request->getGet('showroom');
-            if ($showroom) {
-                $builder->where('showroom', $showroom);
-            }
-
-            // Only show active sales staff (is_act = 1)
-            $builder->where('is_act', 1);
-
-            $builder->orderBy('id', 'DESC');
-
-            $salesStaff = $builder->get()->getResult();
-
-            return $this->respondSuccess($salesStaff);
-        } catch (\Exception $e) {
-            log_message('error', 'Sales staff public list error: ' . $e->getMessage());
-            log_message('error', 'Stack trace: ' . $e->getTraceAsString());
-            return $this->respondError('서버 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Get best advisors list (No authentication required)
-     * 공개 API - Best Sales Advisor 목록 조회
-     */
-    public function bestAdvisors()
-    {
-        try {
-            $builder = $this->getDB()->table('sales_staff');
-
-            // Filter by showroom (optional)
-            $showroom = $this->request->getGet('showroom');
-            if ($showroom) {
-                $builder->where('showroom', $showroom);
-            }
-
-            // Only show active and best advisors
-            $builder->where('is_act', 1);
-            $builder->where('is_best_advisor', 1);
-
-            // Order by display_order, then by id DESC
-            $builder->orderBy('display_order', 'ASC');
-            $builder->orderBy('id', 'DESC');
-
-            $bestAdvisors = $builder->get()->getResult();
-
-            return $this->respondSuccess($bestAdvisors);
-        } catch (\Exception $e) {
-            log_message('error', 'Best advisors list error: ' . $e->getMessage());
-            log_message('error', 'Stack trace: ' . $e->getTraceAsString());
-            return $this->respondError('서버 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Get best advisors year list (No authentication required)
-     * 공개 API - Best Sales Advisor (Year) 목록 조회
-     */
-    public function bestAdvisorsYear()
-    {
-        try {
-            $builder = $this->getDB()->table('sales_staff');
-
-            // Filter by showroom (optional)
-            $showroom = $this->request->getGet('showroom');
-            if ($showroom) {
-                $builder->where('showroom', $showroom);
-            }
-
-            // Only show active and best advisors (year)
-            $builder->where('is_act', 1);
-            $builder->where('is_best_advisor_year', 1);
-
-            // Order by display_order, then by id DESC
-            $builder->orderBy('display_order', 'ASC');
-            $builder->orderBy('id', 'DESC');
-
-            $bestAdvisorsYear = $builder->get()->getResult();
-
-            return $this->respondSuccess($bestAdvisorsYear);
-        } catch (\Exception $e) {
-            log_message('error', 'Best advisors year list error: ' . $e->getMessage());
-            log_message('error', 'Stack trace: ' . $e->getTraceAsString());
-            return $this->respondError('서버 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-}

+ 0 - 304
backend/app/Controllers/Api/ServiceCenterController.php

@@ -1,304 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class ServiceCenterController extends BaseApiController
-{
-    /**
-     * Get service center list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('service_centers sc');
-
-        // Join with branches table to get branch name
-        $builder->select('sc.*, b.name as branch_name');
-        $builder->join('branches b', 'sc.branch_id = b.id', 'left');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-        $isActive = $this->request->getGet('is_active');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'name') {
-                $builder->like('sc.name', $searchKeyword);
-            } elseif ($searchType === 'address') {
-                $builder->like('sc.address', $searchKeyword);
-            } elseif ($searchType === 'phone') {
-                $builder->like('sc.main_phone', $searchKeyword);
-            }
-        }
-
-        // is_active 필터링
-        if ($isActive !== null && $isActive !== '' && $isActive !== false) {
-            $builder->where('sc.is_active', strval($isActive));
-        }
-
-        $builder->orderBy('sc.id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Decode links JSON for each item
-        foreach ($result['items'] as &$item) {
-            if (!empty($item->links)) {
-                $item->links = json_decode($item->links, true);
-            } else {
-                $item->links = [];
-            }
-        }
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single service center
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('service_centers sc');
-        $builder->select('sc.*, b.name as branch_name');
-        $builder->join('branches b', 'sc.branch_id = b.id', 'left');
-        $builder->where('sc.id', $id);
-        $serviceCenter = $builder->get()->getRow();
-
-        if (!$serviceCenter) {
-            return $this->respondError('서비스센터를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Decode links JSON
-        if (!empty($serviceCenter->links)) {
-            $serviceCenter->links = json_decode($serviceCenter->links, true);
-        } else {
-            $serviceCenter->links = [];
-        }
-
-        return $this->respondSuccess($serviceCenter);
-    }
-
-    /**
-     * Create service center
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        try {
-            $json = $this->request->getJSON();
-
-            // Validation
-            if (empty($json->name)) {
-                return $this->respondError('서비스센터명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            if (empty($json->branch_id)) {
-                return $this->respondError('소속 지점을 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            if (empty($json->phone)) {
-                return $this->respondError('대표번호를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            if (empty($json->address)) {
-                return $this->respondError('주소를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            // Encode links to JSON
-            $links = null;
-            if (!empty($json->links) && is_array($json->links)) {
-                $links = json_encode($json->links);
-            }
-
-            // 데이터 준비
-            $data = [
-                'name' => $json->name ?? '',
-                'branch_id' => $json->branch_id ?? null,
-                'main_phone' => $json->phone ?? '',
-                'address' => $json->address ?? '',
-                'detail_address' => $json->detail_address ?? '',
-                'latitude' => $json->latitude ?? null,
-                'longitude' => $json->longitude ?? null,
-                'business_hours' => $json->business_hours ?? '',
-                'service_reservation_link' => $json->service_reservation_link ?? '',
-                'links' => $links,
-                'created_at' => date('Y-m-d H:i:s')
-            ];
-
-            $builder = $this->getDB()->table('service_centers');
-            $result = $builder->insert($data);
-
-            if (!$result) {
-                return $this->respondError('서비스센터 등록에 실패했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-            }
-
-            return $this->respondSuccess(['id' => $this->getDB()->insertID()], '서비스센터가 등록되었습니다.');
-        } catch (\Exception $e) {
-            log_message('error', 'ServiceCenter create error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Update service center
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // Encode links to JSON
-        $links = null;
-        if (!empty($json->links) && is_array($json->links)) {
-            $links = json_encode($json->links);
-        }
-
-        // 데이터 준비
-        $data = [
-            'name' => $json->name ?? '',
-            'branch_id' => $json->branch_id ?? null,
-            'main_phone' => $json->phone ?? '',
-            'address' => $json->address ?? '',
-            'detail_address' => $json->detail_address ?? '',
-            'latitude' => $json->latitude ?? null,
-            'longitude' => $json->longitude ?? null,
-            'business_hours' => $json->business_hours ?? '',
-            'service_reservation_link' => $json->service_reservation_link ?? '',
-            'links' => $links,
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('service_centers');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '서비스센터가 수정되었습니다.');
-    }
-
-    /**
-     * Delete service center
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('service_centers');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '서비스센터가 삭제되었습니다.');
-    }
-
-    /**
-     * Toggle service center active status
-     */
-    public function toggleActive($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('service_centers');
-        $serviceCenter = $builder->where('id', $id)->get()->getRow();
-
-        if (!$serviceCenter) {
-            return $this->respondError('서비스센터를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // 현재 상태의 반대로 변경
-        $newStatus = $serviceCenter->is_active == 1 ? 0 : 1;
-
-        $builder->where('id', $id)->update([
-            'is_active' => $newStatus,
-            'updated_at' => date('Y-m-d H:i:s')
-        ]);
-
-        $statusText = $newStatus == 1 ? '사용' : '비사용';
-        return $this->respondSuccess(['is_active' => $newStatus], "서비스센터가 {$statusText} 상태로 변경되었습니다.");
-    }
-
-    /**
-     * Get public service center list (No authentication required)
-     * 공개 API - 인증 없이 활성 서비스센터 목록 조회
-     */
-    public function publicList()
-    {
-        try {
-            $builder = $this->getDB()->table('service_centers sc');
-            $builder->select('sc.*, b.name as branch_name');
-            $builder->join('branches b', 'sc.branch_id = b.id', 'left');
-
-            // 활성화된 서비스센터만 조회
-            $builder->where('sc.is_active', 1);
-            $builder->orderBy('sc.id', 'DESC');
-
-            $serviceCenters = $builder->get()->getResult();
-
-            // Decode links JSON for each item
-            foreach ($serviceCenters as &$serviceCenter) {
-                if (!empty($serviceCenter->links)) {
-                    $serviceCenter->links = json_decode($serviceCenter->links, true);
-                } else {
-                    $serviceCenter->links = [];
-                }
-            }
-
-            return $this->respondSuccess($serviceCenters);
-        } catch (\Exception $e) {
-            log_message('error', 'ServiceCenter public list error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Get public single service center (No authentication required)
-     * 공개 API - 인증 없이 단일 서비스센터 정보 조회
-     */
-    public function publicShow($id = null)
-    {
-        try {
-            $builder = $this->getDB()->table('service_centers sc');
-            $builder->select('sc.*, b.name as branch_name');
-            $builder->join('branches b', 'sc.branch_id = b.id', 'left');
-            $builder->where('sc.id', $id);
-            $builder->where('sc.is_active', 1);
-            $serviceCenter = $builder->get()->getRow();
-
-            if (!$serviceCenter) {
-                return $this->respondError('서비스센터를 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-            }
-
-            // Decode links JSON
-            if (!empty($serviceCenter->links)) {
-                $serviceCenter->links = json_decode($serviceCenter->links, true);
-            } else {
-                $serviceCenter->links = [];
-            }
-
-            return $this->respondSuccess($serviceCenter);
-        } catch (\Exception $e) {
-            log_message('error', 'ServiceCenter public show error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-}

+ 0 - 306
backend/app/Controllers/Api/ShowroomController.php

@@ -1,306 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\HTTP\ResponseInterface;
-
-class ShowroomController extends BaseApiController
-{
-    /**
-     * Get showroom list
-     */
-    public function index()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $params = $this->getPaginationParams();
-        $builder = $this->getDB()->table('showrooms s');
-
-        // Join with branches table to get branch name
-        $builder->select('s.*, b.name as branch_name');
-        $builder->join('branches b', 's.branch_id = b.id', 'left');
-
-        // Search
-        $searchType = $this->request->getGet('search_type');
-        $searchKeyword = $this->request->getGet('search_keyword');
-        $isActive = $this->request->getGet('is_active');
-
-        if ($searchType && $searchKeyword) {
-            if ($searchType === 'name') {
-                $builder->like('s.name', $searchKeyword);
-            } elseif ($searchType === 'address') {
-                $builder->like('s.address', $searchKeyword);
-            } elseif ($searchType === 'phone') {
-                $builder->like('s.main_phone', $searchKeyword);
-            }
-        }
-
-        // is_active 필터링
-        if ($isActive !== null && $isActive !== '' && $isActive !== false) {
-            $builder->where('s.is_active', strval($isActive));
-        }
-
-        $builder->orderBy('s.id', 'DESC');
-        $result = $this->paginatedResponse($builder, $params);
-
-        // Decode links JSON for each item
-        foreach ($result['items'] as &$item) {
-            if (!empty($item->links)) {
-                $item->links = json_decode($item->links, true);
-            } else {
-                $item->links = [];
-            }
-        }
-
-        return $this->respondSuccess($result);
-    }
-
-    /**
-     * Get single showroom
-     */
-    public function show($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('showrooms s');
-        $builder->select('s.*, b.name as branch_name');
-        $builder->join('branches b', 's.branch_id = b.id', 'left');
-        $builder->where('s.id', $id);
-        $showroom = $builder->get()->getRow();
-
-        if (!$showroom) {
-            return $this->respondError('전시장을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // Decode links JSON
-        if (!empty($showroom->links)) {
-            $showroom->links = json_decode($showroom->links, true);
-        } else {
-            $showroom->links = [];
-        }
-
-        return $this->respondSuccess($showroom);
-    }
-
-    /**
-     * Create showroom
-     */
-    public function create()
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        try {
-            $json = $this->request->getJSON();
-
-            // Validation
-            if (empty($json->name)) {
-                return $this->respondError('전시장명을 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            if (empty($json->branch_id)) {
-                return $this->respondError('소속 지점을 선택하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            if (empty($json->phone)) {
-                return $this->respondError('대표번호를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            if (empty($json->address)) {
-                return $this->respondError('주소를 입력하세요.', ResponseInterface::HTTP_BAD_REQUEST);
-            }
-
-            // Encode links to JSON
-            $links = null;
-            if (!empty($json->links) && is_array($json->links)) {
-                $links = json_encode($json->links);
-            }
-
-            // 데이터 준비
-            $data = [
-                'name' => $json->name ?? '',
-                'branch_id' => $json->branch_id ?? null,
-                'main_phone' => $json->phone ?? '',
-                'address' => $json->address ?? '',
-                'detail_address' => $json->detail_address ?? '',
-                'latitude' => $json->latitude ?? null,
-                'longitude' => $json->longitude ?? null,
-                'business_hours' => $json->business_hours ?? '',
-                'quote_link' => $json->quote_link ?? '',
-                'test_drive_link' => $json->test_drive_link ?? '',
-                'links' => $links,
-                'created_at' => date('Y-m-d H:i:s')
-            ];
-
-            $builder = $this->getDB()->table('showrooms');
-            $result = $builder->insert($data);
-
-            if (!$result) {
-                return $this->respondError('전시장 등록에 실패했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-            }
-
-            return $this->respondSuccess(['id' => $this->getDB()->insertID()], '전시장이 등록되었습니다.');
-        } catch (\Exception $e) {
-            log_message('error', 'Showroom create error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다: ' . $e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Update showroom
-     */
-    public function update($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $json = $this->request->getJSON();
-
-        // Encode links to JSON
-        $links = null;
-        if (!empty($json->links) && is_array($json->links)) {
-            $links = json_encode($json->links);
-        }
-
-        // 데이터 준비
-        $data = [
-            'name' => $json->name ?? '',
-            'branch_id' => $json->branch_id ?? null,
-            'main_phone' => $json->phone ?? '',
-            'address' => $json->address ?? '',
-            'detail_address' => $json->detail_address ?? '',
-            'latitude' => $json->latitude ?? null,
-            'longitude' => $json->longitude ?? null,
-            'business_hours' => $json->business_hours ?? '',
-            'quote_link' => $json->quote_link ?? '',
-            'test_drive_link' => $json->test_drive_link ?? '',
-            'links' => $links,
-            'updated_at' => date('Y-m-d H:i:s')
-        ];
-
-        $builder = $this->getDB()->table('showrooms');
-        $builder->where('id', $id)->update($data);
-
-        return $this->respondSuccess(null, '전시장이 수정되었습니다.');
-    }
-
-    /**
-     * Delete showroom
-     */
-    public function delete($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('showrooms');
-        $builder->where('id', $id)->delete();
-
-        return $this->respondSuccess(null, '전시장이 삭제되었습니다.');
-    }
-
-    /**
-     * Toggle showroom active status
-     */
-    public function toggleActive($id = null)
-    {
-        $auth = $this->requireAuth();
-        if ($auth instanceof ResponseInterface) {
-            return $auth;
-        }
-
-        $builder = $this->getDB()->table('showrooms');
-        $showroom = $builder->where('id', $id)->get()->getRow();
-
-        if (!$showroom) {
-            return $this->respondError('전시장을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-        }
-
-        // 현재 상태의 반대로 변경
-        $newStatus = $showroom->is_active == 1 ? 0 : 1;
-
-        $builder->where('id', $id)->update([
-            'is_active' => $newStatus,
-            'updated_at' => date('Y-m-d H:i:s')
-        ]);
-
-        $statusText = $newStatus == 1 ? '사용' : '비사용';
-        return $this->respondSuccess(['is_active' => $newStatus], "전시장이 {$statusText} 상태로 변경되었습니다.");
-    }
-
-    /**
-     * Get public showroom list (No authentication required)
-     * 공개 API - 인증 없이 활성 전시장 목록 조회
-     */
-    public function publicList()
-    {
-        try {
-            $builder = $this->getDB()->table('showrooms s');
-            $builder->select('s.*, b.name as branch_name');
-            $builder->join('branches b', 's.branch_id = b.id', 'left');
-
-            // 활성화된 전시장만 조회
-            $builder->where('s.is_active', 1);
-            $builder->orderBy('s.id', 'DESC');
-
-            $showrooms = $builder->get()->getResult();
-
-            // Decode links JSON for each item
-            foreach ($showrooms as &$showroom) {
-                if (!empty($showroom->links)) {
-                    $showroom->links = json_decode($showroom->links, true);
-                } else {
-                    $showroom->links = [];
-                }
-            }
-
-            return $this->respondSuccess($showrooms);
-        } catch (\Exception $e) {
-            log_message('error', 'Showroom public list error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    /**
-     * Get public single showroom (No authentication required)
-     * 공개 API - 인증 없이 단일 전시장 정보 조회
-     */
-    public function publicShow($id = null)
-    {
-        try {
-            $builder = $this->getDB()->table('showrooms s');
-            $builder->select('s.*, b.name as branch_name');
-            $builder->join('branches b', 's.branch_id = b.id', 'left');
-            $builder->where('s.id', $id);
-            $builder->where('s.is_active', 1);
-            $showroom = $builder->get()->getRow();
-
-            if (!$showroom) {
-                return $this->respondError('전시장을 찾을 수 없습니다.', ResponseInterface::HTTP_NOT_FOUND);
-            }
-
-            // Decode links JSON
-            if (!empty($showroom->links)) {
-                $showroom->links = json_decode($showroom->links, true);
-            } else {
-                $showroom->links = [];
-            }
-
-            return $this->respondSuccess($showroom);
-        } catch (\Exception $e) {
-            log_message('error', 'Showroom public show error: ' . $e->getMessage());
-            return $this->respondError('서버 오류가 발생했습니다.', ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
-        }
-    }
-}

+ 0 - 20
backend/app/Controllers/Api/TestController.php

@@ -1,20 +0,0 @@
-<?php
-
-namespace App\Controllers\Api;
-
-use CodeIgniter\RESTful\ResourceController;
-
-class TestController extends ResourceController
-{
-    protected $format = 'json';
-
-    public function index()
-    {
-        return $this->respond([
-            'success' => true,
-            'message' => 'CodeIgniter API is working!',
-            'php_version' => phpversion(),
-            'time' => date('Y-m-d H:i:s')
-        ]);
-    }
-}

+ 84 - 18
db.vuerd.json

@@ -4,8 +4,8 @@
   "settings": {
     "width": 2000,
     "height": 2000,
-    "scrollTop": -5,
-    "scrollLeft": -2,
+    "scrollTop": 0,
+    "scrollLeft": -24,
     "zoomLevel": 1,
     "show": 431,
     "database": 4,
@@ -42,26 +42,32 @@
     "tableEntities": {
       "FISm81kEr0UVuk9sKYz4j": {
         "id": "FISm81kEr0UVuk9sKYz4j",
-        "name": "admin",
-        "comment": "",
+        "name": "fishing_field",
+        "comment": "낚시분야",
         "columnIds": [
           "tVtTK-A1GhU8WRdiZT89G",
-          "ZohCM1jv0ehj-396ANamu"
+          "ZohCM1jv0ehj-396ANamu",
+          "cKDPY2OPmJguD0EaEO6AK",
+          "LTAAwKOsboOXo0QloDDKe",
+          "ynAe4Kk4ZBjsyu2n7LhYc"
         ],
         "seqColumnIds": [
           "tVtTK-A1GhU8WRdiZT89G",
-          "ZohCM1jv0ehj-396ANamu"
+          "ZohCM1jv0ehj-396ANamu",
+          "cKDPY2OPmJguD0EaEO6AK",
+          "LTAAwKOsboOXo0QloDDKe",
+          "ynAe4Kk4ZBjsyu2n7LhYc"
         ],
         "ui": {
           "x": 117,
-          "y": 96,
+          "y": 95,
           "zIndex": 2,
-          "widthName": 60,
+          "widthName": 65,
           "widthComment": 60,
           "color": ""
         },
         "meta": {
-          "updateAt": 1779771451277,
+          "updateAt": 1779855737252,
           "createAt": 1779771222669
         }
       },
@@ -89,31 +95,31 @@
       "tVtTK-A1GhU8WRdiZT89G": {
         "id": "tVtTK-A1GhU8WRdiZT89G",
         "tableId": "FISm81kEr0UVuk9sKYz4j",
-        "name": "",
+        "name": "id",
         "comment": "",
-        "dataType": "",
+        "dataType": "INT",
         "default": "",
-        "options": 0,
+        "options": 10,
         "ui": {
-          "keys": 0,
+          "keys": 1,
           "widthName": 60,
           "widthComment": 60,
           "widthDataType": 60,
           "widthDefault": 60
         },
         "meta": {
-          "updateAt": 1779771235411,
+          "updateAt": 1779855722221,
           "createAt": 1779771235411
         }
       },
       "ZohCM1jv0ehj-396ANamu": {
         "id": "ZohCM1jv0ehj-396ANamu",
         "tableId": "FISm81kEr0UVuk9sKYz4j",
-        "name": "",
+        "name": "name",
         "comment": "",
-        "dataType": "",
+        "dataType": "VARCHAR",
         "default": "",
-        "options": 0,
+        "options": 8,
         "ui": {
           "keys": 0,
           "widthName": 60,
@@ -122,9 +128,69 @@
           "widthDefault": 60
         },
         "meta": {
-          "updateAt": 1779771451277,
+          "updateAt": 1779850280321,
           "createAt": 1779771451277
         }
+      },
+      "cKDPY2OPmJguD0EaEO6AK": {
+        "id": "cKDPY2OPmJguD0EaEO6AK",
+        "tableId": "FISm81kEr0UVuk9sKYz4j",
+        "name": "weight",
+        "comment": "가중치",
+        "dataType": "VARCHAR",
+        "default": "",
+        "options": 8,
+        "ui": {
+          "keys": 0,
+          "widthName": 60,
+          "widthComment": 60,
+          "widthDataType": 60,
+          "widthDefault": 60
+        },
+        "meta": {
+          "updateAt": 1779850403535,
+          "createAt": 1779850154790
+        }
+      },
+      "LTAAwKOsboOXo0QloDDKe": {
+        "id": "LTAAwKOsboOXo0QloDDKe",
+        "tableId": "FISm81kEr0UVuk9sKYz4j",
+        "name": "created_at",
+        "comment": "",
+        "dataType": "DATETIME",
+        "default": "",
+        "options": 8,
+        "ui": {
+          "keys": 0,
+          "widthName": 60,
+          "widthComment": 60,
+          "widthDataType": 60,
+          "widthDefault": 60
+        },
+        "meta": {
+          "updateAt": 1779850324785,
+          "createAt": 1779850233419
+        }
+      },
+      "ynAe4Kk4ZBjsyu2n7LhYc": {
+        "id": "ynAe4Kk4ZBjsyu2n7LhYc",
+        "tableId": "FISm81kEr0UVuk9sKYz4j",
+        "name": "updated_at",
+        "comment": "",
+        "dataType": "DATETIME",
+        "default": "",
+        "options": 0,
+        "ui": {
+          "keys": 0,
+          "widthName": 62,
+          "widthComment": 60,
+          "widthDataType": 60,
+          "widthDefault": 60
+        },
+        "meta": {
+          "updateAt": 1779850322708,
+          "createAt": 1779850242123
+        }
       }
     },
     "relationshipEntities": {},