Files
invyone/frontend/lib/api/department.ts
T
johngreen 0e895a90fa feat(부서관리): V1 슬림 스코프 + 트리 컨텍스트 메뉴 UX 리디자인
백엔드:
- V018 soft-delete (deleted_at 컬럼) + 휴지통/복구 흐름
- V019 미사용 컬럼 cleanup (V1 슬림 스코프)
- DepartmentService.updateDepartment 에 parent_dept_code 사이클 가드
  (자기 자신/자손을 부모로 지정 시도 차단)
- DepartmentController, mapper 갱신

프론트:
- 부서관리 페이지(deptMngList) UX 리디자인
  - 트리 노드 ⋮ 컨텍스트 메뉴 (하위 추가, 다른 부서 아래로 이동, 정렬 4단계, 삭제)
  - 헤더 breadcrumb 으로 부서 위치 상시 표시
  - 폼의 상위부서 row 제거 (트리 ⋮ 로 진입점 일원화)
  - 빈 상태 placeholder + X 닫기 동작
  - 토글 버튼 토스 스타일 (아이콘 + 툴팁, 일정한 위치)
  - 부서유형 row 좁은 화면 가로 오버플로 fix
- DepartmentPicker 신규 재사용 컴포넌트 (자손 자동 exclude, 사이클 차단)
- 회사관리/프로비저닝 폼 개선 (Step1Basic, fields, CompanyTable, AdminPageRenderer)
- companyList/[companyCode]/departments 구버전 페이지 삭제

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:34:23 +09:00

190 lines
5.5 KiB
TypeScript

/**
* 부서 관리 API 클라이언트
*/
import { apiClient } from "./client";
import { Department, DepartmentMember, DepartmentFormData } from "@/types/department";
/**
* 부서 목록 조회 (회사별).
* options.includeDeleted=true 시 soft-delete 된 부서도 포함.
*/
export async function getDepartments(
companyCode: string,
options?: { includeDeleted?: boolean },
) {
try {
const url = `/departments/companies/${companyCode}/departments`;
const response = await apiClient.get<{ success: boolean; data: Department[] }>(url, {
params: options?.includeDeleted ? { include_deleted: true } : undefined,
});
return response.data;
} catch (error: any) {
console.error("부서 목록 조회 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 부서 상세 조회
*/
export async function getDepartment(deptCode: string) {
try {
const response = await apiClient.get<{ success: boolean; data: Department }>(`/departments/${deptCode}`);
return response.data;
} catch (error: any) {
console.error("부서 상세 조회 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 부서 생성
*/
export async function createDepartment(companyCode: string, data: DepartmentFormData) {
try {
const response = await apiClient.post<{ success: boolean; data: Department }>(
`/departments/companies/${companyCode}/departments`,
data,
);
return response.data;
} catch (error: any) {
console.error("부서 생성 실패:", error);
const isDuplicate = error.response?.status === 409;
return {
success: false,
error: error.response?.data?.message || error.message,
isDuplicate,
};
}
}
/**
* 부서 수정
*/
export async function updateDepartment(deptCode: string, data: DepartmentFormData) {
try {
const response = await apiClient.put<{ success: boolean; data: Department }>(`/departments/${deptCode}`, data);
return response.data;
} catch (error: any) {
console.error("부서 수정 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 부서 삭제 (V1: soft-delete).
* 응답 호환: 기존 { success, message } 에 data.soft_deleted=true 필드 추가.
*/
export async function deleteDepartment(deptCode: string) {
try {
const response = await apiClient.delete<{
success: boolean;
message?: string;
data?: { soft_deleted?: boolean; dept_code?: string };
}>(`/departments/${deptCode}`);
return response.data;
} catch (error: any) {
console.error("부서 삭제 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 부서 복구 (V1 신규 — soft-delete 된 부서 되살리기).
* 부모가 deleted 면 차단 (400) → "상위 부서를 먼저 복구해주세요" 메시지.
*/
export async function restoreDepartment(deptCode: string) {
try {
const response = await apiClient.post<{
success: boolean;
message?: string;
data?: { dept_code?: string; restored?: boolean };
}>(`/departments/${deptCode}/restore`);
return response.data;
} catch (error: any) {
console.error("부서 복구 실패:", error);
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
/**
* 부서원 목록 조회
*/
export async function getDepartmentMembers(deptCode: string) {
try {
const response = await apiClient.get<{ success: boolean; data: DepartmentMember[] }>(
`/departments/${deptCode}/members`,
);
return response.data;
} catch (error: any) {
console.error("부서원 목록 조회 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 사용자 검색 (부서원 추가용)
*/
export async function searchUsers(companyCode: string, search: string) {
try {
const response = await apiClient.get<{ success: boolean; data: any[] }>(
`/departments/companies/${companyCode}/users/search`,
{ params: { search } },
);
return response.data;
} catch (error: any) {
console.error("사용자 검색 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 부서원 추가
*/
export async function addDepartmentMember(deptCode: string, userId: string) {
try {
const response = await apiClient.post<{ success: boolean; message?: string }>(`/departments/${deptCode}/members`, {
user_id: userId,
});
return response.data;
} catch (error: any) {
console.error("부서원 추가 실패:", error);
const isDuplicate = error.response?.status === 409;
return {
success: false,
error: error.response?.data?.message || error.message,
isDuplicate,
};
}
}
/**
* 부서원 제거
*/
export async function removeDepartmentMember(deptCode: string, userId: string) {
try {
const response = await apiClient.delete<{ success: boolean }>(`/departments/${deptCode}/members/${userId}`);
return response.data;
} catch (error: any) {
console.error("부서원 제거 실패:", error);
return { success: false, error: error.message };
}
}
/**
* 주 부서 설정
*/
export async function setPrimaryDepartment(deptCode: string, userId: string) {
try {
const response = await apiClient.put<{ success: boolean }>(`/departments/${deptCode}/members/${userId}/primary`);
return response.data;
} catch (error: any) {
console.error("주 부서 설정 실패:", error);
return { success: false, error: error.message };
}
}