
이번 단계에서는 Vendor(업체) 관리 기능을 프론트엔드(React + TypeScript)와 백엔드(Spring Boot) 전체 흐름으로 정리했다. 단순 CRUD가 아니라, 페이지네이션 · 정렬 · 옵션 필터 · 상태 배지 · 모달 상세보기까지 포함하여 실제 ERP에서 사용할 수 있는 수준의 구조로 다듬는 것을 목표로 했다.
Backend는 Spring Boot + JPA 기반이며, Page 객체를 그대로 반환하는 구조로 설계했다. Controller는 단순히 파라미터를 받고, Service에서 동적 조건을 구성하도록 역할을 분리했다.
@GetMapping
public Page<VendorResponse> list(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) VendorStatus status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size
) {
Pageable pageable = PageRequest.of(page, size);
return vendorService.search(keyword, status, pageable);
}
Controller는 Presentation Layer 역할만 수행한다. 검색 조건을 직접 처리하지 않고 Service로 위임한다.
public Page<VendorResponse> search(String keyword, VendorStatus status, Pageable pageable) {
return vendorRepository.search(keyword, status, pageable)
.map(VendorResponse::from);
}
Entity → DTO 변환은 map을 통해 처리한다. JPA Page를 그대로 활용하여 totalElements, totalPages 등을 유지한다.
Frontend는 React + TypeScript 기반이며, PageResponse 타입을 정의하여 Backend Page 구조와 정확히 맞춰서 사용했다.
type PageResponse<T> = {
content: T[];
totalElements: number;
totalPages: number;
number: number;
size: number;
};
async function apiListVendors(filter: VendorFilter, page: number, size: number): Promise<PageResponse<Vendor>> {
const qs = buildQuery(filter, page, size);
const res = await fetch(`/api/vendors?${qs}`, { method: "GET" });
if (!res.ok) throw new Error(`list vendors failed: ${res.status}`);
return await res.json();
}
Backend Page 구조와 완전히 동일하게 받아오기 때문에, 추가 가공 없이 totalPages 기반 페이지네이션 구현이 가능해졌다.
기존에는 숫자 버튼이 한 개만 표시되거나, 레이아웃이 오른쪽에 과도하게 몰려 있었다. 이번에 window 기반 페이지 계산 함수로 개선했다.
function buildPageWindow(page: number, totalPages: number, windowSize = 5) {
const half = Math.floor(windowSize / 2);
let start = Math.max(0, page - half);
let end = Math.min(totalPages - 1, start + windowSize - 1);
start = Math.max(0, end - windowSize + 1);
const pages: number[] = [];
for (let p = start; p <= end; p++) pages.push(p);
return pages;
}
이 방식은 현재 페이지를 중심으로 5개씩 보여주는 ERP 스타일 페이지 구조다.
기존에는 "검색 결과 n건"이 오른쪽 액션 영역에 포함되어 시선이 분산되었다. 타이틀과 함께 묶는 구조로 변경했다.
<div className="panel-head">
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<h2 className="panel-title">업체 정보</h2>
<span className="badge">{`검색 결과 ${totalElements}건`}</span>
</div>
<div className="panel-actions">
{/* pagination + 등록 버튼 */}
</div>
</div>
좌측: 상태 요약 우측: 조작 영역 으로 명확히 분리하였다.
원인
해결
원인
해결
원인
해결
Frontend는 API 통신 계층, Backend는 비즈니스 계층으로 분리되어 있으며, DTO를 통해 의존성을 최소화했다.
이제 Vendor는 구조가 완전히 정리된 상태이며, Inbound/Outbound/Transfer 등 다른 도메인도 동일 패턴으로 확장 가능하다.