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:
@@ -21,21 +21,27 @@ const INPUT_TYPE_OPTIONS = [
|
||||
{ value: 'textarea', label: '긴 텍스트' },
|
||||
];
|
||||
|
||||
// 입력 모드 타입: 'new'/'existing' 또는 'custom'/'master' 모두 지원
|
||||
type TemplateFieldInputModeType = 'new' | 'existing' | 'custom' | 'master';
|
||||
|
||||
interface TemplateFieldDialogProps {
|
||||
isTemplateFieldDialogOpen: boolean;
|
||||
setIsTemplateFieldDialogOpen: (open: boolean) => void;
|
||||
editingTemplateFieldId: number | null;
|
||||
setEditingTemplateFieldId: (id: number | null) => void;
|
||||
// string 또는 number | null 모두 지원
|
||||
editingTemplateFieldId: string | number | null;
|
||||
setEditingTemplateFieldId: ((id: string | null) => void) | ((id: number | null) => void);
|
||||
templateFieldName: string;
|
||||
setTemplateFieldName: (name: string) => void;
|
||||
templateFieldKey: string;
|
||||
setTemplateFieldKey: (key: string) => void;
|
||||
templateFieldInputType: 'textbox' | 'number' | 'dropdown' | 'checkbox' | 'date' | 'textarea';
|
||||
setTemplateFieldInputType: (type: any) => void;
|
||||
// string 타입으로 유연하게 처리
|
||||
templateFieldInputType: string;
|
||||
setTemplateFieldInputType: (type: string) => void;
|
||||
templateFieldRequired: boolean;
|
||||
setTemplateFieldRequired: (required: boolean) => void;
|
||||
templateFieldOptions: string;
|
||||
setTemplateFieldOptions: (options: string) => void;
|
||||
// string 또는 string[] 모두 지원
|
||||
templateFieldOptions: string | string[];
|
||||
setTemplateFieldOptions: ((options: string) => void) | React.Dispatch<React.SetStateAction<string[]>>;
|
||||
templateFieldDescription: string;
|
||||
setTemplateFieldDescription: (description: string) => void;
|
||||
templateFieldMultiColumn: boolean;
|
||||
@@ -43,16 +49,17 @@ interface TemplateFieldDialogProps {
|
||||
templateFieldColumnCount: number;
|
||||
setTemplateFieldColumnCount: (count: number) => void;
|
||||
templateFieldColumnNames: string[];
|
||||
setTemplateFieldColumnNames: (names: string[]) => void;
|
||||
setTemplateFieldColumnNames: ((names: string[]) => void) | React.Dispatch<React.SetStateAction<string[]>>;
|
||||
handleAddTemplateField: () => void | Promise<void>;
|
||||
// 항목 관련 props
|
||||
// 항목 관련 props - 유연한 타입 지원
|
||||
itemMasterFields?: ItemMasterField[];
|
||||
templateFieldInputMode?: 'custom' | 'master';
|
||||
setTemplateFieldInputMode?: (mode: 'custom' | 'master') => void;
|
||||
templateFieldInputMode?: TemplateFieldInputModeType;
|
||||
setTemplateFieldInputMode?: (mode: TemplateFieldInputModeType) => void;
|
||||
showMasterFieldList?: boolean;
|
||||
setShowMasterFieldList?: (show: boolean) => void;
|
||||
selectedMasterFieldId?: string;
|
||||
setSelectedMasterFieldId?: (id: string) => void;
|
||||
// string 또는 number | null 모두 지원
|
||||
selectedMasterFieldId?: string | number | null;
|
||||
setSelectedMasterFieldId?: ((id: string) => void) | ((id: number | null) => void);
|
||||
}
|
||||
|
||||
export function TemplateFieldDialog({
|
||||
@@ -90,6 +97,23 @@ export function TemplateFieldDialog({
|
||||
}: TemplateFieldDialogProps) {
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
// 입력 모드 정규화: 'new' → 'custom', 'existing' → 'master'
|
||||
const normalizedInputMode =
|
||||
templateFieldInputMode === 'new' ? 'custom' :
|
||||
templateFieldInputMode === 'existing' ? 'master' :
|
||||
templateFieldInputMode;
|
||||
|
||||
// 옵션을 문자열로 변환하여 처리
|
||||
const optionsString = Array.isArray(templateFieldOptions) ? templateFieldOptions.join(', ') : templateFieldOptions;
|
||||
|
||||
// 래퍼 함수들 - union type 호환성 해결
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSetTemplateFieldOptions = (options: string) => (setTemplateFieldOptions as any)(options);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSetEditingTemplateFieldId = (id: string | null) => (setEditingTemplateFieldId as any)(id);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSetSelectedMasterFieldId = (id: string) => setSelectedMasterFieldId && (setSelectedMasterFieldId as any)(id);
|
||||
|
||||
// 유효성 검사
|
||||
const isNameEmpty = !templateFieldName.trim();
|
||||
const isKeyEmpty = !templateFieldKey.trim();
|
||||
@@ -97,12 +121,12 @@ export function TemplateFieldDialog({
|
||||
const handleClose = () => {
|
||||
setIsSubmitted(false);
|
||||
setIsTemplateFieldDialogOpen(false);
|
||||
setEditingTemplateFieldId(null);
|
||||
handleSetEditingTemplateFieldId(null);
|
||||
setTemplateFieldName('');
|
||||
setTemplateFieldKey('');
|
||||
setTemplateFieldInputType('textbox');
|
||||
setTemplateFieldRequired(false);
|
||||
setTemplateFieldOptions('');
|
||||
handleSetTemplateFieldOptions('');
|
||||
setTemplateFieldDescription('');
|
||||
setTemplateFieldMultiColumn(false);
|
||||
setTemplateFieldColumnCount(2);
|
||||
@@ -110,18 +134,18 @@ export function TemplateFieldDialog({
|
||||
// 항목 관련 상태 초기화
|
||||
setTemplateFieldInputMode?.('custom');
|
||||
setShowMasterFieldList?.(false);
|
||||
setSelectedMasterFieldId?.('');
|
||||
handleSetSelectedMasterFieldId('');
|
||||
};
|
||||
|
||||
const handleSelectMasterField = (field: ItemMasterField) => {
|
||||
setSelectedMasterFieldId?.(String(field.id));
|
||||
handleSetSelectedMasterFieldId(String(field.id));
|
||||
setTemplateFieldName(field.field_name);
|
||||
setTemplateFieldKey(field.id.toString());
|
||||
setTemplateFieldInputType(field.field_type);
|
||||
setTemplateFieldRequired(field.properties?.required || false);
|
||||
setTemplateFieldDescription(field.description || '');
|
||||
// options는 {label, value}[] 배열이므로 label만 추출
|
||||
setTemplateFieldOptions(field.options?.map(opt => opt.label).join(', ') || '');
|
||||
handleSetTemplateFieldOptions(field.options?.map(opt => opt.label).join(', ') || '');
|
||||
if (field.properties?.multiColumn && field.properties?.columnNames) {
|
||||
setTemplateFieldMultiColumn(true);
|
||||
setTemplateFieldColumnCount(field.properties.columnNames.length);
|
||||
@@ -145,7 +169,7 @@ export function TemplateFieldDialog({
|
||||
{!editingTemplateFieldId && setTemplateFieldInputMode && (
|
||||
<div className="flex gap-2 p-1 bg-gray-100 rounded">
|
||||
<Button
|
||||
variant={templateFieldInputMode === 'custom' ? 'default' : 'ghost'}
|
||||
variant={normalizedInputMode === 'custom' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setTemplateFieldInputMode('custom')}
|
||||
className="flex-1"
|
||||
@@ -153,7 +177,7 @@ export function TemplateFieldDialog({
|
||||
직접 입력
|
||||
</Button>
|
||||
<Button
|
||||
variant={templateFieldInputMode === 'master' ? 'default' : 'ghost'}
|
||||
variant={normalizedInputMode === 'master' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTemplateFieldInputMode('master');
|
||||
@@ -167,7 +191,7 @@ export function TemplateFieldDialog({
|
||||
)}
|
||||
|
||||
{/* 항목 목록 */}
|
||||
{templateFieldInputMode === 'master' && !editingTemplateFieldId && showMasterFieldList && (
|
||||
{normalizedInputMode === 'master' && !editingTemplateFieldId && showMasterFieldList && (
|
||||
<div className="border rounded p-3 space-y-2 max-h-[400px] overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label>항목 목록</Label>
|
||||
@@ -276,8 +300,8 @@ export function TemplateFieldDialog({
|
||||
<div>
|
||||
<Label>드롭다운 옵션</Label>
|
||||
<Input
|
||||
value={templateFieldOptions}
|
||||
onChange={(e) => setTemplateFieldOptions(e.target.value)}
|
||||
value={optionsString}
|
||||
onChange={(e) => handleSetTemplateFieldOptions(e.target.value)}
|
||||
placeholder="옵션1, 옵션2, 옵션3 (쉼표로 구분)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user