Files
invyone/frontend/lib/api/substitute.ts
T
johngreen 6a9fc06f0e feat(대무자): 프론트엔드 UI — UserFormModal 대무자 섹션 + ProfileModal 조회 + 결재 뱃지
- 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)
2026-05-12 08:07:15 +09:00

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 };
}
}