feat(admin): 활동 로그 페이지에 페이지 번호 직접 입력 기능 추가

- 페이지네이션을 3-column grid로 배치해 버튼 그룹은 중앙 정렬, 오른쪽 구석에 입력 박스 추가
- 숫자만 입력, Enter 또는 blur 시 페이지 이동 (1~totalPages로 clamp)
- Enter 시 blur() 호출로 포커스 자동 해제

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-04-22 11:47:59 +09:00
parent 8ece4b1850
commit 39bb6f77f9

View file

@ -27,6 +27,7 @@ function Logs() {
const [dateFrom, setDateFrom] = useState('');
const [dateTo, setDateTo] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [pageInput, setPageInput] = useState('1');
const [actorDropdownOpen, setActorDropdownOpen] = useState(false);
const [categoryDropdownOpen, setCategoryDropdownOpen] = useState(false);
const [selectedLog, setSelectedLog] = useState(null);
@ -65,6 +66,20 @@ function Logs() {
const total = data?.total || 0;
const totalPages = data?.totalPages || 0;
//
useEffect(() => { setPageInput(String(currentPage)); }, [currentPage]);
const goToPageFromInput = () => {
const n = parseInt(pageInput, 10);
if (!Number.isFinite(n) || n < 1) {
setPageInput(String(currentPage));
return;
}
const clamped = Math.min(totalPages, n);
setCurrentPage(clamped);
setPageInput(String(clamped));
};
//
const toggleCategory = (cat) => {
setSelectedCategories((prev) =>
@ -343,52 +358,74 @@ function Logs() {
{/* 페이지네이션 */}
{totalPages > 1 && (
<div className="flex items-center justify-center gap-2 mt-6">
<button
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronLeft size={18} />
</button>
{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}` });
}
acc.push({ type: 'page', value: page, key: page });
return acc;
}, [])
.map((item) =>
item.type === 'ellipsis' ? (
<span key={item.key} className="w-9 h-9 flex items-center justify-center text-sm text-gray-400">...</span>
) : (
<button
key={item.key}
onClick={() => setCurrentPage(item.value)}
className={`w-9 h-9 rounded-lg text-sm font-medium transition-colors ${
currentPage === item.value
? 'bg-primary text-white'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
{item.value}
</button>
)
)}
<button
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronRight size={18} />
</button>
<div className="grid grid-cols-3 items-center mt-6">
<div />
<div className="flex items-center justify-center gap-2">
<button
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronLeft size={18} />
</button>
{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}` });
}
acc.push({ type: 'page', value: page, key: page });
return acc;
}, [])
.map((item) =>
item.type === 'ellipsis' ? (
<span key={item.key} className="w-9 h-9 flex items-center justify-center text-sm text-gray-400">...</span>
) : (
<button
key={item.key}
onClick={() => setCurrentPage(item.value)}
className={`w-9 h-9 rounded-lg text-sm font-medium transition-colors ${
currentPage === item.value
? 'bg-primary text-white'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
{item.value}
</button>
)
)}
<button
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
className="p-2 rounded-lg hover:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronRight size={18} />
</button>
</div>
<div className="flex items-center justify-end gap-1.5">
<input
type="text"
inputMode="numeric"
value={pageInput}
onChange={(e) => setPageInput(e.target.value.replace(/\D/g, ''))}
onBlur={goToPageFromInput}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
goToPageFromInput();
e.currentTarget.blur();
}
}}
className="w-12 h-9 text-center tabular-nums border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
aria-label="페이지 번호 입력"
/>
<span className="text-sm text-gray-400 tabular-nums">/ {totalPages}</span>
</div>
</div>
)}
</div>