공통화: - status-config.ts 신규 추가 (상태 설정 중앙 관리) - StatusBadge 컴포넌트 개선 - 뱃지 공통화 가이드 문서 추가 상세 페이지 훅 시스템: - useDetailData, useDetailPageState, useDetailPermissions, useCRUDHandlers 훅 신규 추가 - hooks/index.ts 진입점 추가 - BillDetailV2 신규 컴포넌트 추가 리팩토링: - 회계(매입/어음/거래처), 품목, 단가, 견적, 주문 상태 뱃지 공통화 적용 - 생산(작업지시서/대시보드/작업자화면), 품질(검사관리) 상태 뱃지 적용 - 공사관리(칸반/프로젝트카드) 상태 뱃지 적용 - 고객센터(문의관리), 인사(직원CSV업로드) 개선 - 리스트 페이지 공통화 현황 분석 문서 추가 상수 정리: - lib/constants/ 디렉토리 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
184 lines
4.6 KiB
TypeScript
184 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useCallback, useMemo } from 'react';
|
|
import { useRouter, useParams, useSearchParams } from 'next/navigation';
|
|
|
|
/**
|
|
* 상세 페이지 모드 타입
|
|
* - view: 조회 모드
|
|
* - edit: 수정 모드
|
|
* - create: 등록 모드
|
|
*/
|
|
export type DetailMode = 'view' | 'edit' | 'create';
|
|
|
|
export interface UseDetailPageStateOptions {
|
|
/** 기본 모드 (기본값: 'view') */
|
|
defaultMode?: DetailMode;
|
|
/** 목록 페이지 경로 (뒤로가기용) */
|
|
listPath?: string;
|
|
}
|
|
|
|
export interface UseDetailPageStateReturn<T> {
|
|
// ===== 라우터 정보 =====
|
|
/** 현재 아이템 ID (params.id) */
|
|
id: string | null;
|
|
/** 현재 locale */
|
|
locale: string;
|
|
/** Next.js router */
|
|
router: ReturnType<typeof useRouter>;
|
|
|
|
// ===== 모드 관리 =====
|
|
/** 현재 모드 */
|
|
mode: DetailMode;
|
|
/** 모드 변경 */
|
|
setMode: (mode: DetailMode) => void;
|
|
/** 조회 모드 여부 */
|
|
isViewMode: boolean;
|
|
/** 수정 모드 여부 */
|
|
isEditMode: boolean;
|
|
/** 등록 모드 여부 */
|
|
isCreateMode: boolean;
|
|
|
|
// ===== 데이터 상태 =====
|
|
/** 데이터 */
|
|
data: T | null;
|
|
/** 데이터 설정 */
|
|
setData: (data: T | null) => void;
|
|
/** 로딩 상태 */
|
|
isLoading: boolean;
|
|
/** 로딩 상태 설정 */
|
|
setIsLoading: (loading: boolean) => void;
|
|
/** 에러 메시지 */
|
|
error: string | null;
|
|
/** 에러 설정 */
|
|
setError: (error: string | null) => void;
|
|
|
|
// ===== 네비게이션 =====
|
|
/** 목록으로 이동 */
|
|
goToList: () => void;
|
|
/** 수정 모드로 전환 (URL도 변경) */
|
|
goToEdit: () => void;
|
|
/** 조회 모드로 전환 */
|
|
goToView: () => void;
|
|
}
|
|
|
|
/**
|
|
* 상세 페이지 공통 상태 관리 훅
|
|
*
|
|
* 등록/수정/상세 페이지에서 반복되는 상태 및 라우터 로직을 통합합니다.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // 상세/수정 페이지
|
|
* const {
|
|
* id, mode, isViewMode, isEditMode,
|
|
* data, setData, isLoading, setIsLoading,
|
|
* goToList, goToEdit
|
|
* } = useDetailPageState<Account>({ listPath: '/settings/accounts' });
|
|
*
|
|
* // 등록 페이지
|
|
* const { mode, isCreateMode, goToList } = useDetailPageState<Account>({
|
|
* defaultMode: 'create',
|
|
* listPath: '/settings/accounts'
|
|
* });
|
|
* ```
|
|
*/
|
|
export function useDetailPageState<T = unknown>(
|
|
options: UseDetailPageStateOptions = {}
|
|
): UseDetailPageStateReturn<T> {
|
|
const { defaultMode = 'view', listPath } = options;
|
|
|
|
// ===== 라우터 =====
|
|
const router = useRouter();
|
|
const params = useParams();
|
|
const searchParams = useSearchParams();
|
|
|
|
// ID 추출 (params.id가 string | string[] | undefined일 수 있음)
|
|
const id = useMemo(() => {
|
|
const rawId = params?.id;
|
|
if (Array.isArray(rawId)) return rawId[0] || null;
|
|
return rawId || null;
|
|
}, [params?.id]);
|
|
|
|
// locale 추출
|
|
const locale = useMemo(() => {
|
|
const rawLocale = params?.locale;
|
|
if (Array.isArray(rawLocale)) return rawLocale[0] || 'ko';
|
|
return rawLocale || 'ko';
|
|
}, [params?.locale]);
|
|
|
|
// ===== 모드 관리 =====
|
|
// URL의 ?mode=edit 파라미터 확인
|
|
const urlMode = searchParams?.get('mode');
|
|
const initialMode = urlMode === 'edit' ? 'edit' : defaultMode;
|
|
|
|
const [mode, setModeState] = useState<DetailMode>(initialMode);
|
|
|
|
const setMode = useCallback((newMode: DetailMode) => {
|
|
setModeState(newMode);
|
|
}, []);
|
|
|
|
const isViewMode = mode === 'view';
|
|
const isEditMode = mode === 'edit';
|
|
const isCreateMode = mode === 'create';
|
|
|
|
// ===== 데이터 상태 =====
|
|
const [data, setData] = useState<T | null>(null);
|
|
const [isLoading, setIsLoading] = useState(!isCreateMode); // 등록 모드는 로딩 불필요
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// ===== 네비게이션 =====
|
|
const goToList = useCallback(() => {
|
|
if (listPath) {
|
|
router.push(`/${locale}${listPath}`);
|
|
} else {
|
|
router.back();
|
|
}
|
|
}, [router, locale, listPath]);
|
|
|
|
const goToEdit = useCallback(() => {
|
|
setModeState('edit');
|
|
if (id) {
|
|
// URL에 mode=edit 추가 (현재 경로 유지)
|
|
const currentPath = window.location.pathname;
|
|
router.push(`${currentPath}?mode=edit`);
|
|
}
|
|
}, [router, id]);
|
|
|
|
const goToView = useCallback(() => {
|
|
setModeState('view');
|
|
if (id) {
|
|
// URL에서 mode 파라미터 제거
|
|
const currentPath = window.location.pathname;
|
|
router.push(currentPath);
|
|
}
|
|
}, [router, id]);
|
|
|
|
return {
|
|
// 라우터 정보
|
|
id,
|
|
locale,
|
|
router,
|
|
|
|
// 모드 관리
|
|
mode,
|
|
setMode,
|
|
isViewMode,
|
|
isEditMode,
|
|
isCreateMode,
|
|
|
|
// 데이터 상태
|
|
data,
|
|
setData,
|
|
isLoading,
|
|
setIsLoading,
|
|
error,
|
|
setError,
|
|
|
|
// 네비게이션
|
|
goToList,
|
|
goToEdit,
|
|
goToView,
|
|
};
|
|
}
|