index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. <template>
  2. <div class="admin--sales-list">
  3. <!-- 검색 영역 -->
  4. <div class="admin--search-box admin--search-box-large">
  5. <div class="admin--search-filters">
  6. <div class="admin--filter-row">
  7. <label class="admin--filter-label">전시장</label>
  8. <select v-model="filters.showroom" class="admin--form-select">
  9. <option value="">전체</option>
  10. <option v-for="showroom in showrooms" :key="showroom.id" :value="showroom.id">
  11. {{ showroom.name }}
  12. </option>
  13. </select>
  14. <label class="admin--filter-label">팀</label>
  15. <select v-model="filters.team" class="admin--form-select">
  16. <option value="">전체</option>
  17. <option v-for="team in teams" :key="team.id" :value="team.id">
  18. {{ team.name }}
  19. </option>
  20. </select>
  21. <label class="admin--filter-label">직책</label>
  22. <select v-model="filters.position" class="admin--form-select">
  23. <option value="">전체</option>
  24. <option
  25. v-for="position in positions"
  26. :key="position.value"
  27. :value="position.value"
  28. >
  29. {{ position.label }}
  30. </option>
  31. </select>
  32. </div>
  33. <div class="admin--filter-row">
  34. <label class="admin--filter-label">검색어</label>
  35. <input
  36. v-model="filters.keyword"
  37. type="text"
  38. class="admin--form-input"
  39. placeholder="이름으로 검색"
  40. @keyup.enter="handleSearch"
  41. />
  42. <button class="admin--btn-small admin--btn-small-primary" @click="handleSearch">
  43. 검색
  44. </button>
  45. <button
  46. class="admin--btn-small admin--btn-small-secondary"
  47. @click="handleReset"
  48. >
  49. 초기화
  50. </button>
  51. </div>
  52. </div>
  53. <div class="admin--search-actions">
  54. <button
  55. class="admin--btn-small admin--btn-small-excel"
  56. @click="handleExcelDownload"
  57. >
  58. 엑셀 다운로드
  59. </button>
  60. <button
  61. class="admin--btn-small admin--btn-small-secondary"
  62. @click="handleA2Print"
  63. >
  64. A2 출력하기
  65. </button>
  66. <button class="admin--btn-small admin--btn-small-primary" @click="goToCreate">
  67. + 사원 등록
  68. </button>
  69. </div>
  70. </div>
  71. <!-- 테이블 -->
  72. <div class="admin--table-wrapper">
  73. <table class="admin--table admin--table-sales">
  74. <thead>
  75. <tr>
  76. <th>NO</th>
  77. <th>사진</th>
  78. <th>전시장</th>
  79. <th>팀</th>
  80. <th>이름</th>
  81. <th>직책</th>
  82. <th>대표번호</th>
  83. <th>핸드폰</th>
  84. <th>이메일</th>
  85. <th>상태</th>
  86. <th>관리</th>
  87. </tr>
  88. </thead>
  89. <tbody>
  90. <tr v-if="!salesList || salesList.length === 0">
  91. <td colspan="11" class="admin--table-empty">등록된 영업사원이 없습니다.</td>
  92. </tr>
  93. <tr v-else v-for="(sales, index) in salesList" :key="sales.id">
  94. <td>{{ totalCount - ((currentPage - 1) * perPage + index) }}</td>
  95. <td>
  96. <div class="admin--table-photo">
  97. <img
  98. v-if="sales.photo_url"
  99. :src="getImageUrl(sales.photo_url)"
  100. :alt="sales.name"
  101. />
  102. <div v-else class="admin--table-photo-empty">사진없음</div>
  103. </div>
  104. </td>
  105. <td>{{ getShowroomName(sales.showroom) }}</td>
  106. <td>{{ getTeamName(sales.team) }}</td>
  107. <td class="admin--table-title">{{ sales.name }}</td>
  108. <td>{{ getPositionName(sales.position) }}</td>
  109. <td>{{ sales.direct_phone || "-" }}</td>
  110. <td>{{ sales.main_phone }}</td>
  111. <td>{{ sales.email }}</td>
  112. <td>
  113. <button
  114. class="admin--toggle-btn"
  115. :class="{ 'is-active': sales.is_act == 1 }"
  116. @click="toggleActive(sales.id, sales.is_act)"
  117. >
  118. {{ sales.is_act == 1 ? '노출' : '비노출' }}
  119. </button>
  120. </td>
  121. <td>
  122. <div class="admin--table-actions admin--table-actions-col">
  123. <button
  124. class="admin--btn-small admin--btn-small-primary"
  125. @click="goToEdit(sales.id)"
  126. >
  127. 수정
  128. </button>
  129. <button
  130. class="admin--btn-small admin--btn-small-danger"
  131. @click="handleDelete(sales.id)"
  132. >
  133. 삭제
  134. </button>
  135. <button
  136. class="admin--btn-small admin--btn-small-secondary"
  137. @click="handlePrint(sales.id)"
  138. >
  139. 프린트
  140. </button>
  141. <button
  142. class="admin--btn-small admin--btn-small-excel"
  143. @click="handleExcelExport(sales.id)"
  144. >
  145. 엑셀출력
  146. </button>
  147. </div>
  148. </td>
  149. </tr>
  150. </tbody>
  151. </table>
  152. </div>
  153. <!-- 페이지네이션 -->
  154. <div v-if="totalPages > 1" class="admin--pagination">
  155. <button
  156. class="admin--pagination-btn"
  157. :disabled="currentPage === 1"
  158. @click="changePage(1)"
  159. title="처음"
  160. >
  161. </button>
  162. <button
  163. class="admin--pagination-btn"
  164. :disabled="currentPage === 1"
  165. @click="changePage(currentPage - 1)"
  166. title="이전"
  167. >
  168. </button>
  169. <button
  170. v-for="page in visiblePages"
  171. :key="page"
  172. class="admin--pagination-btn"
  173. :class="{ 'is-active': page === currentPage }"
  174. @click="changePage(page)"
  175. >
  176. {{ page }}
  177. </button>
  178. <button
  179. class="admin--pagination-btn"
  180. :disabled="currentPage === totalPages"
  181. @click="changePage(currentPage + 1)"
  182. title="다음"
  183. >
  184. </button>
  185. <button
  186. class="admin--pagination-btn"
  187. :disabled="currentPage === totalPages"
  188. @click="changePage(totalPages)"
  189. title="끝"
  190. >
  191. </button>
  192. </div>
  193. <!-- 알림 모달 -->
  194. <AdminAlertModal
  195. v-if="alertModal.show"
  196. :title="alertModal.title"
  197. :message="alertModal.message"
  198. :type="alertModal.type"
  199. @confirm="handleAlertConfirm"
  200. @cancel="handleAlertCancel"
  201. @close="closeAlertModal"
  202. />
  203. </div>
  204. </template>
  205. <script setup>
  206. import { ref, computed, onMounted } from "vue";
  207. import { useRouter } from "vue-router";
  208. import AdminAlertModal from '~/components/admin/AdminAlertModal.vue';
  209. definePageMeta({
  210. layout: "admin",
  211. middleware: ["auth"],
  212. });
  213. const router = useRouter();
  214. const { get, del, post } = useApi();
  215. const { getImageUrl } = useImage();
  216. const { teams, positions, getTeamName, getPositionName } = useSalesData();
  217. const salesList = ref([]);
  218. const showrooms = ref([]);
  219. const filters = ref({
  220. showroom: "",
  221. team: "",
  222. position: "",
  223. keyword: "",
  224. });
  225. const currentPage = ref(1);
  226. const perPage = ref(10);
  227. const totalCount = ref(0);
  228. const totalPages = ref(0);
  229. // 알림 모달
  230. const alertModal = ref({
  231. show: false,
  232. title: '알림',
  233. message: '',
  234. type: 'alert',
  235. onConfirm: null
  236. });
  237. // 알림 모달 표시
  238. const showAlert = (message, title = '알림') => {
  239. alertModal.value = {
  240. show: true,
  241. title,
  242. message,
  243. type: 'alert',
  244. onConfirm: null
  245. }
  246. };
  247. // 확인 모달 표시
  248. const showConfirm = (message, onConfirm, title = '확인') => {
  249. alertModal.value = {
  250. show: true,
  251. title,
  252. message,
  253. type: 'confirm',
  254. onConfirm
  255. }
  256. };
  257. // 알림 모달 닫기
  258. const closeAlertModal = () => {
  259. alertModal.value.show = false
  260. };
  261. // 알림 모달 확인
  262. const handleAlertConfirm = () => {
  263. if (alertModal.value.onConfirm) {
  264. alertModal.value.onConfirm()
  265. }
  266. closeAlertModal()
  267. };
  268. // 알림 모달 취소
  269. const handleAlertCancel = () => {
  270. closeAlertModal()
  271. };
  272. // 보이는 페이지 번호 계산
  273. const visiblePages = computed(() => {
  274. const pages = [];
  275. const maxVisible = 5;
  276. let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
  277. let end = Math.min(totalPages.value, start + maxVisible - 1);
  278. if (end - start < maxVisible - 1) {
  279. start = Math.max(1, end - maxVisible + 1);
  280. }
  281. for (let i = start; i <= end; i++) {
  282. pages.push(i);
  283. }
  284. return pages;
  285. });
  286. // 필터 데이터 로드
  287. const loadFilters = async () => {
  288. // 전시장 리스트 (지점 목록)
  289. const { data: branchData, error: branchError } = await get("/branch/list", {
  290. params: {
  291. per_page: 1000,
  292. },
  293. });
  294. console.log("[SalesList] 전시장(지점) API 응답:", {
  295. data: branchData,
  296. error: branchError,
  297. });
  298. if (branchData?.success && branchData?.data) {
  299. showrooms.value = branchData.data.items || [];
  300. console.log("[SalesList] 전시장(지점) 로드 성공");
  301. }
  302. };
  303. // 데이터 로드
  304. const loadSales = async () => {
  305. const params = {
  306. page: currentPage.value,
  307. per_page: perPage.value,
  308. ...filters.value,
  309. };
  310. const { data, error } = await get("/staff/sales", { params });
  311. console.log("[SalesList] 영업사원 목록 API 응답:", { data, error });
  312. if (data?.success && data?.data) {
  313. salesList.value = data.data.items || [];
  314. totalCount.value = data.data.total || 0;
  315. totalPages.value = Math.ceil(totalCount.value / perPage.value);
  316. console.log("[SalesList] 영업사원 목록 로드 성공");
  317. }
  318. };
  319. // 검색
  320. const handleSearch = () => {
  321. currentPage.value = 1;
  322. loadSales();
  323. };
  324. // 초기화
  325. const handleReset = () => {
  326. filters.value = {
  327. showroom: "",
  328. team: "",
  329. position: "",
  330. keyword: "",
  331. };
  332. currentPage.value = 1;
  333. loadSales();
  334. };
  335. // 전시장 이름 가져오기
  336. const getShowroomName = (showroomId) => {
  337. const showroom = showrooms.value.find((s) => s.id === showroomId);
  338. return showroom ? showroom.name : "-";
  339. };
  340. // 페이지 변경
  341. const changePage = (page) => {
  342. if (page < 1 || page > totalPages.value) return;
  343. currentPage.value = page;
  344. loadSales();
  345. window.scrollTo({ top: 0, behavior: "smooth" });
  346. };
  347. // 엑셀 다운로드 (전체)
  348. const handleExcelDownload = async () => {
  349. // 전체 데이터 가져오기 (페이지네이션 없이)
  350. const params = {
  351. ...filters.value,
  352. per_page: 10000, // 충분히 큰 값으로 전체 가져오기
  353. };
  354. const { data } = await get("/staff/sales", { params });
  355. if (!data?.success || !data?.data) {
  356. alert("데이터를 가져올 수 없습니다.");
  357. return;
  358. }
  359. const allStaff = data.data.items || [];
  360. if (allStaff.length === 0) {
  361. alert("다운로드할 데이터가 없습니다.");
  362. return;
  363. }
  364. // HTML 테이블 생성
  365. let tableRows = "";
  366. allStaff.forEach((staff, index) => {
  367. const showroomName = getShowroomName(staff.showroom);
  368. const teamName = getTeamName(staff.team);
  369. const positionName = getPositionName(staff.position);
  370. const photoUrl = staff.photo_url ? getImageUrl(staff.photo_url) : "";
  371. tableRows += `
  372. <tr style="height: 110px;">
  373. <td style="text-align: center; vertical-align: middle;">${index + 1}</td>
  374. <td style="text-align: center; padding: 5px; vertical-align: middle;">
  375. ${
  376. photoUrl
  377. ? `<img src="${photoUrl}" height="100" style="display: block; margin: 0 auto; max-width: 100%;" />`
  378. : "사진없음"
  379. }
  380. </td>
  381. <td style="vertical-align: middle;">${showroomName}</td>
  382. <td style="vertical-align: middle;">${teamName}</td>
  383. <td style="vertical-align: middle;">${staff.name}</td>
  384. <td style="vertical-align: middle;">${positionName}</td>
  385. <td style="vertical-align: middle;">${staff.direct_phone || "-"}</td>
  386. <td style="vertical-align: middle;">${staff.main_phone || "-"}</td>
  387. <td style="vertical-align: middle;">${staff.email || ""}</td>
  388. </tr>
  389. `;
  390. });
  391. const html = `
  392. <html xmlns:x="urn:schemas-microsoft-com:office:excel">
  393. <head>
  394. <meta charset="UTF-8">
  395. <style>
  396. body {
  397. font-family: "Malgun Gothic", "맑은 고딕", Arial, sans-serif;
  398. }
  399. table {
  400. border-collapse: collapse;
  401. width: 100%;
  402. }
  403. th, td {
  404. border: 1px solid #ddd;
  405. padding: 10px;
  406. text-align: left;
  407. }
  408. th {
  409. background-color: #f5f5f5;
  410. font-weight: bold;
  411. text-align: center;
  412. }
  413. .title {
  414. font-size: 18pt;
  415. font-weight: bold;
  416. margin-bottom: 20px;
  417. text-align: center;
  418. }
  419. </style>
  420. </head>
  421. <body>
  422. <div class="title">영업사원 목록</div>
  423. <table>
  424. <thead>
  425. <tr>
  426. <th>NO</th>
  427. <th>사진</th>
  428. <th>전시장</th>
  429. <th>팀</th>
  430. <th>이름</th>
  431. <th>직책</th>
  432. <th>대표번호</th>
  433. <th>핸드폰</th>
  434. <th>이메일</th>
  435. </tr>
  436. </thead>
  437. <tbody>
  438. ${tableRows}
  439. </tbody>
  440. </table>
  441. </body>
  442. </html>`;
  443. // Blob 생성 및 다운로드
  444. const blob = new Blob(["\ufeff" + html], {
  445. type: "application/vnd.ms-excel;charset=utf-8;",
  446. });
  447. const link = document.createElement("a");
  448. const url = URL.createObjectURL(blob);
  449. link.setAttribute("href", url);
  450. link.setAttribute(
  451. "download",
  452. `sales_staff_list_${new Date().toISOString().split("T")[0]}.xls`
  453. );
  454. link.style.visibility = "hidden";
  455. document.body.appendChild(link);
  456. link.click();
  457. document.body.removeChild(link);
  458. };
  459. // A2 출력
  460. const handleA2Print = () => {
  461. window.open("/site-manager/staff/sales/print-a2", "_blank");
  462. };
  463. // 개별 프린트
  464. const handlePrint = (id) => {
  465. window.open(`/site-manager/staff/sales/print/${id}`, "_blank");
  466. };
  467. // 개별 엑셀 출력
  468. const handleExcelExport = async (id) => {
  469. // 해당 사원 데이터 가져오기
  470. const { data } = await get(`/staff/sales/${id}`);
  471. if (!data?.success || !data?.data) {
  472. alert("데이터를 가져올 수 없습니다.");
  473. return;
  474. }
  475. const staff = data.data;
  476. // 전시장 이름
  477. const showroomName = getShowroomName(staff.showroom);
  478. // 팀 이름
  479. const teamName = getTeamName(staff.team);
  480. // 직책 이름
  481. const positionName = getPositionName(staff.position);
  482. // 사진 URL
  483. const photoUrl = staff.photo_url ? getImageUrl(staff.photo_url) : "";
  484. // HTML 생성 (엑셀 호환 테이블 레이아웃)
  485. const html = `
  486. <html xmlns:x="urn:schemas-microsoft-com:office:excel">
  487. <head>
  488. <meta charset="UTF-8">
  489. <style>
  490. body {
  491. font-family: "Malgun Gothic", "맑은 고딕", Arial, sans-serif;
  492. }
  493. table {
  494. border-collapse: collapse;
  495. width: 100%;
  496. }
  497. .header-table {
  498. width: 100%;
  499. margin-bottom: 40px;
  500. border-bottom: 2px solid #000;
  501. padding-bottom: 10px;
  502. }
  503. .header-table td {
  504. border: none;
  505. padding: 10px;
  506. }
  507. .header-title {
  508. font-size: 20pt;
  509. font-weight: bold;
  510. text-align: left;
  511. }
  512. .header-logo {
  513. text-align: right;
  514. font-size: 10pt;
  515. color: #666;
  516. }
  517. .content-table {
  518. width: 100%;
  519. border: 1px solid #ddd;
  520. margin-top: 20px;
  521. }
  522. .content-table td {
  523. border: 1px solid #ddd;
  524. vertical-align: top;
  525. padding: 10px;
  526. }
  527. .info-wrapper {
  528. padding-top: 0px;
  529. }
  530. .photo-cell {
  531. width: 200px;
  532. text-align: center;
  533. vertical-align: top;
  534. }
  535. .photo-cell img {
  536. width: 200px;
  537. height: auto;
  538. display: block;
  539. }
  540. .photo-placeholder {
  541. width: 200px;
  542. height: 300px;
  543. border: 1px solid #ddd;
  544. background-color: #f9f9f9;
  545. display: inline-block;
  546. line-height: 300px;
  547. text-align: center;
  548. color: #999;
  549. }
  550. .info-table {
  551. width: 100%;
  552. border-collapse: collapse;
  553. border: none;
  554. }
  555. .info-table tr {
  556. border: none;
  557. }
  558. .info-table td {
  559. padding: 10px;
  560. border: 1px solid #ddd;
  561. }
  562. .info-label {
  563. width: 100px;
  564. font-weight: bold;
  565. color: #333;
  566. font-size: 13pt;
  567. background-color: #f5f5f5;
  568. }
  569. .info-value {
  570. color: #555;
  571. font-size: 13pt;
  572. }
  573. .info-value-name {
  574. color: #000;
  575. font-size: 16pt;
  576. font-weight: bold;
  577. }
  578. </style>
  579. </head>
  580. <body>
  581. <table class="content-table" style="table-layout: fixed;">
  582. <colgroup>
  583. <col style="width: 200px;">
  584. <col style="width: 100px;">
  585. <col style="width: auto;">
  586. </colgroup>
  587. <tr>
  588. <td class="photo-cell" rowspan="6" style="padding: 0;">
  589. ${
  590. photoUrl
  591. ? `<img src="${photoUrl}" width="200" style="display: block;" />`
  592. : '<div class="photo-placeholder">사진없음</div>'
  593. }
  594. </td>
  595. <td class="info-label" style="height: 44.5px;">전시장</td>
  596. <td class="info-value">${showroomName}</td>
  597. </tr>
  598. <tr>
  599. <td class="info-label" style="height: 44.5px;">팀</td>
  600. <td class="info-value">${teamName}</td>
  601. </tr>
  602. <tr>
  603. <td class="info-label" style="height: 44.5px;">이름</td>
  604. <td class="info-value-name">${staff.name}</td>
  605. </tr>
  606. <tr>
  607. <td class="info-label" style="height: 44.5px;">직책</td>
  608. <td class="info-value">${positionName}</td>
  609. </tr>
  610. <tr>
  611. <td class="info-label" style="height: 44.5px;">핸드폰</td>
  612. <td class="info-value">${staff.main_phone || "-"}</td>
  613. </tr>
  614. <tr>
  615. <td class="info-label" style="height: 44.5px;">이메일</td>
  616. <td class="info-value">${staff.email || ""}</td>
  617. </tr>
  618. </table>
  619. </body>
  620. </html>`;
  621. // Blob 생성 및 다운로드
  622. const blob = new Blob(["\ufeff" + html], {
  623. type: "application/vnd.ms-excel;charset=utf-8;",
  624. });
  625. const link = document.createElement("a");
  626. const url = URL.createObjectURL(blob);
  627. link.setAttribute("href", url);
  628. link.setAttribute(
  629. "download",
  630. `sales_staff_${staff.name}_${new Date().toISOString().split("T")[0]}.xls`
  631. );
  632. link.style.visibility = "hidden";
  633. document.body.appendChild(link);
  634. link.click();
  635. document.body.removeChild(link);
  636. };
  637. // 등록 페이지로 이동
  638. const goToCreate = () => {
  639. router.push("/site-manager/staff/sales/create");
  640. };
  641. // 수정 페이지로 이동
  642. const goToEdit = (id) => {
  643. router.push(`/site-manager/staff/sales/edit/${id}`);
  644. };
  645. // 삭제
  646. const handleDelete = async (id) => {
  647. showConfirm(
  648. '정말 삭제하시겠습니까?',
  649. async () => {
  650. const { data, error } = await del(`/staff/sales/${id}`);
  651. if (error || !data?.success) {
  652. showAlert(error?.message || data?.message || '삭제에 실패했습니다.', '오류');
  653. } else {
  654. showAlert(data.message || '삭제되었습니다.', '성공');
  655. loadSales();
  656. }
  657. },
  658. '영업사원 삭제'
  659. );
  660. };
  661. // 노출/비노출 토글
  662. const toggleActive = (id, currentStatus) => {
  663. const statusText = currentStatus == 1 ? '비노출' : '노출';
  664. showConfirm(
  665. `영업사원을 ${statusText} 상태로 변경하시겠습니까?`,
  666. async () => {
  667. const { data, error } = await post(`/staff/sales/${id}/toggle-active`);
  668. if (error || !data?.success) {
  669. showAlert(error?.message || data?.message || '상태 변경에 실패했습니다.', '오류');
  670. } else {
  671. showAlert(data.message || '영업사원 상태가 변경되었습니다.', '성공');
  672. loadSales();
  673. }
  674. },
  675. '상태 변경'
  676. );
  677. };
  678. onMounted(() => {
  679. loadFilters();
  680. loadSales();
  681. });
  682. </script>
  683. <style scoped>
  684. /* 필터 영역 input/select 테두리 스타일 */
  685. .admin--filter-row .admin--form-input,
  686. .admin--filter-row .admin--form-select {
  687. border: 1px solid var(--admin-border-color);
  688. border-radius: 4px;
  689. height: 33px;
  690. padding: 6px 14px;
  691. font-size: 13px;
  692. }
  693. /* 버튼 영역 간격 조정 */
  694. .admin--search-actions {
  695. display: flex;
  696. gap: 12px;
  697. flex-shrink: 0;
  698. white-space: nowrap;
  699. }
  700. /* 상단 버튼들 세로 크기 */
  701. .admin--search-actions .admin--btn-small {
  702. height: 78px;
  703. display: flex;
  704. align-items: center;
  705. justify-content: center;
  706. }
  707. /* 상태 토글 버튼 */
  708. .admin--toggle-btn {
  709. padding: 6px 16px;
  710. font-size: 12px;
  711. border-radius: 20px;
  712. border: 1px solid #ddd;
  713. background: #f5f5f5;
  714. color: #666;
  715. cursor: pointer;
  716. transition: all 0.3s ease;
  717. font-weight: 500;
  718. }
  719. .admin--toggle-btn:hover {
  720. border-color: #bbb;
  721. background: #e8e8e8;
  722. }
  723. .admin--toggle-btn.is-active {
  724. background: var(--admin-accent-primary);
  725. color: white;
  726. border-color: var(--admin-accent-primary);
  727. }
  728. .admin--toggle-btn.is-active:hover {
  729. background: var(--admin-accent-hover);
  730. border-color: var(--admin-accent-hover);
  731. }
  732. </style>