feat: Request Logs 조회 기간 프리셋 + 필터 UI 개선

- 날짜 프리셋 버튼 추가 (오늘/어제/최근7일/이번달/지난달/직접선택)
- 필터 영역 한 줄로 통합 (서비스/상태/Method/검색/초기화)
- IP 입력 필드 제거
This commit is contained in:
HYOJIN 2026-04-13 09:27:31 +09:00
부모 97e5a24343
커밋 765d0e01c6

파일 보기

@ -26,14 +26,13 @@ const REQUEST_STATUSES = ['SUCCESS', 'FAIL', 'DENIED', 'EXPIRED', 'INVALID_KEY',
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
const DEFAULT_PAGE_SIZE = 20;
const getTodayString = (): string => {
const d = new Date();
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
const formatDate = (d: Date): string => {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
const getToday = (): string => formatDate(new Date());
const getTodayString = getToday;
const formatDateTime = (dateStr: string): string => {
const d = new Date(dateStr);
const year = d.getFullYear();
@ -50,10 +49,10 @@ const RequestLogsPage = () => {
const [startDate, setStartDate] = useState(getTodayString());
const [endDate, setEndDate] = useState(getTodayString());
const [datePreset, setDatePreset] = useState('오늘');
const [serviceId, setServiceId] = useState('');
const [requestStatus, setRequestStatus] = useState('');
const [requestMethod, setRequestMethod] = useState('');
const [requestIp, setRequestIp] = useState('');
const [services, setServices] = useState<ServiceInfo[]>([]);
const [result, setResult] = useState<PageResponse<RequestLog> | null>(null);
@ -84,7 +83,6 @@ const RequestLogsPage = () => {
serviceId: serviceId ? Number(serviceId) : undefined,
requestStatus: requestStatus || undefined,
requestMethod: requestMethod || undefined,
requestIp: requestIp || undefined,
page,
size: DEFAULT_PAGE_SIZE,
};
@ -104,10 +102,10 @@ const RequestLogsPage = () => {
const handleReset = () => {
setStartDate(getTodayString());
setEndDate(getTodayString());
setDatePreset('오늘');
setServiceId('');
setRequestStatus('');
setRequestMethod('');
setRequestIp('');
setCurrentPage(0);
};
@ -169,45 +167,68 @@ const RequestLogsPage = () => {
{/* Search Form */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div>
<div className="md:col-span-3">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"></label>
<div className="flex items-center gap-2 flex-wrap mb-2">
{([
{ label: '오늘', fn: () => { const t = getToday(); setStartDate(t); setEndDate(t); setDatePreset('오늘'); } },
{ label: '어제', fn: () => { const d = new Date(); d.setDate(d.getDate() - 1); const y = formatDate(d); setStartDate(y); setEndDate(y); setDatePreset('어제'); } },
{ label: '최근 7일', fn: () => { const d = new Date(); d.setDate(d.getDate() - 6); setStartDate(formatDate(d)); setEndDate(getToday()); setDatePreset('최근 7일'); } },
{ label: '이번 달', fn: () => { const d = new Date(); setStartDate(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`); setEndDate(getToday()); setDatePreset('이번 달'); } },
{ label: '지난 달', fn: () => { const d = new Date(); d.setMonth(d.getMonth() - 1); const s = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`; const e = new Date(d.getFullYear(), d.getMonth() + 1, 0); setStartDate(s); setEndDate(formatDate(e)); setDatePreset('지난 달'); } },
{ label: '직접 선택', fn: () => { setDatePreset('직접 선택'); } },
]).map((btn) => (
<button
key={btn.label}
type="button"
onClick={btn.fn}
className={`px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors ${
datePreset === btn.label
? 'bg-blue-50 dark:bg-blue-900/20 border-blue-300 dark:border-blue-700 text-blue-600 dark:text-blue-400'
: 'border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
{btn.label}
</button>
))}
</div>
<div className="flex items-center gap-2">
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
onChange={(e) => { setStartDate(e.target.value); setDatePreset('직접 선택'); }}
className="flex-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
<span className="text-gray-500 dark:text-gray-400">~</span>
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
onChange={(e) => { setEndDate(e.target.value); setDatePreset('직접 선택'); }}
className="flex-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
</div>
</div>
</div>
<div className="flex items-end gap-3 flex-wrap">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"></label>
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1"></label>
<select
value={serviceId}
onChange={(e) => setServiceId(e.target.value)}
className="w-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
>
<option value=""></option>
{services.map((s) => (
<option key={s.serviceId} value={s.serviceId}>
{s.serviceName}
</option>
<option key={s.serviceId} value={s.serviceId}>{s.serviceName}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"></label>
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1"></label>
<select
value={requestStatus}
onChange={(e) => setRequestStatus(e.target.value)}
className="w-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
>
<option value=""></option>
{REQUEST_STATUSES.map((s) => (
@ -215,14 +236,12 @@ const RequestLogsPage = () => {
))}
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">HTTP Method</label>
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Method</label>
<select
value={requestMethod}
onChange={(e) => setRequestMethod(e.target.value)}
className="w-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
>
<option value=""></option>
{HTTP_METHODS.map((m) => (
@ -230,17 +249,7 @@ const RequestLogsPage = () => {
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">IP</label>
<input
type="text"
value={requestIp}
onChange={(e) => setRequestIp(e.target.value)}
placeholder="IP 주소"
className="w-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
</div>
<div className="flex items-end gap-2">
<div className="flex items-end gap-2 ml-auto">
<button
onClick={() => handleSearch(0)}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium"