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:
parent
8ece4b1850
commit
39bb6f77f9
1 changed files with 83 additions and 46 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue