6a9fc06f0e
- frontend/lib/api/substitute.ts: 7개 API 함수 (Record<string, any> 컨벤션)
- components/admin/SubstituteSection.tsx (신규): 관리자용 대무자 지정 섹션
· 활성/예정 대무 관계 테이블, 사전 겹침 검증
· v5 토큰 (--v5-surface-solid, --v5-glow-sm) 사용, blur 금지
- components/admin/UserFormModal.tsx: 수정 모드일 때 SubstituteSection 노출
- components/layout/MySubstituteView.tsx (신규): ProfileModal 용 read-only 조회
· 내 대무자 + 내가 대무 중인 사람 양방향, D-day 카운트다운
- components/layout/ProfileModal.tsx: MySubstituteView 삽입
- app/(main)/approval/page.tsx: 대기함 행에 "대무 ← {원본 결재자}" 뱃지
· currentUser.user_id !== line.approver_id 비교 (별도 타입 필드 X)
116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
/**
|
|
* 대무자(代務者) 관리 API 클라이언트
|
|
* 엔드포인트: /api/substitutes/*
|
|
*
|
|
* 데이터 타입은 CLAUDE.md 컨벤션에 따라 Record<string, any>.
|
|
* 백엔드가 Map<String, Object> 로 응답.
|
|
*/
|
|
|
|
import { apiClient } from "@/lib/api/client";
|
|
import type { ApiResponse } from "./approval";
|
|
|
|
/**
|
|
* 회사 전체 대무 관계 조회 (관리자).
|
|
* @param params - status, original_user_id, proxy_user_id, limit, offset 등 옵션
|
|
*/
|
|
export async function getSubstituteList(
|
|
params: Record<string, any> = {}
|
|
): Promise<ApiResponse<{ list: Record<string, any>[]; total: number }>> {
|
|
try {
|
|
const response = await apiClient.get("/substitutes", { params });
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 본인 대무 관계 조회 (ProfileModal read-only).
|
|
* 반환: { proxying_for_me: [...], my_proxies: [...] }
|
|
*/
|
|
export async function getMySubstitutes(): Promise<
|
|
ApiResponse<{
|
|
proxying_for_me: Record<string, any>[];
|
|
my_proxies: Record<string, any>[];
|
|
}>
|
|
> {
|
|
try {
|
|
const response = await apiClient.get("/substitutes/mine");
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
export async function getSubstituteInfo(
|
|
substituteId: number
|
|
): Promise<ApiResponse<Record<string, any>>> {
|
|
try {
|
|
const response = await apiClient.get(`/substitutes/${substituteId}`);
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 대무자 신규 지정 (관리자).
|
|
* end_date 필수, start_date 옵션(비우면 즉시).
|
|
*/
|
|
export async function createSubstitute(data: {
|
|
original_user_id: string;
|
|
proxy_user_id: string;
|
|
end_date: string;
|
|
start_date?: string;
|
|
reason?: string;
|
|
}): Promise<ApiResponse<Record<string, any>>> {
|
|
try {
|
|
const response = await apiClient.post("/substitutes", data);
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
export async function updateSubstitute(
|
|
substituteId: number,
|
|
data: Record<string, any>
|
|
): Promise<ApiResponse<Record<string, any>>> {
|
|
try {
|
|
const response = await apiClient.put(`/substitutes/${substituteId}`, data);
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
export async function deleteSubstitute(
|
|
substituteId: number
|
|
): Promise<ApiResponse<void>> {
|
|
try {
|
|
const response = await apiClient.delete(`/substitutes/${substituteId}`);
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 같은 (original_user_id, proxy_user_id) 쌍의 기간 겹침 사전 검증.
|
|
* UI 등록 직전 호출 (백엔드 EXCLUDE 제약이 최종 방어).
|
|
*/
|
|
export async function checkSubstituteOverlap(data: {
|
|
original_user_id: string;
|
|
proxy_user_id: string;
|
|
start_date?: string | null;
|
|
end_date: string;
|
|
exclude_substitute_id?: number;
|
|
}): Promise<ApiResponse<{ overlap: boolean; count: number }>> {
|
|
try {
|
|
const response = await apiClient.post("/substitutes/check-overlap", data);
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|