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:
@@ -11,8 +11,8 @@ import {
|
||||
Plus,
|
||||
FileText,
|
||||
Edit,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
SORT_OPTIONS,
|
||||
FILTER_OPTIONS,
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
export function AttendanceManagement() {
|
||||
const router = useRouter();
|
||||
@@ -108,6 +109,7 @@ export function AttendanceManagement() {
|
||||
setAttendanceRecords(attendancesResult.data);
|
||||
setTotal(attendancesResult.total);
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[AttendanceManagement] fetchData error:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -340,6 +342,7 @@ export function AttendanceManagement() {
|
||||
}
|
||||
setAttendanceDialogOpen(false);
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Save attendance error:', error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
@@ -552,11 +555,7 @@ export function AttendanceManagement() {
|
||||
|
||||
// 로딩 상태
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
return <ContentLoadingSpinner text="근태 정보를 불러오는 중..." />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
deleteDepartmentsMany,
|
||||
type DepartmentRecord,
|
||||
} from './actions';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
/**
|
||||
* API 응답을 로컬 Department 타입으로 변환
|
||||
@@ -77,6 +78,7 @@ export function DepartmentManagement() {
|
||||
console.error('[DepartmentManagement] fetchDepartments error:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DepartmentManagement] fetchDepartments error:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -221,6 +223,7 @@ export function DepartmentManagement() {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DepartmentManagement] confirmDelete error:', error);
|
||||
} finally {
|
||||
setDeleteDialogOpen(false);
|
||||
@@ -260,6 +263,7 @@ export function DepartmentManagement() {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[DepartmentManagement] handleDialogSubmit error:', error);
|
||||
} finally {
|
||||
setDialogOpen(false);
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
DEFAULT_FIELD_SETTINGS,
|
||||
USER_ROLE_LABELS,
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
// 필터 옵션 타입
|
||||
type FilterOption = 'all' | 'hasUserId' | 'noUserId' | 'active' | 'leave' | 'resigned';
|
||||
@@ -116,6 +117,7 @@ export function EmployeeManagement() {
|
||||
setEmployees(result.data);
|
||||
setTotal(result.total);
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[EmployeeManagement] fetchEmployees error:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -312,6 +314,7 @@ export function EmployeeManagement() {
|
||||
console.error('Bulk delete failed:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Bulk delete error:', error);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
@@ -341,6 +344,7 @@ export function EmployeeManagement() {
|
||||
console.error('Delete failed:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('Delete error:', error);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
SORT_OPTIONS,
|
||||
formatCurrency,
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
export function SalaryManagement() {
|
||||
// ===== 상태 관리 =====
|
||||
@@ -98,6 +99,7 @@ export function SalaryManagement() {
|
||||
toast.error(result.error || '급여 목록을 불러오는데 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('loadSalaries error:', error);
|
||||
toast.error('급여 목록을 불러오는데 실패했습니다.');
|
||||
} finally {
|
||||
@@ -147,6 +149,7 @@ export function SalaryManagement() {
|
||||
toast.error(result.error || '상태 변경에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('handleMarkCompleted error:', error);
|
||||
toast.error('상태 변경에 실패했습니다.');
|
||||
} finally {
|
||||
@@ -173,6 +176,7 @@ export function SalaryManagement() {
|
||||
toast.error(result.error || '상태 변경에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('handleMarkScheduled error:', error);
|
||||
toast.error('상태 변경에 실패했습니다.');
|
||||
} finally {
|
||||
@@ -193,6 +197,7 @@ export function SalaryManagement() {
|
||||
toast.error(result.error || '급여 상세 정보를 불러오는데 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('handleViewDetail error:', error);
|
||||
toast.error('급여 상세 정보를 불러오는데 실패했습니다.');
|
||||
} finally {
|
||||
@@ -215,6 +220,7 @@ export function SalaryManagement() {
|
||||
toast.error(result.error || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('handleSaveDetail error:', error);
|
||||
toast.error('저장에 실패했습니다.');
|
||||
} finally {
|
||||
|
||||
@@ -74,6 +74,7 @@ import {
|
||||
REQUEST_STATUS_LABELS,
|
||||
REQUEST_STATUS_COLORS,
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
// ===== Mock 데이터 생성 (request 탭용 - 신청 현황은 leaves API 사용 예정) =====
|
||||
|
||||
@@ -170,6 +171,7 @@ export function VacationManagement() {
|
||||
setUsageData(converted);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VacationManagement] fetchUsageData error:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -204,6 +206,7 @@ export function VacationManagement() {
|
||||
setGrantData(converted);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VacationManagement] fetchGrantData error:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -246,6 +249,7 @@ export function VacationManagement() {
|
||||
setRequestData(converted);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VacationManagement] fetchLeaveRequests error:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -344,6 +348,7 @@ export function VacationManagement() {
|
||||
console.error('[VacationManagement] 승인 실패:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VacationManagement] handleApproveConfirm error:', error);
|
||||
} finally {
|
||||
setSelectedItems(new Set());
|
||||
@@ -369,6 +374,7 @@ export function VacationManagement() {
|
||||
console.error('[VacationManagement] 반려 실패:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VacationManagement] handleRejectConfirm error:', error);
|
||||
} finally {
|
||||
setSelectedItems(new Set());
|
||||
@@ -750,6 +756,7 @@ export function VacationManagement() {
|
||||
console.error('[VacationManagement] 휴가 부여 실패:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VacationManagement] 휴가 부여 에러:', error);
|
||||
alert('휴가 부여 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -779,6 +786,7 @@ export function VacationManagement() {
|
||||
console.error('[VacationManagement] 휴가 신청 실패:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[VacationManagement] 휴가 신청 에러:', error);
|
||||
alert('휴가 신청 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user