| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997 |
- <template>
- <div>
- <div class="inner--headers">
- <h2>{{ pageId }}</h2>
- <div class="bread--crumbs--wrap">
- <span>홈</span>
- <span>{{ pageId }}</span>
- </div>
- </div>
- <!-- 검색 및 필터 영역 -->
- <div class="search--modules type2">
- <div class="search--inner">
- <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--filter">
- <v-select
- v-model="searchFilter.region"
- :items="regionOptions"
- 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 mb-4">
- <div class="section--header">
- <h3>내 승인 요청 현황</h3>
- <v-btn
- class="custom-btn mini btn-outline"
- @click="showMyRequests = !showMyRequests"
- >
- {{ showMyRequests ? "숨기기" : "보기" }}
- </v-btn>
- </div>
- <div v-show="showMyRequests" class="my--requests--wrap">
- <div v-if="myRequests.length === 0" class="no-data">
- <p>진행 중인 승인 요청이 없습니다.</p>
- </div>
- <div v-else class="request--cards">
- <div
- v-for="request in myRequests"
- :key="request.SEQ"
- class="request--card"
- :class="getStatusClass(request.STATUS)"
- >
- <div class="card--header">
- <h4>{{ request.vendorName }}</h4>
- <v-chip :color="getStatusColor(request.STATUS)" size="small">
- {{ getStatusText(request.STATUS) }}
- </v-chip>
- </div>
- <div class="card--content">
- <p class="request--date">요청일: {{ formatDate(request.REQUEST_DATE) }}</p>
- <p v-if="request.STATUS === 'PENDING'" class="expire--date">
- 만료일: {{ formatDate(request.EXPIRED_DATE) }}
- </p>
- <p v-if="request.REQUEST_MESSAGE" class="request--message">
- "{{ request.REQUEST_MESSAGE }}"
- </p>
- </div>
- <div class="card--actions">
- <v-btn
- v-if="request.STATUS === 'PENDING'"
- class="custom-btn mini btn-outline-red"
- @click="cancelRequest(request.SEQ)"
- size="small"
- >
- 취소
- </v-btn>
- <v-btn
- class="custom-btn mini btn-outline"
- @click="viewRequestDetail(request.SEQ)"
- size="small"
- >
- 상세보기
- </v-btn>
- </div>
- </div>
- </div>
- </div>
- </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="vendors.length > 0" class="vendor--search--wrap">
- <div class="vendor--grid">
- <div v-for="vendor in vendors" :key="vendor.SEQ" class="vendor--card">
- <div class="vendor--card--header">
- <div class="vendor--logo">
- <v-img
- v-if="vendor.LOGO"
- :src="vendor.LOGO"
- :alt="vendor.COMPANY_NAME + ' 로고'"
- width="60"
- height="60"
- ></v-img>
- <div v-else class="no-logo">
- {{ vendor.COMPANY_NAME?.charAt(0) || "V" }}
- </div>
- </div>
- <div class="vendor--info">
- <h3>{{ vendor.COMPANY_NAME }}</h3>
- <p class="vendor--category">{{ getCategoryText(vendor.CATEGORY) }}</p>
- <div class="vendor--meta">
- <span v-if="vendor.REGION">📍 {{ vendor.REGION }}</span>
- <span v-if="vendor.PARTNERSHIP_COUNT"
- >🤝 {{ vendor.PARTNERSHIP_COUNT }}개 파트너십</span
- >
- </div>
- </div>
- </div>
- <div class="vendor--card--body">
- <p v-if="vendor.DESCRIPTION" class="vendor--description">
- {{ vendor.DESCRIPTION }}
- </p>
- <div class="vendor--tags" v-if="vendor.TAGS">
- <v-chip
- v-for="tag in vendor.TAGS.split(',')"
- :key="tag"
- size="small"
- variant="outlined"
- class="mr-1 mb-1"
- >
- {{ tag.trim() }}
- </v-chip>
- </div>
- </div>
- <div class="vendor--card--footer">
- <div class="partnership--status">
- <span
- v-if="getPartnershipStatus(vendor.SEQ)"
- :class="[
- 'status-badge',
- getPartnershipStatus(vendor.SEQ)?.toLowerCase() || 'unknown',
- ]"
- >
- {{ getPartnershipStatusText(vendor.SEQ) }}
- </span>
- <span v-else class="status-badge available">신규 파트너십 가능</span>
- </div>
- <div class="card--actions">
- <v-btn
- class="custom-btn mini btn-outline mr-2"
- @click="viewVendorDetail(vendor.SEQ)"
- >
- 상세보기
- </v-btn>
- <v-btn
- v-if="showRequestButton(vendor.SEQ)"
- class="custom-btn mini btn-blue"
- @click="openRequestModal(vendor)"
- >
- 승인요청
- </v-btn>
- <v-chip
- v-else-if="getPartnershipStatus(vendor.SEQ) === 'APPROVED'"
- color="success"
- size="small"
- >
- 승인완료
- </v-chip>
- </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-store-search</v-icon>
- <h3>검색된 벤더사가 없습니다</h3>
- <p>다른 검색 조건을 시도해보세요</p>
- </div>
- </div>
- </div>
- <!-- 승인요청 모달 -->
- <v-dialog v-model="requestModal.show" max-width="600px">
- <v-card>
- <v-card-title class="text-h5">
- {{ requestModal.vendor?.COMPANY_NAME }}에 승인요청
- </v-card-title>
- <v-card-text>
- <div class="request--form">
- <div class="vendor--summary">
- <div class="vendor--logo--small">
- <v-img
- v-if="requestModal.vendor?.LOGO"
- :src="requestModal.vendor.LOGO"
- width="40"
- height="40"
- ></v-img>
- <div v-else class="no-logo--small">
- {{ requestModal.vendor?.COMPANY_NAME?.charAt(0) || "V" }}
- </div>
- </div>
- <div>
- <h4>{{ requestModal.vendor?.COMPANY_NAME }}</h4>
- <p>{{ getCategoryText(requestModal.vendor?.CATEGORY) }}</p>
- </div>
- </div>
- <v-textarea
- v-model="requestModal.message"
- label="요청 메시지"
- placeholder="벤더사에 전달할 메시지를 작성해주세요..."
- rows="4"
- counter="500"
- maxlength="500"
- class="mt-4"
- ></v-textarea>
- <div class="form--section">
- <h5>희망 조건 (선택사항)</h5>
- <v-text-field
- v-model="requestModal.commissionRate"
- label="희망 수수료율 (%)"
- type="number"
- min="0"
- max="100"
- step="0.1"
- class="mt-2"
- ></v-text-field>
- <v-textarea
- v-model="requestModal.specialConditions"
- label="특별 조건"
- placeholder="기타 협업 조건이나 요청사항을 입력해주세요..."
- rows="3"
- class="mt-2"
- ></v-textarea>
- </div>
- </div>
- </v-card-text>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn color="grey" variant="text" @click="closeRequestModal"> 취소 </v-btn>
- <v-btn color="primary" @click="submitRequest" :loading="submitting">
- 승인요청
- </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 submitting = ref(false);
- const error = ref(null);
- const currentPage = ref(1);
- const showMyRequests = ref(false);
- // 검색 필터
- const searchFilter = ref({
- keyword: "",
- category: "",
- region: "",
- });
- // 정렬 옵션
- const sortOption = ref("latest");
- const sortOptions = ref([
- { title: "최신순", value: "latest" },
- { title: "파트너십 많은순", value: "partnership" },
- { title: "이름순", value: "name" },
- ]);
- // 카테고리 옵션
- 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 regionOptions = ref([
- { title: "전체", value: "" },
- { title: "서울", value: "SEOUL" },
- { title: "경기", value: "GYEONGGI" },
- { title: "인천", value: "INCHEON" },
- { title: "부산", value: "BUSAN" },
- { title: "대구", value: "DAEGU" },
- { title: "대전", value: "DAEJEON" },
- { title: "광주", value: "GWANGJU" },
- { title: "울산", value: "ULSAN" },
- { title: "기타", value: "OTHER" },
- ]);
- // 데이터
- const vendors = ref([]);
- const myRequests = ref([]);
- const pagination = ref({
- currentPage: 1,
- totalPages: 1,
- totalCount: 0,
- pageSize: 12,
- });
- // 승인요청 모달
- const requestModal = ref({
- show: false,
- vendor: null,
- message: "",
- commissionRate: null,
- specialConditions: "",
- });
- /************************************************************************
- | computed
- ************************************************************************/
- const currentUser = computed(() => {
- // 인증 스토어에서 사용자 정보 가져오기
- const authStore = useAuthStore();
-
- console.log('🔍 currentUser computed 디버깅:', {
- 'authStore.auth': authStore.auth,
- 'snsTempData': authStore.auth.snsTempData,
- 'localStorage': localStorage.getItem("authStore")
- });
-
- // SNS 로그인 시 snsTempData가 있는 경우 해당 데이터 사용
- if (authStore.auth.snsTempData?.user) {
- console.log('✅ SNS 로그인 데이터 사용:', authStore.auth.snsTempData.user);
- return authStore.auth.snsTempData.user;
- }
-
- // 일반 로그인 시 auth 데이터 사용
- if (authStore.auth.seq) {
- console.log('✅ 일반 로그인 데이터 사용:', authStore.auth);
- return authStore.auth;
- }
-
- // 로컬스토리지 백업
- try {
- const localAuthData = JSON.parse(localStorage.getItem("authStore"))?.auth;
- console.log('💾 localStorage 데이터:', localAuthData);
-
- if (localAuthData?.snsTempData?.user) {
- console.log('✅ localStorage SNS 데이터 사용:', localAuthData.snsTempData.user);
- return localAuthData.snsTempData.user;
- }
-
- console.log('✅ localStorage 일반 데이터 사용:', localAuthData);
- return localAuthData || {};
- } catch (e) {
- console.error('❌ localStorage authStore 파싱 오류:', e);
- return {};
- }
- });
- /************************************************************************
- | 메서드
- ************************************************************************/
- const handleSearch = async () => {
- currentPage.value = 1;
- await loadVendors();
- };
- const handlePageChange = async (page) => {
- currentPage.value = page;
- await loadVendors();
- };
- const handleSort = async () => {
- currentPage.value = 1;
- await loadVendors();
- };
- const loadVendors = async () => {
- try {
- loading.value = true;
- error.value = null;
- const params = {
- keyword: searchFilter.value.keyword,
- category: searchFilter.value.category,
- region: searchFilter.value.region,
- sortBy: sortOption.value,
- page: currentPage.value,
- size: pagination.value.pageSize,
- influencerSeq: currentUser.value.SEQ || currentUser.value.seq || currentUser.value.ID || currentUser.value.id,
- };
- useAxios()
- .post("/api/vendor/search", params)
- .then((res) => {
- if (res.data.success) {
- vendors.value = res.data.data.items;
- pagination.value = res.data.data.pagination;
- } 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 loadMyRequests = async () => {
- try {
- const params = {
- influencerSeq: currentUser.value.SEQ || currentUser.value.seq || currentUser.value.ID || currentUser.value.id,
- // status 파라미터 제거하여 모든 상태의 요청을 로드
- };
- useAxios()
- .post("/api/vendor-influencer/list", params)
- .then((res) => {
- if (res.data.success) {
- myRequests.value = res.data.data.items;
- }
- })
- .catch((err) => {
- console.error("내 요청 목록 로드 오류:", err);
- });
- } catch (err) {
- console.error("내 요청 목록 로드 오류:", err);
- }
- };
- const openRequestModal = (vendor) => {
- requestModal.value = {
- show: true,
- vendor: vendor,
- message: "",
- commissionRate: null,
- specialConditions: "",
- };
- };
- const closeRequestModal = () => {
- requestModal.value = {
- show: false,
- vendor: null,
- message: "",
- commissionRate: null,
- specialConditions: "",
- };
- };
- const submitRequest = async () => {
- try {
- submitting.value = true;
- // 사용자 seq 필드 확인 (SNS 로그인 시 대문자 SEQ, 일반 로그인 시 소문자 seq)
- console.log('🔍 currentUser.value 전체:', currentUser.value);
- console.log('🔍 SEQ 후보들:', {
- 'SEQ': currentUser.value.SEQ,
- 'seq': currentUser.value.seq,
- 'id': currentUser.value.id,
- 'ID': currentUser.value.ID
- });
-
- const userSeq = currentUser.value.SEQ || currentUser.value.seq || currentUser.value.ID || currentUser.value.id;
-
- console.log('🎯 최종 선택된 userSeq:', userSeq);
-
- if (!userSeq) {
- console.error('❌ userSeq가 비어있음:', currentUser.value);
- $toast.error('로그인 정보를 확인할 수 없습니다. 다시 로그인해주세요.');
- return;
- }
-
- const params = {
- vendorSeq: requestModal.value.vendor.SEQ,
- influencerSeq: userSeq,
- requestType: "INFLUENCER_REQUEST",
- requestMessage: requestModal.value.message,
- requestedBy: userSeq,
- commissionRate: requestModal.value.commissionRate,
- specialConditions: requestModal.value.specialConditions,
- };
-
- console.log('📤 API 호출 파라미터:', params);
- useAxios()
- .post("/api/vendor-influencer/request", params)
- .then((res) => {
- if (res.data.success) {
- $toast.success("승인요청이 성공적으로 전송되었습니다.");
- closeRequestModal();
- loadMyRequests();
- loadVendors();
- } else {
- $toast.error(res.data.message || "승인요청 전송 중 오류가 발생했습니다.");
- }
- })
- .catch((err) => {
- $toast.error(err.message || "승인요청 전송 중 오류가 발생했습니다.");
- })
- .finally(() => {
- submitting.value = false;
- });
- } catch (err) {
- $toast.error(err.message || "승인요청 전송 중 오류가 발생했습니다.");
- submitting.value = false;
- }
- };
- const cancelRequest = async (requestSeq) => {
- if (!confirm("승인요청을 취소하시겠습니까?")) return;
- try {
- const params = {
- mappingSeq: requestSeq,
- cancelledBy: currentUser.value.SEQ || currentUser.value.seq || currentUser.value.ID || currentUser.value.id,
- cancelReason: "사용자에 의한 취소",
- };
- useAxios()
- .post("/api/vendor-influencer/cancel", params)
- .then((res) => {
- if (res.data.success) {
- $toast.success("승인요청이 취소되었습니다.");
- loadMyRequests();
- } else {
- $toast.error(res.data.message || "요청 취소 중 오류가 발생했습니다.");
- }
- })
- .catch((err) => {
- $toast.error(err.message || "요청 취소 중 오류가 발생했습니다.");
- });
- } catch (err) {
- $toast.error(err.message || "요청 취소 중 오류가 발생했습니다.");
- }
- };
- const viewVendorDetail = (vendorSeq) => {
- router.push(`/view/vendor/${vendorSeq}`);
- };
- const viewRequestDetail = (requestSeq) => {
- router.push(`/view/vendor/request/${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: "거절됨",
- CANCELLED: "취소됨",
- };
- return statusMap[status] || status || "알 수 없음";
- };
- const getStatusColor = (status) => {
- const colorMap = {
- PENDING: "orange",
- APPROVED: "success",
- REJECTED: "error",
- CANCELLED: "grey",
- };
- return colorMap[status] || "grey";
- };
- const getStatusClass = (status) => {
- return `status-${status?.toLowerCase() || "unknown"}`;
- };
- const getPartnershipStatus = (vendorSeq) => {
- // 현재 인플루언서의 해당 벤더사에 대한 파트너십 상태 확인
- const request = myRequests.value.find(req => req.VENDOR_SEQ === vendorSeq);
- return request ? request.STATUS : null;
- };
- const getPartnershipStatusText = (vendorSeq) => {
- const status = getPartnershipStatus(vendorSeq);
- const statusMap = {
- PENDING: "승인 대기중",
- APPROVED: "승인 완료",
- REJECTED: "승인 거절됨",
- CANCELLED: "요청 취소됨"
- };
- return statusMap[status] || "신규 파트너십 가능";
- };
- // 승인요청 버튼 표시 여부 확인
- const showRequestButton = (vendorSeq) => {
- const status = getPartnershipStatus(vendorSeq);
- return !status || status === 'REJECTED' || status === 'CANCELLED';
- };
- const formatDate = (dateString) => {
- return new Date(dateString).toLocaleDateString("ko-KR");
- };
- /************************************************************************
- | 라이프사이클
- ************************************************************************/
- onMounted(async () => {
- await Promise.all([loadVendors(), loadMyRequests()]);
- });
- </script>
- <style scoped>
- .section--header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- padding-bottom: 8px;
- border-bottom: 1px solid #e0e0e0;
- }
- .my--requests--wrap {
- background: #f8f9fa;
- border-radius: 8px;
- padding: 16px;
- margin-bottom: 20px;
- }
- .request--cards {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
- gap: 16px;
- }
- .request--card {
- background: white;
- border-radius: 8px;
- padding: 16px;
- border-left: 4px solid #e0e0e0;
- }
- .request--card.status-pending {
- border-left-color: #ff9800;
- }
- .request--card.status-approved {
- border-left-color: #4caf50;
- }
- .request--card.status-rejected {
- border-left-color: #f44336;
- }
- .card--header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- }
- .card--header h4 {
- margin: 0;
- font-size: 16px;
- font-weight: 600;
- }
- .card--content {
- margin-bottom: 12px;
- }
- .card--content p {
- margin: 4px 0;
- font-size: 14px;
- color: #666;
- }
- .request--message {
- font-style: italic;
- color: #333 !important;
- }
- .card--actions {
- display: flex;
- gap: 8px;
- }
- .vendor--search--wrap {
- margin-top: 20px;
- }
- .vendor--grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
- gap: 20px;
- margin-bottom: 20px;
- }
- .vendor--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;
- }
- .vendor--card:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
- }
- .vendor--card--header {
- display: flex;
- gap: 16px;
- margin-bottom: 16px;
- }
- .vendor--logo {
- width: 60px;
- height: 60px;
- border-radius: 8px;
- overflow: hidden;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f5f5f5;
- }
- .no-logo {
- font-size: 24px;
- font-weight: bold;
- color: #666;
- }
- .vendor--info h3 {
- margin: 0 0 4px 0;
- font-size: 18px;
- font-weight: 600;
- }
- .vendor--category {
- color: #666;
- font-size: 14px;
- margin: 0 0 8px 0;
- }
- .vendor--meta {
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
- .vendor--meta span {
- font-size: 12px;
- color: #888;
- }
- .vendor--card--body {
- margin-bottom: 16px;
- }
- .vendor--description {
- font-size: 14px;
- color: #666;
- line-height: 1.4;
- margin-bottom: 12px;
- }
- .vendor--tags {
- margin-bottom: 8px;
- }
- .vendor--card--footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .partnership--status {
- flex: 1;
- }
- .status-badge {
- padding: 4px 8px;
- border-radius: 4px;
- font-size: 12px;
- font-weight: 500;
- }
- .status-badge.available {
- background: #e8f5e8;
- color: #2e7d32;
- }
- .status-badge.pending {
- background: #fff3e0;
- color: #ef6c00;
- }
- .status-badge.approved {
- background: #e8f5e8;
- color: #2e7d32;
- }
- .status-badge.rejected {
- background: #ffebee;
- color: #c62828;
- }
- .card--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;
- }
- .request--form {
- padding: 8px 0;
- }
- .vendor--summary {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px;
- background: #f8f9fa;
- border-radius: 8px;
- margin-bottom: 16px;
- }
- .vendor--logo--small {
- width: 40px;
- height: 40px;
- border-radius: 6px;
- overflow: hidden;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f5f5f5;
- }
- .no-logo--small {
- font-size: 16px;
- font-weight: bold;
- color: #666;
- }
- .form--section {
- margin-top: 16px;
- }
- .form--section h5 {
- margin: 0 0 8px 0;
- font-size: 14px;
- font-weight: 600;
- color: #333;
- }
- .result-count {
- font-size: 14px;
- color: #666;
- font-weight: 500;
- }
- </style>
|