diff --git a/backend/src/routes/admin/logs.js b/backend/src/routes/admin/logs.js index 210f7e2..bdd9e19 100644 --- a/backend/src/routes/admin/logs.js +++ b/backend/src/routes/admin/logs.js @@ -7,6 +7,39 @@ import { serverError } from '../../utils/error.js'; export default async function logsRoutes(fastify) { const { db } = fastify; + /** + * GET /api/admin/logs/categories + * 로그에 존재하는 카테고리 목록 조회 + */ + fastify.get('/categories', { + schema: { + tags: ['admin/logs'], + summary: '로그 카테고리 목록 조회', + security: [{ bearerAuth: [] }], + response: { + 200: { + type: 'object', + properties: { + categories: { + type: 'array', + items: { type: 'string' }, + }, + }, + }, + 500: errorResponse, + }, + }, + preHandler: [fastify.authenticate], + }, async (request, reply) => { + try { + const [rows] = await db.query('SELECT DISTINCT category FROM logs ORDER BY category'); + return { categories: rows.map(r => r.category) }; + } catch (err) { + fastify.log.error(`로그 카테고리 조회 오류: ${err.message}`); + return serverError(reply, err.message); + } + }); + /** * GET /api/admin/logs * 활동 로그 목록 조회 diff --git a/frontend/src/api/admin/logs.js b/frontend/src/api/admin/logs.js index f1d78d1..723a2c9 100644 --- a/frontend/src/api/admin/logs.js +++ b/frontend/src/api/admin/logs.js @@ -15,6 +15,14 @@ import { fetchAuthApi } from '@/api/client'; * @param {string} [params.to] - 종료 날짜 (YYYY-MM-DD) * @returns {Promise<{logs: Array, total: number, page: number, limit: number, totalPages: number}>} */ +/** + * 로그 카테고리 목록 조회 + * @returns {Promise<{categories: string[]}>} + */ +export async function getLogCategories() { + return fetchAuthApi('/admin/logs/categories'); +} + export async function getLogs(params = {}) { const query = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { diff --git a/frontend/src/components/pc/admin/common/DatePicker.jsx b/frontend/src/components/pc/admin/common/DatePicker.jsx index 69c0ee4..9ebafde 100644 --- a/frontend/src/components/pc/admin/common/DatePicker.jsx +++ b/frontend/src/components/pc/admin/common/DatePicker.jsx @@ -18,6 +18,9 @@ function DatePicker({ placeholder = '날짜 선택', showDayOfWeek = false, minYear = 2000, + min, + max, + compact = false, }) { const [isOpen, setIsOpen] = useState(false); const [viewMode, setViewMode] = useState('days'); @@ -132,6 +135,14 @@ function DatePicker({ return today.getFullYear() === year && today.getMonth() === month && today.getDate() === day; }; + const isDisabledDate = (day) => { + if (!day) return true; + const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + if (min && dateStr < min) return true; + if (max && dateStr > max) return true; + return false; + }; + const currentYear = new Date().getFullYear(); const currentMonth = new Date().getMonth(); const isCurrentYear = (y) => currentYear === y; @@ -179,12 +190,14 @@ function DatePicker({ @@ -303,19 +316,20 @@ function DatePicker({
{days.map((day, i) => { const dayOfWeek = i % 7; + const disabled = isDisabledDate(day); return (
+ {/* 카테고리 드롭다운 */} +
+ + + {categoryDropdownOpen && ( + <> +
setCategoryDropdownOpen(false)} /> + + {categories.map((cat) => ( + + ))} + {selectedCategories.length > 0 && ( + <> +
+ + + )} + + + )} + +
+ {/* 날짜 필터 */}
-
+
{ setDateFrom(v); setCurrentPage(1); }} placeholder="시작일" + max={dateTo || undefined} + compact />
~ -
+
{ setDateTo(v); setCurrentPage(1); }} placeholder="종료일" + min={dateFrom || undefined} + compact />
@@ -238,31 +315,13 @@ function Logs() { {hasActiveFilters && ( )}
- - {/* 하단: 카테고리 칩 */} -
- 카테고리 - {CATEGORIES.map((cat) => ( - - ))} -
{/* 결과 개수 */} @@ -311,7 +370,7 @@ function Logs() { - {CATEGORIES.find((c) => c.value === log.category)?.label || log.category} + {CATEGORY_LABELS[log.category] || log.category} @@ -351,14 +410,12 @@ function Logs() { {Array.from({ length: totalPages }, (_, i) => i + 1) .filter((page) => { - // 페이지가 많을 때 현재 페이지 주변만 표시 if (totalPages <= 7) return true; if (page === 1 || page === totalPages) return true; if (Math.abs(page - currentPage) <= 2) return true; return false; }) .reduce((acc, page, i, arr) => { - // 생략 부호(...) 추가 if (i > 0 && page - arr[i - 1] > 1) { acc.push({ type: 'ellipsis', key: `e-${page}` }); }