feat: Enhance approval request handling and user management
- Updated the approval request controller to include target_record_id in query parameters for improved filtering. - Refactored the approval request creation logic to merge approval_mode into target_record_data, allowing for better handling of approval processes. - Enhanced the user search functionality in the approval request modal to accommodate additional user attributes such as position and department. - Improved error handling messages for clarity regarding required fields in the approval request modal. - Added new menu item for accessing the approval box directly from user dropdown and app layout. Made-with: Cursor
This commit is contained in:
@@ -41,12 +41,16 @@ interface ApprovalRequestModalProps {
|
||||
}
|
||||
|
||||
interface UserSearchResult {
|
||||
user_id: string;
|
||||
user_name: string;
|
||||
userId: string;
|
||||
userName: string;
|
||||
positionName?: string;
|
||||
deptName?: string;
|
||||
deptCode?: string;
|
||||
email?: string;
|
||||
user_id?: string;
|
||||
user_name?: string;
|
||||
position_name?: string;
|
||||
dept_name?: string;
|
||||
dept_code?: string;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
function genId(): string {
|
||||
@@ -98,10 +102,17 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
|
||||
try {
|
||||
const res = await getUserList({ search: query.trim(), limit: 20 });
|
||||
const data = res?.data || res || [];
|
||||
const users: UserSearchResult[] = Array.isArray(data) ? data : [];
|
||||
// 이미 추가된 결재자 제외
|
||||
const rawUsers: any[] = Array.isArray(data) ? data : [];
|
||||
const users: UserSearchResult[] = rawUsers.map((u: any) => ({
|
||||
userId: u.userId || u.user_id || "",
|
||||
userName: u.userName || u.user_name || "",
|
||||
positionName: u.positionName || u.position_name || "",
|
||||
deptName: u.deptName || u.dept_name || "",
|
||||
deptCode: u.deptCode || u.dept_code || "",
|
||||
email: u.email || "",
|
||||
}));
|
||||
const existingIds = new Set(approvers.map((a) => a.user_id));
|
||||
setSearchResults(users.filter((u) => !existingIds.has(u.user_id)));
|
||||
setSearchResults(users.filter((u) => u.userId && !existingIds.has(u.userId)));
|
||||
} catch {
|
||||
setSearchResults([]);
|
||||
} finally {
|
||||
@@ -128,10 +139,10 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
|
||||
...prev,
|
||||
{
|
||||
id: genId(),
|
||||
user_id: user.user_id,
|
||||
user_name: user.user_name,
|
||||
position_name: user.position_name || "",
|
||||
dept_name: user.dept_name || "",
|
||||
user_id: user.userId,
|
||||
user_name: user.userName,
|
||||
position_name: user.positionName || "",
|
||||
dept_name: user.deptName || "",
|
||||
},
|
||||
]);
|
||||
setSearchQuery("");
|
||||
@@ -162,8 +173,8 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
|
||||
setError("결재자를 1명 이상 추가해주세요.");
|
||||
return;
|
||||
}
|
||||
if (!eventDetail?.targetTable || !eventDetail?.targetRecordId) {
|
||||
setError("결재 대상 정보가 없습니다. 레코드를 선택 후 다시 시도해주세요.");
|
||||
if (!eventDetail?.targetTable) {
|
||||
setError("결재 대상 테이블 정보가 없습니다. 버튼 설정을 확인해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,11 +185,9 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
|
||||
title: title.trim(),
|
||||
description: description.trim() || undefined,
|
||||
target_table: eventDetail.targetTable,
|
||||
target_record_id: eventDetail.targetRecordId,
|
||||
target_record_data: {
|
||||
...eventDetail.targetRecordData,
|
||||
approval_mode: approvalMode,
|
||||
},
|
||||
target_record_id: eventDetail.targetRecordId || undefined,
|
||||
target_record_data: eventDetail.targetRecordData,
|
||||
approval_mode: approvalMode,
|
||||
screen_id: eventDetail.screenId,
|
||||
button_component_id: eventDetail.buttonComponentId,
|
||||
approvers: approvers.map((a, idx) => ({
|
||||
@@ -321,7 +330,7 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
|
||||
<div className="max-h-48 overflow-y-auto">
|
||||
{searchResults.map((user) => (
|
||||
<button
|
||||
key={user.user_id}
|
||||
key={user.userId}
|
||||
type="button"
|
||||
onClick={() => addApprover(user)}
|
||||
className="flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-accent"
|
||||
@@ -331,13 +340,13 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-xs font-medium sm:text-sm">
|
||||
{user.user_name}
|
||||
{user.userName}
|
||||
<span className="text-muted-foreground ml-1 text-[10px]">
|
||||
({user.user_id})
|
||||
({user.userId})
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-muted-foreground truncate text-[10px]">
|
||||
{[user.dept_name, user.position_name].filter(Boolean).join(" / ") || "-"}
|
||||
{[user.deptName, user.positionName].filter(Boolean).join(" / ") || "-"}
|
||||
</p>
|
||||
</div>
|
||||
<Plus className="text-muted-foreground h-4 w-4 shrink-0" />
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
LogOut,
|
||||
User,
|
||||
Building2,
|
||||
FileCheck,
|
||||
} from "lucide-react";
|
||||
import { useMenu } from "@/contexts/MenuContext";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
@@ -524,6 +525,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>프로필</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => router.push("/admin/approvalBox")}>
|
||||
<FileCheck className="mr-2 h-4 w-4" />
|
||||
<span>결재함</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>로그아웃</span>
|
||||
@@ -692,6 +698,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>프로필</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => router.push("/admin/approvalBox")}>
|
||||
<FileCheck className="mr-2 h-4 w-4" />
|
||||
<span>결재함</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>로그아웃</span>
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { LogOut, User } from "lucide-react";
|
||||
import { LogOut, User, FileCheck } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface UserDropdownProps {
|
||||
user: any;
|
||||
@@ -20,6 +21,8 @@ interface UserDropdownProps {
|
||||
* 사용자 드롭다운 메뉴 컴포넌트
|
||||
*/
|
||||
export function UserDropdown({ user, onProfileClick, onLogout }: UserDropdownProps) {
|
||||
const router = useRouter();
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
@@ -79,6 +82,11 @@ export function UserDropdown({ user, onProfileClick, onLogout }: UserDropdownPro
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>프로필</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => router.push("/admin/approvalBox")}>
|
||||
<FileCheck className="mr-2 h-4 w-4" />
|
||||
<span>결재함</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={onLogout}>
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>로그아웃</span>
|
||||
|
||||
@@ -577,8 +577,10 @@ export const OptimizedButtonComponent: React.FC<OptimizedButtonProps> = ({
|
||||
const getActionDisplayName = (actionType: ButtonActionType): string => {
|
||||
const displayNames: Record<ButtonActionType, string> = {
|
||||
save: "저장",
|
||||
cancel: "취소",
|
||||
delete: "삭제",
|
||||
edit: "수정",
|
||||
copy: "복사",
|
||||
add: "추가",
|
||||
search: "검색",
|
||||
reset: "초기화",
|
||||
@@ -589,6 +591,9 @@ export const OptimizedButtonComponent: React.FC<OptimizedButtonProps> = ({
|
||||
newWindow: "새 창",
|
||||
navigate: "페이지 이동",
|
||||
control: "제어",
|
||||
transferData: "데이터 전달",
|
||||
quickInsert: "즉시 저장",
|
||||
approval: "결재",
|
||||
};
|
||||
return displayNames[actionType] || actionType;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user