From 4eb7532f822c821fb6b69bf103bd075fefba769b Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 15 Jul 2025 10:07:09 +0000 Subject: (대표님) 20250715 협력사 정기평가, spreadJS, roles 서비스에 함수 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/roles/services.ts | 104 +++++++++++- lib/roles/table/add-role-dialog.tsx | 248 +++++++++++++++++----------- lib/roles/table/assign-roles-sheet.tsx | 168 +++++++++++++++---- lib/roles/table/roles-table.tsx | 60 +++---- lib/roles/table/update-roles-sheet.tsx | 2 +- lib/roles/userTable/assignedUsers-table.tsx | 127 ++++++++++++-- 6 files changed, 521 insertions(+), 188 deletions(-) (limited to 'lib/roles') diff --git a/lib/roles/services.ts b/lib/roles/services.ts index 1a91d4fa..54c7d833 100644 --- a/lib/roles/services.ts +++ b/lib/roles/services.ts @@ -3,7 +3,7 @@ import { revalidateTag, unstable_cache, unstable_noStore } from "next/cache"; import db from "@/db/db"; import { permissions, Role, rolePermissions, roles, RoleView, roleView, userRoles } from "@/db/schema/users"; -import { and, or, asc, desc, ilike, eq, inArray } from "drizzle-orm"; +import { and, or, asc, desc, ilike, eq, inArray, sql } from "drizzle-orm"; import { filterColumns } from "@/lib/filter-columns"; import { selectRolesWithUserCount, @@ -297,4 +297,106 @@ export async function getMenuPermissions( .where(ilike(permissions.permissionKey, pattern)); return rows; +} + + +export async function checkRegularEvaluationRoleExists(): Promise { + try { + const existingRoles = await db + .select({ id: roles.id, name: roles.name }) + .from(roles) + .where(sql`${roles.name} ILIKE '%정기평가%'`) + .limit(1) + + return existingRoles.length > 0 + } catch (error) { + console.error("정기평가 role 체크 중 에러:", error) + throw new Error("정기평가 role 체크에 실패했습니다") + } +} + + + +/** + * 여러 정기평가 role들의 할당 상태를 한번에 체크 + */ +export async function checkMultipleRegularEvaluationRolesAssigned(roleIds: number[]): Promise<{[roleId: number]: boolean}> { + try { + // 정기평가 role들만 필터링 + const regularEvaluationRoles = await db + .select({ id: roles.id, name: roles.name }) + .from(roles) + .where( + and( + inArray(roles.id, roleIds), + sql`${roles.name} ILIKE '%정기평가%'` + ) + ) + + const regularEvaluationRoleIds = regularEvaluationRoles.map(r => r.id) + const result: {[roleId: number]: boolean} = {} + + // 모든 role ID에 대해 초기값 설정 + roleIds.forEach(roleId => { + result[roleId] = false + }) + + if (regularEvaluationRoleIds.length > 0) { + // 할당된 정기평가 role들 체크 + const assignedRoles = await db + .select({ roleId: userRoles.roleId }) + .from(userRoles) + .where(inArray(userRoles.roleId, regularEvaluationRoleIds)) + + // 할당된 role들을 true로 설정 + assignedRoles.forEach(assignment => { + result[assignment.roleId] = true + }) + } + + return result + } catch (error) { + console.error("여러 정기평가 role 할당 상태 체크 중 에러:", error) + throw new Error("정기평가 role 할당 상태 체크에 실패했습니다") + } +} + +/** + * 특정 유저가 이미 다른 정기평가 role을 가지고 있는지 체크 + */ +export async function checkUserHasRegularEvaluationRole(userId: string): Promise<{hasRole: boolean, roleName?: string}> { + try { + const userRegularEvaluationRoles = await db + .select({ + roleId: userRoles.roleId, + roleName: roles.name + }) + .from(userRoles) + .innerJoin(roles, eq(userRoles.roleId, roles.id)) + .where( + and( + eq(userRoles.userId, userId), + sql`${roles.name} ILIKE '%정기평가%'` + ) + ) + .limit(1) + + return { + hasRole: userRegularEvaluationRoles.length > 0, + roleName: userRegularEvaluationRoles[0]?.roleName + } + } catch (error) { + console.error(`유저 ${userId}의 정기평가 role 체크 중 에러:`, error) + throw new Error("유저 정기평가 role 체크에 실패했습니다") + } +} + + +export async function removeRolesFromUsers(roleIds: number[], userIds: number[]) { + try { + // userRoles 테이블에서 해당 역할들을 제거하는 로직 + // 구현 필요 + } catch (error) { + return { error: "역할 제거 실패" } + } } \ No newline at end of file diff --git a/lib/roles/table/add-role-dialog.tsx b/lib/roles/table/add-role-dialog.tsx index 365daf29..162aaa89 100644 --- a/lib/roles/table/add-role-dialog.tsx +++ b/lib/roles/table/add-role-dialog.tsx @@ -21,12 +21,13 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" -import { Check, ChevronsUpDown, Loader } from "lucide-react" +import { Check, ChevronsUpDown, Loader, AlertTriangle } from "lucide-react" import { cn } from "@/lib/utils" import { toast } from "sonner" +import { Alert, AlertDescription } from "@/components/ui/alert" import { createRoleSchema, type CreateRoleSchema } from "../validations" -import { createRole } from "../services" +import { createRole, checkRegularEvaluationRoleExists } from "../services" import { Textarea } from "@/components/ui/textarea" import { Company } from "@/db/schema/companies" import { getAllCompanies } from "@/lib/admin-users/service" @@ -44,8 +45,6 @@ import { CommandEmpty, } from "@/components/ui/command" - - const domainOptions = [ { value: "partners", label: "협력업체" }, { value: "evcp", label: "삼성중공업" }, @@ -54,7 +53,9 @@ const domainOptions = [ export function AddRoleDialog() { const [open, setOpen] = React.useState(false) const [isAddPending, startAddTransition] = React.useTransition() - const [companies, setCompanies] = React.useState([]) // 회사 목록 + const [companies, setCompanies] = React.useState([]) + const [regularEvaluationExists, setRegularEvaluationExists] = React.useState(false) + const [isCheckingRegularEvaluation, setIsCheckingRegularEvaluation] = React.useState(false) React.useEffect(() => { getAllCompanies().then((res) => { @@ -67,12 +68,39 @@ export function AddRoleDialog() { resolver: zodResolver(createRoleSchema), defaultValues: { name: "", - domain: "evcp", // 기본값 + domain: "evcp", description: "", - // companyId: null, // optional }, }) + // name 필드 watch + const watchedName = form.watch("name") + + // "정기평가"가 포함된 이름인지 체크 + const isRegularEvaluationRole = watchedName.includes("정기평가") + + // 정기평가 role 존재 여부 체크 (debounced) + React.useEffect(() => { + if (!isRegularEvaluationRole) { + setRegularEvaluationExists(false) + return + } + + const timeoutId = setTimeout(async () => { + setIsCheckingRegularEvaluation(true) + try { + const exists = await checkRegularEvaluationRoleExists() + setRegularEvaluationExists(exists) + } catch (error) { + console.error("정기평가 role 체크 실패:", error) + } finally { + setIsCheckingRegularEvaluation(false) + } + }, 500) // 500ms debounce + + return () => clearTimeout(timeoutId) + }, [isRegularEvaluationRole, watchedName]) + async function onSubmit(data: CreateRoleSchema) { startAddTransition(async () => { const result = await createRole(data) @@ -82,19 +110,21 @@ export function AddRoleDialog() { } form.reset() setOpen(false) - toast.success("Role added") + setRegularEvaluationExists(false) + toast.success("Role이 성공적으로 추가되었습니다") }) } function handleDialogOpenChange(nextOpen: boolean) { if (!nextOpen) { form.reset() + setRegularEvaluationExists(false) } setOpen(nextOpen) } - // domain이 partners일 경우 companyId 입력 필드 보이게 const selectedDomain = form.watch("domain") + const canSubmit = !isRegularEvaluationRole || !regularEvaluationExists return ( @@ -129,6 +159,35 @@ export function AddRoleDialog() { /> + + {/* 정기평가 관련 경고 메시지 */} + {isRegularEvaluationRole && ( +
+ {isCheckingRegularEvaluation ? ( + + + + 정기평가 role 존재 여부를 확인하고 있습니다... + + + ) : regularEvaluationExists ? ( + + + + 경고: "정기평가"가 포함된 role이 이미 존재합니다. + 정기평가 role은 시스템에서 하나만 허용됩니다. + + + ) : ( + + + + 정기평가 role을 생성할 수 있습니다. + + + )} +
+ )} )} /> @@ -161,7 +220,6 @@ export function AddRoleDialog() { Domain