File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/**
* 코드관리 화면(SYS_COD_001)의 URL 조건 — 순수 규칙만 담는다(next/react 의존 없음).
*
* 이 화면은 목록이 둘이라 상태가 셋이다. **선택된 공통코드(`comCd`)**, 공통코드 목록의 검색·
* 페이지, 상세코드 목록의 페이지. 셋 다 URL이 소유한다 — 다른 목록 화면과 같은 규칙이고,
* 새로고침·뒤로가기·링크 공유가 그대로 동작한다.
*
* **선택이 바뀌면 상세 페이지는 1로 돌아간다** — 다른 그룹의 3페이지는 의미가 없다.
* 그 규칙은 `buildCommonCodeHref`가 강제한다(호출부가 잊어도 어긋나지 않게).
*
* 페이징·검색을 URL에 두면서도 백엔드에는 넘기지 않는다 — 백엔드가 두 목록 모두 전체를
* 반환하고 검색도 완전일치라, 자르고 거르는 일은 서버 컴포넌트가 한다(Repository 주석 참조).
*/
/** 라우트 경로 — 이 파일 안에서만 하드코딩하고 나머지는 이 상수를 참조한다. */
export const COMMON_CODES_PATH = '/system/codes';
/** 검색 대상 — 시안(SYS_COD_001 ①) "코드명 / 코드ID". */
export type CommonCodeSearchField = 'name' | 'comCd';
export const COMMON_CODE_SEARCH_FIELD_OPTIONS: ReadonlyArray<{
value: CommonCodeSearchField;
label: string;
}> = [
{ value: 'name', label: '코드명' },
{ value: 'comCd', label: '코드ID' },
];
export const DEFAULT_COMMON_CODE_SEARCH_FIELD: CommonCodeSearchField = 'name';
/** 시안의 두 목록은 한 화면에 나란히 서므로 페이지 크기를 고르는 자리가 없다 — 고정값이다. */
export const COMMON_CODE_PAGE_SIZE = 10;
export const COMMON_CODE_DETAIL_PAGE_SIZE = 10;
const DEFAULT_PAGE = 1;
const MAX_KEYWORD_LENGTH = 100;
export type CommonCodeQuery = {
/** 선택된 공통코드. 아직 고르지 않았으면 null이고, 화면이 첫 행으로 채운다. */
comCd: string | null;
searchField: CommonCodeSearchField;
keyword: string;
/** 공통코드 목록의 페이지. */
page: number;
/** 상세코드 목록의 페이지. */
detailPage: number;
};
type RawSearchParams = Record<string, string | string[] | undefined>;
function readParam(params: RawSearchParams, key: string): string | undefined {
const value = params[key];
return Array.isArray(value) ? value[0] : value;
}
function readPage(params: RawSearchParams, key: string): number {
const value = Number(readParam(params, key));
return Number.isInteger(value) && value > 0 ? value : DEFAULT_PAGE;
}
function isSearchField(
value: string | undefined
): value is CommonCodeSearchField {
return (
value !== undefined &&
COMMON_CODE_SEARCH_FIELD_OPTIONS.some((option) => option.value === value)
);
}
/**
* URL의 searchParams를 검증된 `CommonCodeQuery`로 정규화한다. searchParams는 사용자가 임의로
* 조작할 수 있는 값이라 신뢰하지 않는다 — 허용 목록을 벗어나면 기본값으로 떨어진다.
*
* `comCd`만은 허용 목록을 여기서 확인할 수 없다(코드 목록이 서버에 있다) — 존재 여부는 화면이
* 조회 결과와 맞춰 보고 없으면 첫 행으로 대체한다.
*/
export function parseCommonCodeQuery(
searchParams: RawSearchParams
): CommonCodeQuery {
const searchFieldRaw = readParam(searchParams, 'searchField');
const comCd = readParam(searchParams, 'comCd')?.trim();
return {
comCd: comCd ? comCd.slice(0, 50) : null,
searchField: isSearchField(searchFieldRaw)
? searchFieldRaw
: DEFAULT_COMMON_CODE_SEARCH_FIELD,
keyword: (readParam(searchParams, 'keyword') ?? '')
.trim()
.slice(0, MAX_KEYWORD_LENGTH),
page: readPage(searchParams, 'page'),
detailPage: readPage(searchParams, 'detailPage'),
};
}
/**
* `CommonCodeQuery`(+ 부분 override)를 링크로 직렬화한다. `parseCommonCodeQuery`의 역연산이며
* 기본값과 같은 필드는 URL에서 생략해 링크를 짧게 유지한다.
*
* **공통코드 선택이 바뀌면 상세 페이지를 1로 되돌린다** — 호출부가 잊어도 어긋나지 않도록
* 여기서 강제한다(override로 detailPage를 함께 준 경우는 그 값을 존중한다).
*/
export function buildCommonCodeHref(
query: CommonCodeQuery,
overrides: Partial<CommonCodeQuery> = {}
): string {
const merged = { ...query, ...overrides };
if (
overrides.comCd !== undefined &&
overrides.comCd !== query.comCd &&
overrides.detailPage === undefined
) {
merged.detailPage = DEFAULT_PAGE;
}
const params = new URLSearchParams();
if (merged.comCd) {
params.set('comCd', merged.comCd);
}
if (merged.searchField !== DEFAULT_COMMON_CODE_SEARCH_FIELD) {
params.set('searchField', merged.searchField);
}
if (merged.keyword) {
params.set('keyword', merged.keyword);
}
if (merged.page !== DEFAULT_PAGE) {
params.set('page', String(merged.page));
}
if (merged.detailPage !== DEFAULT_PAGE) {
params.set('detailPage', String(merged.detailPage));
}
const queryString = params.toString();
return queryString ? `${COMMON_CODES_PATH}?${queryString}` : COMMON_CODES_PATH;
}