Преглед на файлове

인플루언서 관리 페이지 추가

DESKTOP-T61HUSC\user преди 3 месеца
родител
ревизия
acffa0f065

+ 16 - 3
assets/scss/default.scss

@@ -361,14 +361,19 @@
             
             .custom-btn{
               height: 2.5rem;
-              background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
               border-radius: 10px;
               width: 10%;
-              color:#fff;
               font-weight: 500;
               transition: all 0.3s ease;
               box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
-              
+              color:#fff;
+              background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+              &.btn-white{
+                border:1px solid #e6ebf1;
+                color:#000;
+                margin-left: auto;
+                background:#fff;
+              }
               &:hover {
                 transform: translateY(-1px);
                 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
@@ -377,6 +382,12 @@
           }
           
           .cs--list{
+            &.card{
+              padding: 0 1rem 1rem 1rem;
+              display: flex;
+              gap: 1rem;
+              flex-wrap: wrap;
+            }
             .list{
               padding: 20px 25px;
               cursor: pointer;
@@ -431,6 +442,7 @@
                   color: #666;
                   font-weight: 500;
                   font-size: 0.9rem;
+                  white-space: nowrap;
                 }
                 
                 &.list--title { // TITLE
@@ -459,6 +471,7 @@
                 
                 &.status-completed {
                   color: #ffffff;
+                  white-space: nowrap;
                   background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                   padding: 4px 8px;
                   border-radius: 10px;

+ 1 - 0
backend/app/Config/Routes.php

@@ -42,6 +42,7 @@ $routes->get('user/getBrdName/(:num)', 'User::getBrdName/$1');
 $routes->post('dashboard/recentOrders', 'Dashboard::recentOrders'); // 최근 주문 내역
 $routes->post('dashboard/activeItems', 'Dashboard::activeItems'); // 진행중인 공동구매
 $routes->post('dashboard/influencerStats', 'Dashboard::influencerStats'); // 인플루언서별 통계
+$routes->post('dashboard/influencerOrderDetail', 'Dashboard::influencerOrderDetail'); // 인플루언서별 주문내역
 $routes->post('dashboard/summary', 'Dashboard::summary'); // 전체 통계 요약
 
 //$routes->post('user/search', 'User::userSearch');

+ 42 - 1
backend/app/Controllers/Dashboard.php

@@ -144,7 +144,7 @@ class Dashboard extends ResourceController
 
         try {
             $builder = $db->table('ITEM_ORDER_LIST D')
-                ->select('I.CONTACT_INF as INF_SEQ, INF.NICK_NAME as INF_NAME, COUNT(D.SEQ) as ORDER_COUNT')
+                ->select('I.CONTACT_INF as INF_SEQ, INF.NICK_NAME as INF_NAME, INF.NAME as INF_REAL_NAME, INF.PHONE, COUNT(D.SEQ) as ORDER_COUNT')
                 ->join('ITEM_LIST I', 'D.ITEM_SEQ = I.SEQ', 'inner')
                 ->join('USER_LIST INF', 'I.CONTACT_INF = INF.SEQ', 'left')
                 ->where('I.DEL_YN', 'N')
@@ -181,6 +181,46 @@ class Dashboard extends ResourceController
         }
     }
 
+    // 인플루언서별 판매된 주문 내역
+    public function influencerOrderDetail()
+    {
+        // 한국 시간으로 설정
+        date_default_timezone_set('Asia/Seoul');
+
+        $db = \Config\Database::connect();
+        $request = $this->request->getJSON(true);
+        
+        $contactInf = isset($request['CONTACT_INF']) ? $request['CONTACT_INF'] : null;
+        $companyNumber = isset($request['COMPANY_NUMBER']) ? $request['COMPANY_NUMBER'] : null;
+        
+        try {
+            $builder = $db->table('ITEM_LIST I')
+                ->select('I.*, COALESCE(SUM(D.QTY), 0) as QTY, COALESCE(COUNT(D.SEQ), 0) as ORDER_COUNT')
+                ->join('ITEM_ORDER_LIST D', 'I.SEQ = D.ITEM_SEQ', 'left')
+                ->where('I.DEL_YN', 'N')
+                ->groupBy('I.SEQ')
+                ->orderBy('I.ORDER_END_DATE', 'DESC');
+                
+            if (!empty($contactInf) && !empty($companyNumber)) {
+                $builder->where('I.CONTACT_INF', $contactInf)
+                       ->where('I.COMPANY_NUMBER', $companyNumber);
+            }
+            
+            $items = $builder->get()->getResultArray();
+            
+            return $this->respond([
+                'data' => $items,
+                'total' => count($items)
+            ], 200);
+        } catch (\Exception $e) {
+            return $this->respond([
+                'status' => 'fail',
+                'message' => 'DB 오류: ' . $e->getMessage()
+            ], 500);
+        }
+    }
+
+
     // 대시보드 전체 통계 요약
     public function summary()
     {
@@ -258,4 +298,5 @@ class Dashboard extends ResourceController
             ], 500);
         }
     }
+
 }

+ 10 - 10
components/common/header.vue

@@ -50,7 +50,7 @@
             >
               <button
                 @click="handleSubMenuClick(subMenu)"
-                :class="{ actv: subMenu.linkType === $route.path }"
+                :class="{ actv: $route.path.startsWith(subMenu.linkType) }"
               >
                 {{ subMenu.menuName }}
               </button>
@@ -206,12 +206,12 @@
           menuName: "공동구매",
           linkType: "/view/common/item",
         },
-        {
-          menuId: "menu03",
-          parentMenuId: "menu03",
-          menuName: "마감된 공동구매",
-          linkType: "/view/common/item/closed",
-        },
+        // {
+        //   menuId: "menu03",
+        //   parentMenuId: "menu03",
+        //   menuName: "마감된 공동구매",
+        //   linkType: "/view/common/item/closed",
+        // },
         // {
         //   menuId: "menu02",
         //   parentMenuId: "menu02",
@@ -295,15 +295,15 @@
 
     // 아코디언이 열려있지 않을 때만 페이지 기준으로 활성화 판단
     if (!activeSubmenu.value) {
-      // 현재 페이지 경로와 일치하는 경우
-      if (menu.linkType === route.path) {
+      // 현재 페이지 경로가 메뉴 링크를 포함하는 경우
+      if (route.path.startsWith(menu.linkType)) {
         return true;
       }
 
       // 하위 메뉴 중 하나가 현재 페이지인 경우
       if (menu.subMenus && menu.subMenus.length > 0) {
         const hasActiveSubMenu = menu.subMenus.some(
-          (subMenu) => subMenu.linkType === route.path
+          (subMenu) => route.path.startsWith(subMenu.linkType)
         );
         if (hasActiveSubMenu) {
           return true;

+ 407 - 0
pages/view/common/dashboard/contact.vue

@@ -0,0 +1,407 @@
+<template>
+  <div>
+    <div class="inner--headers">
+      <h2>{{ pageId }}</h2>
+      <div class="bread--crumbs--wrap">
+        <span>홈</span>
+        <span>{{ pageId }}</span>
+      </div>
+    </div>
+
+    <div class="data--list--wrap">
+      <div class="item--list--wrap">
+        <div class="cs--list--wrap">
+          <div class="cs--header">
+            <h3>인플루언서별 주문 건수</h3>
+          </div>
+          
+          <!-- 검색 영역 -->
+          <div class="cs--search-area">
+            <div class="search-controls">
+              <div class="search-filter">
+                <v-select
+                  v-model="filter"
+                  :items="filderArr"
+                  variant="outlined"
+                  density="compact"
+                  hide-details
+                ></v-select>
+              </div>
+              <div class="search-input">
+                <v-text-field
+                  v-model="searchModel"
+                  placeholder="검색어를 입력하세요"
+                  variant="outlined"
+                  density="compact"
+                  hide-details
+                  prepend-inner-icon="mdi-magnify"
+                  @keyup.enter="fnSearch(searchModel, filter)"
+                ></v-text-field>
+              </div>
+              <div class="search-actions">
+                <v-btn
+                  class="custom-btn mini btn--pp"
+                  color="primary"
+                  @click="fnSearch(searchModel, filter)"
+                >
+                  검색
+                </v-btn>
+                <v-btn
+                  class="custom-btn mini btn-white"
+                  color="secondary"
+                  variant="outlined"
+                  @click="resetSearch"
+                >
+                  초기화
+                </v-btn>
+              </div>
+            </div>
+          </div>
+          <div class="cs--list card" v-if="influencerStats.length > 0">
+            <div v-for="(stat, index) in paginatedInfluencers" :key="stat.INF_SEQ" class="influencer-profile-card" @click="toItemDetail(stat.INF_SEQ)">
+              <div class="profile-header">
+                <div class="profile-rank">{{ (currentPage - 1) * itemsPerPage + index + 1 }}</div>
+                <div class="profile-avatar">
+                  <div class="avatar-emoji">❤️</div>
+                </div>
+                <div class="profile-info">
+                  <div class="profile-name">
+                    <span class="nickname">{{ stat.INF_NAME || '닉네임 없음' }}</span>
+                    <span class="real-name">{{ stat.INF_REAL_NAME || '이름 없음' }}</span>
+                  </div>
+                  <div class="profile-contact">
+                    <span class="phone">{{ stat.PHONE || '연락처 없음' }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="profile-stats">
+                <div class="stat-item">
+                  <span class="stat-label">총 주문 건수</span>
+                  <span class="stat-value">{{ stat.ORDER_COUNT }}건</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="cs--list" v-else>
+            <div class="no-data">
+              <h4>인플루언서 데이터가 없습니다</h4>
+            </div>
+          </div>
+        </div>
+        <div class="item--pagination" v-if="influencerStats.length > 0">
+          <v-pagination
+            v-model="currentPage"
+            :length="Math.ceil(influencerStats.length / itemsPerPage)"
+          ></v-pagination>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import "@vuepic/vue-datepicker/dist/main.css";
+import dayjs from 'dayjs';
+  /************************************************************************
+|    레이아웃
+************************************************************************/
+  definePageMeta({
+    layout: "default",
+  });
+  /************************************************************************
+|   PROPS
+ ************************************************************************/
+  const props = defineProps({
+    propsData: {
+      type: Object,
+      default: () => {},
+    },
+  });
+  /************************************************************************
+|    스토어
+ ************************************************************************/
+  const useDtStore = useDetailStore();
+  const useAtStore = useAuthStore();
+  /************************************************************************
+|    전역
+ ************************************************************************/
+  const itemType = ref("G");
+  const memberType = useAtStore.auth.memberType;
+  const searchModel = ref("");
+  const selectedRange = ref('all');
+  const searchStartDate = ref("");
+  const searchEndDate = ref("");
+  const dateOptions = [
+    { label: '오늘', value: 'today' },
+    { label: '7일', value: '7d' },
+    { label: '1개월', value: '1m' },
+    { label: '3개월', value: '3m' },
+    { label: '전체', value: 'all' },
+  ]
+  const datePickerFormat = "yyyy-MM-dd";
+  const filter = ref("");
+  const filderArr = ref([
+    { title: "전체", value: "" },
+    { title: "인플루언서명", value: "name" },
+  ]);
+  const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
+  const router = useRouter();
+  const pageId = computed(() => {
+    return '인플루언서 정보';
+    //return memberType === 'INFLUENCER' ? '제품 관리 (파트너십)' : '제품 관리 (자사)';
+  });
+  const itemList = ref([]);
+  const allItemList = ref([]); // 전체 데이터 저장용
+  const itemsPerPage = 9;
+  const currentPage = ref(1);
+  const influencerStats = ref([]);
+  const allInfluencerStats = ref([]); // 전체 인플루언서 통계 저장용
+
+  /* eslint-disable */
+  /* prettier-ignore */
+
+  /************************************************************************
+|    함수(METHODS)
+************************************************************************/
+
+  const paginatedItems = computed(() => {
+    const start = (currentPage.value - 1) * itemsPerPage;
+    return itemList.value.slice(start, start + itemsPerPage);
+  });
+
+  const paginatedInfluencers = computed(() => {
+    const start = (currentPage.value - 1) * itemsPerPage;
+    return influencerStats.value.slice(start, start + itemsPerPage);
+  });
+
+  const toItemDetail = (__EVENT) => {
+    router.push({
+      path: "/view/common/dashboard/detail",
+    });
+    useDtStore.boardInfo.seq = __EVENT;
+  };
+
+  const loadInfluencerStats = async () => {
+    try {
+      const _req = {
+        COMPANY_NUMBER: useAtStore.auth.companyNumber,
+        MEMBER_TYPE: useAtStore.auth.memberType,
+        MEMBER_SEQ: useAtStore.auth.seq,
+      };
+      
+      const response = await useAxios().post('/dashboard/influencerStats', _req);
+      allInfluencerStats.value = response.data.data || response.data;
+      influencerStats.value = allInfluencerStats.value;
+    } catch (error) {
+      console.error('인플루언서 통계 로드 실패:', error);
+    }
+  };
+
+  const fnSearch = (__KEYWORD, __FILTER) => {
+    let filteredStats = [...allInfluencerStats.value];
+    
+    // 키워드 검색
+    if (__KEYWORD && __KEYWORD.trim() !== '') {
+      const keyword = __KEYWORD.toLowerCase();
+      filteredStats = filteredStats.filter(stat => {
+        if (__FILTER === 'name') {
+          return stat.INF_NAME && stat.INF_NAME.toLowerCase().includes(keyword);
+        } else {
+          // 전체 검색 (인플루언서 이름)
+          return stat.INF_NAME && stat.INF_NAME.toLowerCase().includes(keyword);
+        }
+      });
+    }
+    
+    influencerStats.value = filteredStats;
+    currentPage.value = 1; // 검색 시 첫 페이지로 이동
+  };
+
+  // 검색 초기화
+  const resetSearch = () => {
+    searchModel.value = '';
+    searchStartDate.value = null;
+    searchEndDate.value = null;
+    selectedRange.value = '';
+    influencerStats.value = [...allInfluencerStats.value];
+    currentPage.value = 1;
+  };
+
+
+  const goToDeliveryDetail = (item) => {
+    // 제품 정보를 스토어에 저장
+    useDtStore.boardInfo.seq = item.SEQ;
+    useDtStore.boardInfo.pageType = "D";
+    useDtStore.boardInfo.itemType = itemType.value;
+    
+    // 배송 관리 페이지로 이동
+    router.push({
+      path: "/view/common/deli/detail",
+      query: {
+        itemId: item.SEQ,
+        itemName: item.NAME,
+        price1: item.PRICE1,
+        price2: item.PRICE2 || item.PRICE1,
+        thumbFile: item.THUMB_FILE || ''
+      }
+    });
+  };
+
+  /************************************************************************
+|    WATCH
+************************************************************************/
+
+  onMounted(() => {
+    loadInfluencerStats();
+
+    // 날짜 초기화
+    const today = dayjs();
+    searchEndDate.value = today.format('YYYY-MM-DD');
+  });
+</script>
+
+<style scoped>
+.influencer-profile-card {
+  background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);
+  border: 2px solid transparent;
+  background-clip: padding-box;
+  border-radius: 20px;
+  padding: 24px;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  position: relative;
+  width: calc((100% - 2rem) / 3);
+  border: 1px solid #eee;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+}
+
+.influencer-profile-card:hover {
+  transform: translateY(-4px) scale(1.01);
+}
+
+.profile-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.profile-rank {
+  width: 36px;
+  height: 36px;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: bold;
+  font-size: 14px;
+  margin-right: 16px;
+  flex-shrink: 0;
+  position: relative;
+}
+
+.profile-avatar {
+  margin-right: 16px;
+  flex-shrink: 0;
+}
+
+.avatar-emoji {
+  width: 56px;
+  height: 56px;
+  border-radius: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28px;
+  animation: bounce 3s infinite;
+  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+  border-radius: 50%;
+}
+
+@keyframes bounce {
+  0%, 100% {
+    transform: translateY(0);
+  }
+  50% {
+    transform: translateY(-3px);
+  }
+}
+
+.profile-name {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  margin-bottom: 8px;
+}
+
+.nickname {
+  font-size: 18px;
+  font-weight: 700;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.real-name {
+  font-size: 14px;
+  color: #94a3b8;
+  font-weight: 500;
+}
+
+.profile-contact {
+  display: flex;
+  gap: 16px;
+  flex-wrap: wrap;
+}
+
+.user-id, .phone {
+  font-size: 13px;
+  color: #475569;
+  padding: 6px 12px;
+  border-radius: 20px;
+  font-weight: 500;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+  transition: all 0.2s ease;
+}
+
+.profile-stats {
+  border-top: 2px dashed #e2e8f0;
+  padding-top: 16px;
+  margin-top: 16px;
+}
+
+.stat-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.stat-label {
+  font-size: 14px;
+  color: #6b7280;
+}
+
+.stat-value {
+  font-size: 20px;
+  font-weight: bold;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.no-data {
+  text-align: center;
+  padding: 40px;
+  color: #6b7280;
+}
+
+.no-data h4 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 500;
+}
+</style>

+ 258 - 0
pages/view/common/dashboard/detail.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="modern-item-add">
+    <!-- 메인 컨텐츠 -->
+    <div class="main-content">
+      <div class="form-container">
+        <v-form ref="addForm" class="modern-form">
+          <div class="form-section">
+            <h3 class="section-title">
+              <i class="mdi mdi-package-variant"></i>
+              {{ pageId }}
+            </h3>
+
+            <div class="form-field-group group--2">
+              <div class="form-field">
+                <label class="field-label">인플루언서</label>
+                <div class="field-content">
+                  <div class="display-value date-range">
+                    <i class="mdi mdi-account-circle"></i>
+                    {{ contact_inf_nickName || "닉네임 없음" }} ({{ contact_inf_name || "이름 없음" }})
+                  </div>
+                </div>
+              </div>
+              <div class="form-field">
+                <label class="field-label">연락처</label>
+                <div class="field-content">
+                  <div  class="display-value date-range">
+                    <i class="mdi mdi-phone"></i>
+                    {{ contact_inf_phone || "연락처 없음" }}
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div class="form-field">
+              <div class="field-content">
+                <div class="btn--actions--wrap pb--rem">
+                  <div class="left--sections">
+                    <label class="mb--0 field-label">공동구매 주문 내역</label>
+                  </div>
+                </div>
+                <div class="tbl-wrapper">
+                  <div class="tbl-wrap">
+                    <!-- ag grid -->
+                    <ag-grid-vue
+                      :style="{ width: '100%', height: gridHeight }"
+                      class="ag-theme-quartz order--table"
+                      :gridOptions="gridOptions"
+                      rowSelection="multiple"
+                      :rowData="tblItems"
+                      :paginationPageSize="pageObj.pageSize"
+                      :suppressPaginationPanel="true"
+                      @grid-ready="onGridReady"
+                      @cell-value-changed="onCellValueChanged"
+                    >
+                    </ag-grid-vue>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </v-form>
+      </div>
+
+      <!-- 액션 버튼 -->
+      <div class="action-buttons">
+        <div class="button-group left">
+          <v-btn
+            class="action-btn secondary"
+            variant="outlined"
+            @click="fnBtnEvt()"
+          >
+            <i class="mdi mdi-format-list-bulleted"></i>
+            목록으로
+          </v-btn>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import useAxios from "@/composables/useAxios";
+import "@vuepic/vue-datepicker/dist/main.css";
+import { AgGridVue } from "ag-grid-vue3";
+/************************************************************************
+|    레이아웃
+************************************************************************/
+definePageMeta({
+  layout: "default",
+});
+
+/************************************************************************
+|    스토어
+ ************************************************************************/
+const useDtStore = useDetailStore();
+const useAtStore = useAuthStore();
+
+/************************************************************************
+|    전역
+ ************************************************************************/
+const memberType = useAtStore.auth.memberType;
+const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
+const router = useRouter();
+const pageId = ref("");
+let pageObj = ref({
+  page: 1, // 현재 페이지
+  pageMaxNumSize: 10, // 페이지 숫자 최대 표현 개수
+  pageSize: 10, // 테이블 조회 데이터 개수
+  totalCnt: 0, // 전체 페이지
+});
+const apiUrl = ref("");
+apiUrl.value = import.meta.env.VITE_APP_API_URL;
+const contact_inf_nickName = ref("");
+const contact_inf_name = ref("");
+const contact_inf_phone = ref("");
+
+const pageType = ref("");
+
+// ag-grid 관련 변수
+const tblItems = ref([]);
+const gridApi = ref(null);
+const gridOptions = ref({
+  columnDefs: [
+    {
+      headerName: "No",
+      valueGetter: (params) => params.api.getDisplayedRowCount() - params.node.rowIndex,
+      sortable: false,
+      filter: false,
+      width: 10,
+    },
+    {
+      headerName: "제품명",
+      field: "NAME",
+    },
+    {
+      headerName: "주문 건수",
+      field: "ORDER_COUNT",
+      width: 10,
+      valueFormatter: (params) => {
+        return params.value ? Number(params.value).toLocaleString() : '0';
+      }
+    },
+    {
+      headerName: "총 수량",
+      field: "QTY",
+      width: 10,
+      valueFormatter: (params) => {
+        return params.value ? Number(params.value).toLocaleString() : '0';
+      }
+    },
+    {
+      headerName: "공동구매 기간",
+      field: "ORDER_START_DATE",
+      width: 60,
+      valueFormatter: (params) => {
+        const startDate = params.data.ORDER_START_DATE ? params.data.ORDER_START_DATE.slice(0, 10) : '';
+        const endDate = params.data.ORDER_END_DATE ? params.data.ORDER_END_DATE.slice(0, 10) : '';
+        return startDate && endDate ? `${startDate} ~ ${endDate}` : '';
+      }
+    },
+  ],
+  autoSizeStrategy: {
+    type: "fitGridWidth", // width맞춤
+  },
+  suppressHorizontalScroll: true, // 가로 스크롤 제거
+  defaultColDef: {
+    sortable: false,
+    filter: false,
+    resizable: false,
+  },
+  suppressMovableColumns: true,
+  suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
+  localeText: {
+    noRowsToShow: '공동구매 주문 내역이 없습니다.'
+  }
+});
+
+/************************************************************************
+ |    함수(METHODS)
+ ************************************************************************/
+
+ // 동적 높이 계산
+const gridHeight = computed(() => {
+  const rowCount = tblItems.value.length;
+  const minRows = 8; // 최소 5줄 높이
+  const maxRows = 10; // 최대 15줄 높이 (스크롤 시작점)
+  const rowHeight = 2.94; // rem 단위
+  
+  if (rowCount <= minRows) {
+    return `calc(${minRows} * ${rowHeight}rem)`;
+  } else if (rowCount > maxRows) {
+    return `calc(${maxRows} * ${rowHeight}rem)`;
+  } else {
+    return `calc(${rowCount} * ${rowHeight}rem)`;
+  }
+});
+
+// 인플루언서 이름 조회
+const getInfName = async () => {
+  try {
+    const response = await useAxios().get(`/user/getInfName/${useDtStore.boardInfo.seq}`);
+
+    contact_inf_nickName.value = response.data.data.NICK_NAME;
+    contact_inf_name.value = response.data.data.NAME;
+    contact_inf_phone.value = response.data.data.PHONE;
+
+  } catch (error) {
+    return null;
+  }
+};
+
+/*======================================================================
+|    작성 시퀀스
+| 1. 작성 컨펌
+| 2. 버튼 체크
+| 3. 등록시 -> 등록 API 호출
+======================================================================*/
+
+const fnBtnEvt = () => {
+  router.push({
+      path: "/view/common/dashboard/contact",
+    });
+};
+
+// ag-grid 관련 함수들
+const onGridReady = (params) => {
+  gridApi.value = params.api;
+};
+
+const fnDetail = async () => {
+  let _req = {
+    // Y : 노출, N : 비노출
+    SHOW_YN: "Y",
+    TYPE: "G",
+    CONTACT_INF: useDtStore.boardInfo.seq,
+    MEMBER_TYPE: memberType,
+    MEMBER_SEQ: useAtStore.auth.seq,
+    COMPANY_NUMBER: useAtStore.auth.companyNumber || "1"
+  };
+  
+  await useAxios()
+  .post("/dashboard/influencerOrderDetail", _req)
+  .then((res) => {
+      //console.error(res.data.data)
+      tblItems.value = res.data.data;
+    });
+};
+
+/************************************************************************
+|    라이프사이클
+************************************************************************/
+onMounted(() => {
+  pageId.value = "인플루언서 상세"
+  fnDetail();
+  getInfName();
+});
+
+</script>

+ 5 - 1
pages/view/common/dashboard/index.vue

@@ -167,7 +167,7 @@
                 <p>인플루언서 정보가 없습니다.</p>
               </div>
               <div class="view-all">
-                <v-btn variant="text" class="custom-btn btn-white" @click="showComingSoon()">전체 보기</v-btn>
+                <v-btn variant="text" class="custom-btn btn-white" @click="goToContact()">전체 보기</v-btn>
               </div>
             </div>
           </div>
@@ -304,6 +304,10 @@
       loadInfluencerStats(),
     ]);
   };
+
+  const goToContact = () => {
+    router.push('/view/common/dashboard/contact');
+  };
   
   const goToItems = () => {
     router.push('/view/common/item');

+ 11 - 1
pages/view/common/item/closed.vue

@@ -11,6 +11,10 @@
     <div class="data--list--wrap">
       <div class="item--list--wrap">
         <div class="cs--list--wrap">
+          <div class="cs--header">
+            <h3>마감된 공동구매</h3>
+            <v-btn class="custom-btn btn-white" @click="closedLocated()">진행중인 공동구매</v-btn>
+          </div>
           <div class="cs--header">
             <h3>나의 공동구매</h3>
           </div>
@@ -152,7 +156,7 @@ import dayjs from 'dayjs';
   const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
   const router = useRouter();
   const pageId = computed(() => {
-    return '마감된 공동구매';
+    return '공동구매';
     //return memberType === 'INFLUENCER' ? '제품 관리 (파트너십)' : '제품 관리 (자사)';
   });
   const itemList = ref([]);
@@ -167,6 +171,12 @@ import dayjs from 'dayjs';
 |    함수(METHODS)
 ************************************************************************/
 
+  const closedLocated = () => {
+    router.push({
+      path: "/view/common/item",
+    });
+  };
+
   const paginatedItems = computed(() => {
     const start = (currentPage.value - 1) * itemsPerPage;
     return itemList.value.slice(start, start + itemsPerPage);

+ 8 - 1
pages/view/common/item/index.vue

@@ -27,7 +27,8 @@
       <div class="item--list--wrap">
         <div class="cs--list--wrap">
           <div class="cs--header" v-if="memberType !== 'INFLUENCER'">
-            <h3>공동구매 등록</h3>
+            <h3>진행중인 공동구매</h3>
+            <v-btn v-if="memberType !== 'INFLUENCER'" class="custom-btn btn-white" style="margin-right: 1rem;" @click="closedLocated()">마감된 공동구매</v-btn>
             <v-btn class="custom-btn" @click="addLocated()">신규 등록</v-btn>
           </div>
           <div class="cs--header">
@@ -223,6 +224,12 @@ import dayjs from 'dayjs';
     }
   }
 
+  const closedLocated = () => {
+    router.push({
+      path: "/view/common/item/closed",
+    });
+  };
+
   const addLocated = () => {
     router.push({
       path: "/view/common/item/add",