- CEO 대시보드 컴포넌트 추가 - AuthenticatedLayout 개선 - 각 모듈 actions.ts 에러 핸들링 개선 - API fetch-wrapper, refresh-token 로직 개선 - ReceivablesStatus 컴포넌트 업데이트 - globals.css 스타일 업데이트 - 기타 다수 컴포넌트 수정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
451 lines
13 KiB
TypeScript
451 lines
13 KiB
TypeScript
'use server';
|
|
|
|
|
|
import { isRedirectError } from 'next/dist/client/components/redirect';
|
|
import { revalidatePath } from 'next/cache';
|
|
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
|
import type { Role, RoleStats, PermissionMatrix, MenuTreeItem, ApiResponse, PaginatedResponse } from './types';
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
|
|
// ========== Role CRUD ==========
|
|
|
|
/**
|
|
* 역할 목록 조회
|
|
*/
|
|
export async function fetchRoles(params?: {
|
|
page?: number;
|
|
size?: number;
|
|
q?: string;
|
|
is_hidden?: boolean;
|
|
}): Promise<ApiResponse<PaginatedResponse<Role>>> {
|
|
try {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.page) searchParams.set('page', params.page.toString());
|
|
if (params?.size) searchParams.set('per_page', params.size.toString());
|
|
if (params?.q) searchParams.set('q', params.q);
|
|
if (params?.is_hidden !== undefined) searchParams.set('is_hidden', params.is_hidden.toString());
|
|
|
|
const url = `${API_URL}/api/v1/roles?${searchParams.toString()}`;
|
|
const { response, error } = await serverFetch(url, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '역할 목록 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '역할 목록 조회 실패' };
|
|
}
|
|
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to fetch roles:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '역할 목록 조회 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할 상세 조회
|
|
*/
|
|
export async function fetchRole(id: number): Promise<ApiResponse<Role>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${id}`, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '역할 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '역할 조회 실패' };
|
|
}
|
|
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to fetch role:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '역할 조회 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할 생성
|
|
*/
|
|
export async function createRole(data: {
|
|
name: string;
|
|
description?: string;
|
|
is_hidden?: boolean;
|
|
}): Promise<ApiResponse<Role>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '역할 생성에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '역할 생성 실패' };
|
|
}
|
|
|
|
revalidatePath('/settings/permissions');
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to create role:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '역할 생성 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할 수정
|
|
*/
|
|
export async function updateRole(
|
|
id: number,
|
|
data: {
|
|
name?: string;
|
|
description?: string;
|
|
is_hidden?: boolean;
|
|
}
|
|
): Promise<ApiResponse<Role>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '역할 수정에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '역할 수정 실패' };
|
|
}
|
|
|
|
revalidatePath('/settings/permissions');
|
|
revalidatePath(`/settings/permissions/${id}`);
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to update role:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '역할 수정 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할 삭제
|
|
*/
|
|
export async function deleteRole(id: number): Promise<ApiResponse<void>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '역할 삭제에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '역할 삭제 실패' };
|
|
}
|
|
|
|
revalidatePath('/settings/permissions');
|
|
return { success: true };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to delete role:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '역할 삭제 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할 통계 조회
|
|
*/
|
|
export async function fetchRoleStats(): Promise<ApiResponse<RoleStats>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/stats`, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '역할 통계 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '역할 통계 조회 실패' };
|
|
}
|
|
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to fetch role stats:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '역할 통계 조회 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 활성 역할 목록 (드롭다운용)
|
|
*/
|
|
export async function fetchActiveRoles(): Promise<ApiResponse<Role[]>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/active`, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '활성 역할 목록 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '활성 역할 목록 조회 실패' };
|
|
}
|
|
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to fetch active roles:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '활성 역할 목록 조회 실패' };
|
|
}
|
|
}
|
|
|
|
// ========== Permission Matrix ==========
|
|
|
|
/**
|
|
* 권한 매트릭스용 메뉴 트리 조회
|
|
*/
|
|
export async function fetchPermissionMenus(): Promise<ApiResponse<{
|
|
menus: MenuTreeItem[];
|
|
permission_types: string[];
|
|
}>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/role-permissions/menus`, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '메뉴 트리 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '메뉴 트리 조회 실패' };
|
|
}
|
|
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to fetch permission menus:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '메뉴 트리 조회 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할의 권한 매트릭스 조회
|
|
*/
|
|
export async function fetchPermissionMatrix(roleId: number): Promise<ApiResponse<PermissionMatrix>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${roleId}/permissions/matrix`, { method: 'GET' });
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '권한 매트릭스 조회에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '권한 매트릭스 조회 실패' };
|
|
}
|
|
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to fetch permission matrix:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '권한 매트릭스 조회 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 메뉴의 특정 권한 토글
|
|
*/
|
|
export async function togglePermission(
|
|
roleId: number,
|
|
menuId: number,
|
|
permissionType: string
|
|
): Promise<ApiResponse<{
|
|
granted: boolean;
|
|
propagated_to: number[];
|
|
}>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${roleId}/permissions/toggle`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
menu_id: menuId,
|
|
permission_type: permissionType,
|
|
}),
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '권한 토글에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '권한 토글 실패' };
|
|
}
|
|
|
|
revalidatePath(`/settings/permissions/${roleId}`);
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to toggle permission:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '권한 토글 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 모든 권한 허용
|
|
*/
|
|
export async function allowAllPermissions(roleId: number): Promise<ApiResponse<{ count: number }>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${roleId}/permissions/allow-all`, {
|
|
method: 'POST',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '전체 허용에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '전체 허용 실패' };
|
|
}
|
|
|
|
revalidatePath(`/settings/permissions/${roleId}`);
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to allow all permissions:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '전체 허용 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 모든 권한 거부
|
|
*/
|
|
export async function denyAllPermissions(roleId: number): Promise<ApiResponse<{ count: number }>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${roleId}/permissions/deny-all`, {
|
|
method: 'POST',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '전체 거부에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '전체 거부 실패' };
|
|
}
|
|
|
|
revalidatePath(`/settings/permissions/${roleId}`);
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to deny all permissions:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '전체 거부 실패' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 기본 권한으로 초기화 (view만 허용)
|
|
*/
|
|
export async function resetPermissions(roleId: number): Promise<ApiResponse<{ count: number }>> {
|
|
try {
|
|
const { response, error } = await serverFetch(`${API_URL}/api/v1/roles/${roleId}/permissions/reset`, {
|
|
method: 'POST',
|
|
});
|
|
|
|
if (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
if (!response) {
|
|
return { success: false, error: '권한 초기화에 실패했습니다.' };
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok || !result.success) {
|
|
return { success: false, error: result.message || '권한 초기화 실패' };
|
|
}
|
|
|
|
revalidatePath(`/settings/permissions/${roleId}`);
|
|
return { success: true, data: result.data };
|
|
} catch (error) {
|
|
if (isRedirectError(error)) throw error;
|
|
console.error('Failed to reset permissions:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : '권한 초기화 실패' };
|
|
}
|
|
} |