| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142 |
- <template>
- <div>
- <div class="inner--headers">
- <h2>{{ pageId }}</h2>
- <div class="bread--crumbs--wrap">
- <span>홈</span>
- <span>벤더 대시보드</span>
- <span>{{ pageId }}</span>
- </div>
- </div>
- <!-- 통계 카드 -->
- <div class="stats--cards--wrap">
- <div class="stats--card">
- <div class="stats--icon pending">
- <v-icon>mdi-clock-outline</v-icon>
- </div>
- <div class="stats--content">
- <h3>{{ stats.pending || 0 }}</h3>
- <p>대기 중인 승인요청</p>
- </div>
- </div>
- <div class="stats--card">
- <div class="stats--icon approved">
- <v-icon>mdi-check-circle</v-icon>
- </div>
- <div class="stats--content">
- <h3>{{ stats.approved || 0 }}</h3>
- <p>승인 완료</p>
- </div>
- </div>
- <div class="stats--card">
- <div class="stats--icon rejected">
- <v-icon>mdi-close-circle</v-icon>
- </div>
- <div class="stats--content">
- <h3>{{ stats.rejected || 0 }}</h3>
- <p>거부</p>
- </div>
- </div>
- <div class="stats--card">
- <div class="stats--icon total">
- <v-icon>mdi-account-group</v-icon>
- </div>
- <div class="stats--content">
- <h3>{{ stats.total || 0 }}</h3>
- <p>총 요청 수</p>
- </div>
- </div>
- </div>
- <!-- 필터 및 검색 -->
- <div class="search--modules type2">
- <div class="search--inner">
- <div class="form--cont--filter">
- <v-select
- v-model="searchFilter.status"
- :items="statusOptions"
- variant="outlined"
- class="custom-select"
- label="상태"
- clearable
- >
- </v-select>
- </div>
- <div class="form--cont--filter">
- <v-select
- v-model="searchFilter.category"
- :items="categoryOptions"
- variant="outlined"
- class="custom-select"
- label="인플루언서 카테고리"
- clearable
- >
- </v-select>
- </div>
- <div class="form--cont--text">
- <v-text-field
- v-model="searchFilter.keyword"
- class="custom-input mini"
- style="width: 100%"
- placeholder="인플루언서명을 입력하세요"
- @keyup.enter="handleSearch"
- ></v-text-field>
- </div>
- </div>
- <v-btn
- class="custom-btn btn-blue mini sch--btn"
- @click="handleSearch"
- :loading="loading"
- >
- 검색
- </v-btn>
- </div>
- <!-- 인플루언서 승인 요청 목록 -->
- <div class="data--list--wrap">
- <div class="btn--actions--wrap">
- <div class="left--sections">
- <span class="result-count">
- 총 {{ pagination.totalCount || 0 }}개의 승인요청
- </span>
- </div>
- <div class="right--sections">
- <v-select
- v-model="sortOption"
- :items="sortOptions"
- variant="outlined"
- class="custom-select mini"
- @update:model-value="handleSort"
- >
- </v-select>
- </div>
- </div>
- <!-- 로딩 상태 -->
- <div v-if="loading" class="loading-wrap">
- <v-progress-circular indeterminate color="primary"></v-progress-circular>
- <p>승인요청을 불러오고 있습니다...</p>
- </div>
- <!-- 에러 상태 -->
- <div v-else-if="error" class="error-wrap">
- <v-alert type="error" dismissible @click:close="error = null">
- {{ error }}
- </v-alert>
- </div>
- <!-- 승인요청 리스트 -->
- <div v-else-if="requests.length > 0" class="requests--list--wrap">
- <div class="requests--grid">
- <div
- v-for="request in requests"
- :key="request.SEQ"
- class="request--card"
- :class="getRequestStatusClass(request.STATUS)"
- >
- <!-- 카드 헤더 -->
- <div class="request--card--header">
- <div class="influencer--info">
- <div class="influencer--avatar">
- <v-img
- v-if="request.influencerAvatar"
- :src="request.influencerAvatar"
- :alt="request.influencerNickname + ' 프로필'"
- width="50"
- height="50"
- ></v-img>
- <div v-else class="no-avatar">
- {{ request.influencerNickname?.charAt(0) || "U" }}
- </div>
- </div>
- <div class="influencer--details">
- <div class="influencer--header">
- <h4>{{ request.influencerNickname || request.influencerName }}</h4>
- <p class="influencer--category">
- {{ getCategoryText(request.influencerCategory) }}
- </p>
- </div>
- <div class="influencer--contact">
- <p v-if="request.influencerEmail" class="contact--item">
- <v-icon size="small">mdi-email</v-icon>
- {{ request.influencerEmail }}
- </p>
- <p v-if="request.influencerPhone" class="contact--item">
- <v-icon size="small">mdi-phone</v-icon>
- {{ request.influencerPhone }}
- </p>
- <p v-if="request.influencerRegion" class="contact--item">
- <v-icon size="small">mdi-map-marker</v-icon>
- {{ request.influencerRegion }}
- </p>
- </div>
- <div class="influencer--meta">
- <span v-if="request.followerCount" class="meta--item">
- <v-icon size="small">mdi-account-group</v-icon>
- {{ formatNumber(request.followerCount) }} 팔로워
- </span>
- <span v-if="request.avgViews" class="meta--item">
- <v-icon size="small">mdi-eye</v-icon>
- 평균 {{ formatNumber(request.avgViews) }} 조회
- </span>
- <span v-if="request.engagementRate" class="meta--item">
- <v-icon size="small">mdi-chart-line</v-icon>
- 참여율 {{ request.engagementRate }}%
- </span>
- </div>
- <div v-if="request.influencerDescription" class="influencer--description">
- <p>{{ request.influencerDescription }}</p>
- </div>
- <div v-if="request.influencerSnsChannels" class="influencer--sns">
- <div v-for="(channel, index) in parseSnsChannels(request.influencerSnsChannels)"
- :key="index"
- class="sns--item">
- <v-icon size="small">{{ getSnsIcon(channel.platform) }}</v-icon>
- {{ channel.handle }}
- </div>
- </div>
- </div>
- </div>
- <div class="request--status">
- <v-chip :color="getStatusColor(request.STATUS)" size="small">
- {{ getStatusText(request.STATUS) }}
- </v-chip>
- <p class="request--date">{{ formatDate(request.REQUEST_DATE) }}</p>
- </div>
- </div>
- <!-- 카드 바디 -->
- <div class="request--card--body">
- <div v-if="request.REQUEST_MESSAGE" class="request--message">
- <h5>요청 메시지</h5>
- <p>"{{ request.REQUEST_MESSAGE }}"</p>
- </div>
- <div
- v-if="request.COMMISSION_RATE || request.SPECIAL_CONDITIONS"
- class="request--conditions"
- >
- <h5>희망 조건</h5>
- <div v-if="request.COMMISSION_RATE" class="condition--item">
- <span class="condition--label">희망 수수료율:</span>
- <span class="condition--value">{{ request.COMMISSION_RATE }}%</span>
- </div>
- <div v-if="request.SPECIAL_CONDITIONS" class="condition--item">
- <span class="condition--label">특별 조건:</span>
- <span class="condition--value">{{ request.SPECIAL_CONDITIONS }}</span>
- </div>
- </div>
- <div v-if="request.STATUS === 'PENDING'" class="expire--info">
- <v-icon size="16" color="warning">mdi-clock-alert</v-icon>
- <span>만료일: {{ formatDate(request.EXPIRED_DATE) }}</span>
- </div>
- </div>
- <!-- 카드 푸터 (액션 버튼) -->
- <div class="request--card--footer">
- <div class="card--actions">
- <v-btn
- class="custom-btn mini btn-outline"
- @click="viewInfluencerDetail(request.INFLUENCER_SEQ)"
- >
- 프로필 보기
- </v-btn>
- <div v-if="request.STATUS === 'PENDING'" class="approval--actions">
- <v-btn
- class="custom-btn mini btn-red"
- @click="handleReject(request)"
- :loading="processing"
- >
- 거부
- </v-btn>
- <v-btn
- class="custom-btn mini btn-blue"
- @click="handleApprove(request)"
- :loading="processing"
- >
- 승인
- </v-btn>
- </div>
- <v-btn
- v-else
- class="custom-btn mini btn-outline"
- @click="viewRequestHistory(request.SEQ)"
- >
- 이력보기
- </v-btn>
- </div>
- </div>
- </div>
- </div>
- <!-- 페이지네이션 -->
- <div class="pagination-wrap" v-if="pagination.totalPages > 1">
- <v-pagination
- v-model="currentPage"
- :length="pagination.totalPages"
- :total-visible="7"
- @update:model-value="handlePageChange"
- ></v-pagination>
- </div>
- </div>
- <!-- 검색 결과 없음 -->
- <div v-else class="no-data-wrap">
- <div class="no-data">
- <v-icon size="64" color="grey-lighten-1">mdi-account-search</v-icon>
- <h3>승인요청이 없습니다</h3>
- <p>아직 인플루언서로부터 승인요청이 없습니다</p>
- </div>
- </div>
- </div>
- <!-- 승인 확인 모달 -->
- <v-dialog v-model="approveModal.show" max-width="500px">
- <v-card>
- <v-card-title class="text-h5 text-success">
- <v-icon left>mdi-check-circle</v-icon>
- 승인 확인
- </v-card-title>
- <v-card-text>
- <div class="approve--content">
- <div class="influencer--summary">
- <div class="influencer--avatar--small">
- <v-img
- v-if="approveModal.request?.influencerAvatar"
- :src="approveModal.request.influencerAvatar"
- width="40"
- height="40"
- ></v-img>
- <div v-else class="no-avatar--small">
- {{ approveModal.request?.influencerNickname?.charAt(0) || "U" }}
- </div>
- </div>
- <div>
- <h4>{{ approveModal.request?.influencerNickname }}</h4>
- <p>{{ getCategoryText(approveModal.request?.influencerCategory) }}</p>
- </div>
- </div>
- <p>이 인플루언서의 승인요청을 승인하시겠습니까?</p>
- <v-textarea
- v-model="approveModal.approveMessage"
- label="승인 메시지 (선택사항)"
- placeholder="인플루언서에게 전달할 메시지를 입력해주세요..."
- rows="3"
- counter="300"
- maxlength="300"
- class="mt-4"
- ></v-textarea>
- </div>
- </v-card-text>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn color="grey" variant="text" @click="closeApproveModal">취소</v-btn>
- <v-btn color="success" @click="confirmApprove" :loading="processing">
- 승인하기
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- <!-- 거부 확인 모달 -->
- <v-dialog v-model="rejectModal.show" max-width="500px">
- <v-card>
- <v-card-title class="text-h5 text-error">
- <v-icon left>mdi-close-circle</v-icon>
- 거부 확인
- </v-card-title>
- <v-card-text>
- <div class="reject--content">
- <div class="influencer--summary">
- <div class="influencer--avatar--small">
- <v-img
- v-if="rejectModal.request?.influencerAvatar"
- :src="rejectModal.request.influencerAvatar"
- width="40"
- height="40"
- ></v-img>
- <div v-else class="no-avatar--small">
- {{ rejectModal.request?.influencerNickname?.charAt(0) || "U" }}
- </div>
- </div>
- <div>
- <h4>{{ rejectModal.request?.influencerNickname }}</h4>
- <p>{{ getCategoryText(rejectModal.request?.influencerCategory) }}</p>
- </div>
- </div>
- <p>이 인플루언서의 승인요청을 거부하시겠습니까?</p>
- <v-textarea
- v-model="rejectModal.rejectReason"
- label="거부 사유"
- placeholder="거부 사유를 입력해주세요..."
- rows="4"
- counter="500"
- maxlength="500"
- class="mt-4"
- required
- ></v-textarea>
- </div>
- </v-card-text>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn color="grey" variant="text" @click="closeRejectModal">취소</v-btn>
- <v-btn color="error" @click="confirmReject" :loading="processing">
- 거부하기
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- </div>
- </template>
- <script setup>
- import { ref, onMounted, computed } from "vue";
- import { useRouter } from "vue-router";
- /************************************************************************
- | 레이아웃
- ************************************************************************/
- definePageMeta({
- layout: "default",
- });
- /************************************************************************
- | 스토어 & 라우터
- ************************************************************************/
- const router = useRouter();
- const { $toast } = useNuxtApp();
- /************************************************************************
- | 반응형 데이터
- ************************************************************************/
- const pageId = ref("인플루언서 승인요청 관리");
- const loading = ref(false);
- const processing = ref(false);
- const error = ref(null);
- const currentPage = ref(1);
- // 검색 필터
- const searchFilter = ref({
- keyword: "",
- status: "",
- category: "",
- });
- // 정렬 옵션
- const sortOption = ref("latest");
- const sortOptions = ref([
- { title: "최신순", value: "latest" },
- { title: "오래된순", value: "oldest" },
- { title: "마감임박순", value: "expiring" },
- ]);
- // 상태 옵션
- const statusOptions = ref([
- { title: "전체", value: "" },
- { title: "대기중", value: "PENDING" },
- { title: "승인완료", value: "APPROVED" },
- { title: "거부됨", value: "REJECTED" },
- ]);
- // 카테고리 옵션
- const categoryOptions = ref([
- { title: "전체", value: "" },
- { title: "패션·뷰티", value: "FASHION_BEAUTY" },
- { title: "식품·건강", value: "FOOD_HEALTH" },
- { title: "라이프스타일", value: "LIFESTYLE" },
- { title: "테크·가전", value: "TECH_ELECTRONICS" },
- { title: "스포츠·레저", value: "SPORTS_LEISURE" },
- { title: "문화·엔터테인먼트", value: "CULTURE_ENTERTAINMENT" },
- ]);
- // 데이터
- const requests = ref([]);
- const stats = ref({
- pending: 0,
- approved: 0,
- rejected: 0,
- total: 0,
- });
- const pagination = ref({
- currentPage: 1,
- totalPages: 1,
- totalCount: 0,
- pageSize: 12,
- });
- // 승인 모달
- const approveModal = ref({
- show: false,
- request: null,
- approveMessage: "",
- });
- // 거부 모달
- const rejectModal = ref({
- show: false,
- request: null,
- rejectReason: "",
- });
- /************************************************************************
- | computed
- ************************************************************************/
- const currentUser = computed(() => {
- const authData = JSON.parse(localStorage.getItem("authStore"))?.auth || {};
- console.log('🔍 currentUser (벤더 대시보드):', authData);
- return authData;
- });
- /************************************************************************
- | 메서드
- ************************************************************************/
- const handleSearch = async () => {
- currentPage.value = 1;
- await loadRequests();
- };
- const handlePageChange = async (page) => {
- currentPage.value = page;
- await loadRequests();
- };
- const handleSort = async () => {
- currentPage.value = 1;
- await loadRequests();
- };
- const loadRequests = async () => {
- try {
- loading.value = true;
- error.value = null;
- const params = {
- vendorSeq: currentUser.value.seq,
- keyword: searchFilter.value.keyword,
- status: searchFilter.value.status,
- category: searchFilter.value.category,
- sortBy: sortOption.value,
- page: currentPage.value,
- size: pagination.value.pageSize,
- };
- console.log('🔍 loadRequests 호출됨:', params);
-
- useAxios()
- .post("/api/vendor-influencer/requests", params)
- .then((res) => {
- console.log('📥 API 응답:', res.data);
- if (res.data.success) {
- const items = res.data.data.items;
- console.log('📋 받아온 요청 목록:', items.length, items);
-
- // SEQ 중복 확인
- const seqs = items.map(item => item.SEQ);
- const uniqueSeqs = [...new Set(seqs)];
- if (seqs.length !== uniqueSeqs.length) {
- console.warn('⚠️ 중복된 SEQ 발견:', seqs);
- }
-
- requests.value = items;
- pagination.value = res.data.data.pagination;
- stats.value = res.data.data.stats || stats.value;
- } else {
- error.value =
- res.data.message || "승인요청 목록을 불러오는 중 오류가 발생했습니다.";
- }
- })
- .catch((err) => {
- error.value = err.message || "승인요청 목록을 불러오는 중 오류가 발생했습니다.";
- })
- .finally(() => {
- loading.value = false;
- });
- } catch (err) {
- error.value = err.message || "승인요청 목록을 불러오는 중 오류가 발생했습니다.";
- loading.value = false;
- }
- };
- const handleApprove = (request) => {
- approveModal.value = {
- show: true,
- request: request,
- approveMessage: "",
- };
- };
- const closeApproveModal = () => {
- approveModal.value = {
- show: false,
- request: null,
- approveMessage: "",
- };
- };
- const confirmApprove = async () => {
- try {
- processing.value = true;
- const params = {
- mappingSeq: approveModal.value.request.SEQ,
- action: "APPROVE",
- processedBy: currentUser.value.seq,
- responseMessage: approveModal.value.approveMessage,
- };
-
- console.log('✅ 승인 처리 시작:', params);
- useAxios()
- .post("/api/vendor-influencer/approve", params)
- .then((res) => {
- console.log('📥 승인 처리 응답:', res.data);
- if (res.data.success) {
- $toast.success("승인요청이 승인되었습니다.");
- closeApproveModal();
- console.log('🔄 승인 후 목록 새로고침');
- loadRequests();
- } else {
- console.error('❌ 승인 처리 실패:', res.data);
- $toast.error(res.data.message || "승인 처리 중 오류가 발생했습니다.");
- }
- })
- .catch((err) => {
- $toast.error(err.message || "승인 처리 중 오류가 발생했습니다.");
- })
- .finally(() => {
- processing.value = false;
- });
- } catch (err) {
- $toast.error(err.message || "승인 처리 중 오류가 발생했습니다.");
- processing.value = false;
- }
- };
- const handleReject = (request) => {
- rejectModal.value = {
- show: true,
- request: request,
- rejectReason: "",
- };
- };
- const closeRejectModal = () => {
- rejectModal.value = {
- show: false,
- request: null,
- rejectReason: "",
- };
- };
- const confirmReject = async () => {
- if (!rejectModal.value.rejectReason.trim()) {
- $toast.error("거부 사유를 입력해주세요.");
- return;
- }
- try {
- processing.value = true;
- const params = {
- mappingSeq: rejectModal.value.request.SEQ,
- action: "REJECT",
- processedBy: currentUser.value.seq,
- responseMessage: rejectModal.value.rejectReason,
- };
- useAxios()
- .post("/api/vendor-influencer/approve", params)
- .then((res) => {
- if (res.data.success) {
- $toast.success("승인요청이 거부되었습니다.");
- closeRejectModal();
- loadRequests();
- } else {
- $toast.error(res.data.message || "거부 처리 중 오류가 발생했습니다.");
- }
- })
- .catch((err) => {
- $toast.error(err.message || "거부 처리 중 오류가 발생했습니다.");
- })
- .finally(() => {
- processing.value = false;
- });
- } catch (err) {
- $toast.error(err.message || "거부 처리 중 오류가 발생했습니다.");
- processing.value = false;
- }
- };
- const viewInfluencerDetail = (influencerSeq) => {
- router.push(`/view/influencer/${influencerSeq}`);
- };
- const viewRequestHistory = (requestSeq) => {
- router.push(`/view/vendor/request-history/${requestSeq}`);
- };
- // 유틸리티 함수들
- const getCategoryText = (category) => {
- const categoryMap = {
- FASHION_BEAUTY: "패션·뷰티",
- FOOD_HEALTH: "식품·건강",
- LIFESTYLE: "라이프스타일",
- TECH_ELECTRONICS: "테크·가전",
- SPORTS_LEISURE: "스포츠·레저",
- CULTURE_ENTERTAINMENT: "문화·엔터테인먼트",
- };
- return categoryMap[category] || category || "기타";
- };
- const getStatusText = (status) => {
- const statusMap = {
- PENDING: "대기중",
- APPROVED: "승인완료",
- REJECTED: "거절됨",
- EXPIRED: "만료됨",
- };
- return statusMap[status] || status || "알 수 없음";
- };
- const getStatusColor = (status) => {
- const colorMap = {
- PENDING: "orange",
- APPROVED: "success",
- REJECTED: "error",
- EXPIRED: "grey",
- };
- return colorMap[status] || "grey";
- };
- const getRequestStatusClass = (status) => {
- return `request-status-${status?.toLowerCase() || "unknown"}`;
- };
- const formatDate = (dateString) => {
- return new Date(dateString).toLocaleDateString("ko-KR");
- };
- const formatNumber = (num) => {
- if (!num) return "0";
- if (num >= 1000000) return (num / 1000000).toFixed(1) + "M";
- if (num >= 1000) return (num / 1000).toFixed(1) + "K";
- return num.toString();
- };
- const parseSnsChannels = (snsChannels) => {
- try {
- return JSON.parse(snsChannels);
- } catch (e) {
- return [];
- }
- };
- const getSnsIcon = (platform) => {
- const iconMap = {
- instagram: 'mdi-instagram',
- youtube: 'mdi-youtube',
- tiktok: 'mdi-music-note',
- blog: 'mdi-post',
- facebook: 'mdi-facebook',
- twitter: 'mdi-twitter'
- };
- return iconMap[platform.toLowerCase()] || 'mdi-link';
- };
- /************************************************************************
- | 라이프사이클
- ************************************************************************/
- onMounted(async () => {
- console.log('🚀 influencer-requests 컴포넌트 마운트됨');
- await loadRequests();
- });
- </script>
- <style scoped>
- .stats--cards--wrap {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 20px;
- margin-bottom: 30px;
- }
- .stats--card {
- background: white;
- border-radius: 12px;
- padding: 20px;
- display: flex;
- align-items: center;
- gap: 16px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
- .stats--icon {
- width: 50px;
- height: 50px;
- border-radius: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- }
- .stats--icon.pending {
- background: #ff9800;
- }
- .stats--icon.approved {
- background: #4caf50;
- }
- .stats--icon.rejected {
- background: #f44336;
- }
- .stats--icon.total {
- background: #2196f3;
- }
- .stats--content h3 {
- margin: 0;
- font-size: 24px;
- font-weight: 700;
- color: #333;
- }
- .stats--content p {
- margin: 0;
- font-size: 14px;
- color: #666;
- }
- .requests--list--wrap {
- margin-top: 20px;
- }
- .requests--grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
- gap: 20px;
- margin-bottom: 20px;
- }
- .request--card {
- background: white;
- border-radius: 12px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- transition: transform 0.2s, box-shadow 0.2s;
- border-left: 4px solid #e0e0e0;
- }
- .request--card:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
- }
- .request--card.request-status-pending {
- border-left-color: #ff9800;
- }
- .request--card.request-status-approved {
- border-left-color: #4caf50;
- }
- .request--card.request-status-rejected {
- border-left-color: #f44336;
- }
- .request--card--header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 16px;
- }
- .influencer--info {
- display: flex;
- gap: 12px;
- flex: 1;
- }
- .influencer--avatar {
- width: 50px;
- height: 50px;
- border-radius: 50%;
- overflow: hidden;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f5f5f5;
- }
- .no-avatar {
- font-size: 20px;
- font-weight: bold;
- color: #666;
- }
- .influencer--details h4 {
- margin: 0 0 4px 0;
- font-size: 16px;
- font-weight: 600;
- }
- .influencer--category {
- color: #666;
- font-size: 14px;
- margin: 0 0 8px 0;
- }
- .influencer--meta {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
- .influencer--meta span {
- font-size: 12px;
- color: #888;
- }
- .request--status {
- text-align: right;
- flex-shrink: 0;
- }
- .request--date {
- margin: 8px 0 0;
- font-size: 12px;
- color: #999;
- }
- .request--card--body {
- margin-bottom: 16px;
- }
- .request--message,
- .request--conditions {
- margin-bottom: 12px;
- }
- .request--message h5,
- .request--conditions h5 {
- margin: 0 0 8px 0;
- font-size: 14px;
- font-weight: 600;
- color: #333;
- }
- .request--message p {
- margin: 0;
- font-size: 14px;
- color: #666;
- font-style: italic;
- }
- .condition--item {
- display: flex;
- gap: 8px;
- margin-bottom: 4px;
- }
- .condition--label {
- font-size: 13px;
- color: #666;
- min-width: 80px;
- }
- .condition--value {
- font-size: 13px;
- color: #333;
- font-weight: 500;
- }
- .expire--info {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- color: #ff9800;
- background: #fff8e1;
- padding: 6px 10px;
- border-radius: 6px;
- }
- .request--card--footer {
- border-top: 1px solid #f0f0f0;
- padding-top: 16px;
- }
- .card--actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .approval--actions {
- display: flex;
- gap: 8px;
- }
- .loading-wrap,
- .error-wrap,
- .no-data-wrap {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 60px 20px;
- }
- .no-data {
- text-align: center;
- }
- .no-data h3 {
- margin: 16px 0 8px;
- color: #666;
- }
- .no-data p {
- color: #999;
- }
- .pagination-wrap {
- display: flex;
- justify-content: center;
- margin-top: 20px;
- }
- .approve--content,
- .reject--content {
- padding: 8px 0;
- }
- .influencer--summary {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px;
- background: #f8f9fa;
- border-radius: 8px;
- margin-bottom: 16px;
- }
- .influencer--avatar--small {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- overflow: hidden;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f5f5f5;
- }
- .no-avatar--small {
- font-size: 16px;
- font-weight: bold;
- color: #666;
- }
- .result-count {
- font-size: 14px;
- color: #666;
- font-weight: 500;
- }
- .influencer--contact {
- margin: 4px 0;
- }
- .contact--item {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 13px;
- color: #666;
- margin: 2px 0;
- }
- .contact--item .v-icon {
- color: #999;
- }
- .influencer--header {
- margin-bottom: 8px;
- }
- .influencer--contact {
- margin: 8px 0;
- padding: 8px;
- background: #f8f9fa;
- border-radius: 6px;
- }
- .contact--item {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 13px;
- color: #666;
- margin: 4px 0;
- }
- .influencer--meta {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- margin: 8px 0;
- }
- .meta--item {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 13px;
- color: #555;
- background: #f0f0f0;
- padding: 4px 8px;
- border-radius: 4px;
- }
- .influencer--description {
- margin: 8px 0;
- font-size: 13px;
- color: #666;
- line-height: 1.4;
- }
- .influencer--sns {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 8px;
- }
- .sns--item {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 12px;
- color: #555;
- background: #eef2ff;
- padding: 4px 8px;
- border-radius: 4px;
- }
- </style>
|