fix(WEB): 토큰 만료 시 무한 로딩 대신 로그인 리다이렉트 처리
- 52개 이상의 컴포넌트에 isNextRedirectError 처리 추가 - Server Action의 redirect() 에러가 catch 블록에서 삼켜지는 문제 해결 - access_token + refresh_token 모두 만료 시 정상적으로 로그인 페이지로 리다이렉트 수정된 영역: - accounting: 10개 컴포넌트 - production: 12개 컴포넌트 - hr: 5개 컴포넌트 - settings: 8개 컴포넌트 - approval: 5개 컴포넌트 - items: 20개+ 컴포넌트 - board: 5개 컴포넌트 - quality: 4개 컴포넌트 - material, outbound, quotes 등 기타 컴포넌트 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,10 @@ import { useErrorAlert } from '../contexts';
|
||||
import type { ItemPage, SectionTemplate, TemplateField, BOMItem, ItemMasterField } from '@/contexts/ItemMasterContext';
|
||||
import { templateService } from '../services';
|
||||
import { ApiError } from '@/lib/api/error-handler';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
// 필드 타입 정의 - field_type 캐스팅용
|
||||
type FieldTypeValue = 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
|
||||
|
||||
export interface UseTemplateManagementReturn {
|
||||
// 섹션 템플릿 다이얼로그 상태
|
||||
@@ -44,8 +48,9 @@ export interface UseTemplateManagementReturn {
|
||||
setTemplateFieldName: (name: string) => void;
|
||||
templateFieldKey: string;
|
||||
setTemplateFieldKey: (key: string) => void;
|
||||
templateFieldInputType: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea';
|
||||
setTemplateFieldInputType: (type: 'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea') => void;
|
||||
// string 타입으로 유연하게 처리
|
||||
templateFieldInputType: string;
|
||||
setTemplateFieldInputType: (type: string) => void;
|
||||
templateFieldRequired: boolean;
|
||||
setTemplateFieldRequired: (required: boolean) => void;
|
||||
templateFieldOptions: string;
|
||||
@@ -59,9 +64,9 @@ export interface UseTemplateManagementReturn {
|
||||
templateFieldColumnNames: string[];
|
||||
setTemplateFieldColumnNames: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
|
||||
// 템플릿 필드 마스터 항목 관련
|
||||
templateFieldInputMode: 'custom' | 'master';
|
||||
setTemplateFieldInputMode: (mode: 'custom' | 'master') => void;
|
||||
// 템플릿 필드 마스터 항목 관련 - 유연한 타입 지원
|
||||
templateFieldInputMode: 'custom' | 'master' | 'new' | 'existing';
|
||||
setTemplateFieldInputMode: (mode: 'custom' | 'master' | 'new' | 'existing') => void;
|
||||
templateFieldShowMasterFieldList: boolean;
|
||||
setTemplateFieldShowMasterFieldList: (show: boolean) => void;
|
||||
templateFieldSelectedMasterFieldId: string;
|
||||
@@ -139,7 +144,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
// 템플릿 필드 폼 상태
|
||||
const [templateFieldName, setTemplateFieldName] = useState('');
|
||||
const [templateFieldKey, setTemplateFieldKey] = useState('');
|
||||
const [templateFieldInputType, setTemplateFieldInputType] = useState<'textbox' | 'dropdown' | 'checkbox' | 'number' | 'date' | 'textarea'>('textbox');
|
||||
const [templateFieldInputType, setTemplateFieldInputType] = useState<string>('textbox');
|
||||
const [templateFieldRequired, setTemplateFieldRequired] = useState(false);
|
||||
const [templateFieldOptions, setTemplateFieldOptions] = useState('');
|
||||
const [templateFieldDescription, setTemplateFieldDescription] = useState('');
|
||||
@@ -147,8 +152,8 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
const [templateFieldColumnCount, setTemplateFieldColumnCount] = useState(2);
|
||||
const [templateFieldColumnNames, setTemplateFieldColumnNames] = useState<string[]>(['컬럼1', '컬럼2']);
|
||||
|
||||
// 템플릿 필드 마스터 항목 관련
|
||||
const [templateFieldInputMode, setTemplateFieldInputMode] = useState<'custom' | 'master'>('custom');
|
||||
// 템플릿 필드 마스터 항목 관련 - 'new'/'existing' 호환을 위해 유연하게 처리
|
||||
const [templateFieldInputMode, setTemplateFieldInputMode] = useState<'custom' | 'master' | 'new' | 'existing'>('custom');
|
||||
const [templateFieldShowMasterFieldList, setTemplateFieldShowMasterFieldList] = useState(false);
|
||||
const [templateFieldSelectedMasterFieldId, setTemplateFieldSelectedMasterFieldId] = useState('');
|
||||
|
||||
@@ -176,6 +181,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
resetSectionTemplateForm();
|
||||
toast.success('섹션이 추가되었습니다!');
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('섹션 추가 실패:', error);
|
||||
toast.error('섹션 추가에 실패했습니다.');
|
||||
}
|
||||
@@ -212,6 +218,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
resetSectionTemplateForm();
|
||||
toast.success('섹션이 수정되었습니다!');
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('섹션 수정 실패:', error);
|
||||
toast.error('섹션 수정에 실패했습니다.');
|
||||
}
|
||||
@@ -225,6 +232,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
await deleteSection(id);
|
||||
toast.success('섹션이 삭제되었습니다!');
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('섹션 삭제 실패:', error);
|
||||
toast.error('섹션 삭제에 실패했습니다.');
|
||||
}
|
||||
@@ -282,7 +290,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
const updateData = {
|
||||
field_name: templateFieldName,
|
||||
field_key: templateFieldKey, // 2025-11-28: field_key 추가
|
||||
field_type: templateFieldInputType,
|
||||
field_type: templateFieldInputType as FieldTypeValue,
|
||||
is_required: templateFieldRequired,
|
||||
placeholder: templateFieldDescription || null,
|
||||
options: templateFieldInputType === 'dropdown' && templateFieldOptions.trim()
|
||||
@@ -326,7 +334,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
master_field_id: null,
|
||||
field_name: templateFieldName,
|
||||
field_key: templateFieldKey,
|
||||
field_type: templateFieldInputType,
|
||||
field_type: templateFieldInputType as FieldTypeValue,
|
||||
order_no: 0,
|
||||
is_required: templateFieldRequired,
|
||||
placeholder: templateFieldDescription || null,
|
||||
@@ -352,6 +360,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
|
||||
resetTemplateFieldForm();
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('항목 처리 실패:', error);
|
||||
|
||||
// 422 ValidationException 상세 메시지 처리 (field_key 중복/예약어, field_name 중복 등) → AlertDialog로 표시
|
||||
@@ -402,6 +411,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
await unlinkFieldFromSection(templateId, Number(fieldId));
|
||||
toast.success('항목 연결이 해제되었습니다');
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('항목 연결 해제 실패:', error);
|
||||
toast.error('항목 연결 해제에 실패했습니다.');
|
||||
}
|
||||
@@ -415,6 +425,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
await addBOMItem(templateId, item);
|
||||
// toast는 BOMManagementSection 컴포넌트에서 처리
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('BOM 항목 추가 실패:', error);
|
||||
toast.error('BOM 항목 추가에 실패했습니다');
|
||||
}
|
||||
@@ -427,6 +438,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
await updateBOMItem(itemId, item);
|
||||
// toast는 BOMManagementSection 컴포넌트에서 처리
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('BOM 항목 수정 실패:', error);
|
||||
toast.error('BOM 항목 수정에 실패했습니다');
|
||||
}
|
||||
@@ -439,6 +451,7 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
|
||||
await deleteBOMItem(itemId);
|
||||
// toast는 BOMManagementSection 컴포넌트에서 처리
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('BOM 항목 삭제 실패:', error);
|
||||
toast.error('BOM 항목 삭제에 실패했습니다');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user