summaryrefslogtreecommitdiff
path: root/lib/roles
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-15 10:07:09 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-15 10:07:09 +0000
commit4eb7532f822c821fb6b69bf103bd075fefba769b (patch)
treeb4bcf6c0bf791d71569f3f35498ed256bf7cfaf3 /lib/roles
parent660c7888d885badab7af3e96f9c16bd0172ad0f1 (diff)
(대표님) 20250715 협력사 정기평가, spreadJS, roles 서비스에 함수 추가
Diffstat (limited to 'lib/roles')
-rw-r--r--lib/roles/services.ts104
-rw-r--r--lib/roles/table/add-role-dialog.tsx248
-rw-r--r--lib/roles/table/assign-roles-sheet.tsx168
-rw-r--r--lib/roles/table/roles-table.tsx60
-rw-r--r--lib/roles/table/update-roles-sheet.tsx2
-rw-r--r--lib/roles/userTable/assignedUsers-table.tsx127
6 files changed, 521 insertions, 188 deletions
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<boolean> {
+ 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<Company[]>([]) // 회사 목록
+ const [companies, setCompanies] = React.useState<Company[]>([])
+ 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 (
<Dialog open={open} onOpenChange={handleDialogOpenChange}>
@@ -129,6 +159,35 @@ export function AddRoleDialog() {
/>
</FormControl>
<FormMessage />
+
+ {/* 정기평가 관련 경고 메시지 */}
+ {isRegularEvaluationRole && (
+ <div className="mt-2">
+ {isCheckingRegularEvaluation ? (
+ <Alert>
+ <Loader className="h-4 w-4 animate-spin" />
+ <AlertDescription>
+ 정기평가 role 존재 여부를 확인하고 있습니다...
+ </AlertDescription>
+ </Alert>
+ ) : regularEvaluationExists ? (
+ <Alert variant="destructive">
+ <AlertTriangle className="h-4 w-4" />
+ <AlertDescription>
+ <strong>경고:</strong> "정기평가"가 포함된 role이 이미 존재합니다.
+ 정기평가 role은 시스템에서 하나만 허용됩니다.
+ </AlertDescription>
+ </Alert>
+ ) : (
+ <Alert>
+ <Check className="h-4 w-4" />
+ <AlertDescription>
+ 정기평가 role을 생성할 수 있습니다.
+ </AlertDescription>
+ </Alert>
+ )}
+ </div>
+ )}
</FormItem>
)}
/>
@@ -161,7 +220,6 @@ export function AddRoleDialog() {
<FormLabel>Domain</FormLabel>
<FormControl>
<Select
- // domain이 바뀔 때마다 form state에도 반영
onValueChange={field.onChange}
value={field.value}
>
@@ -184,96 +242,85 @@ export function AddRoleDialog() {
{/* 4) companyId => domain이 partners인 경우만 노출 */}
{selectedDomain === "partners" && (
- <FormField
- control={form.control}
- name="companyId"
- render={({ field }) => {
- // 현재 선택된 회사 ID (number) → 문자열
- const valueString = field.value ? String(field.value) : ""
-
+ <FormField
+ control={form.control}
+ name="companyId"
+ render={({ field }) => {
+ const valueString = field.value ? String(field.value) : ""
+ const selectedCompany = companies.find(
+ (c) => String(c.id) === valueString
+ )
+ const selectedCompanyLabel = selectedCompany && `${selectedCompany.name} ${selectedCompany.taxID}`
+ const [popoverOpen, setPopoverOpen] = React.useState(false)
- // 현재 선택된 회사
- const selectedCompany = companies.find(
- (c) => String(c.id) === valueString
- )
-
- const selectedCompanyLabel = selectedCompany && `${selectedCompany.name} ${selectedCompany.taxID}`
-
- const [popoverOpen, setPopoverOpen] = React.useState(false)
-
-
- return (
- <FormItem>
- <FormLabel>Company</FormLabel>
- <FormControl>
- <Popover
- open={popoverOpen}
- onOpenChange={setPopoverOpen}
- modal={true}
- >
- <PopoverTrigger asChild>
- <Button
- variant="outline"
- role="combobox"
- aria-expanded={popoverOpen}
- className="w-full justify-between"
+ return (
+ <FormItem>
+ <FormLabel>Company</FormLabel>
+ <FormControl>
+ <Popover
+ open={popoverOpen}
+ onOpenChange={setPopoverOpen}
+ modal={true}
>
- {selectedCompany
- ? `${selectedCompany.name} ${selectedCompany.taxID}`
- : "Select company..."}
- <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
- </Button>
- </PopoverTrigger>
-
- <PopoverContent className="w-full p-0">
- <Command>
- <CommandInput
- placeholder="Search company..."
- className="h-9"
-
- />
- <CommandList>
- <CommandEmpty>No company found.</CommandEmpty>
- <CommandGroup>
- {companies.map((comp) => {
- // string(comp.id)
- const compIdStr = String(comp.id)
- const label = `${comp.name}${comp.taxID}`
- const label2 = `${comp.name} ${comp.taxID}`
- return (
- <CommandItem
- key={comp.id}
- value={label2}
- onSelect={() => {
- // 회사 ID를 number로
- field.onChange(Number(comp.id))
- setPopoverOpen(false)
+ <PopoverTrigger asChild>
+ <Button
+ variant="outline"
+ role="combobox"
+ aria-expanded={popoverOpen}
+ className="w-full justify-between"
+ >
+ {selectedCompany
+ ? `${selectedCompany.name} ${selectedCompany.taxID}`
+ : "Select company..."}
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+ </Button>
+ </PopoverTrigger>
- }}
- >
- {label2}
- <Check
- className={cn(
- "ml-auto h-4 w-4",
- selectedCompanyLabel === label2
- ? "opacity-100"
- : "opacity-0"
- )}
- />
- </CommandItem>
- )
- })}
- </CommandGroup>
- </CommandList>
- </Command>
- </PopoverContent>
- </Popover>
- </FormControl>
- <FormMessage />
- </FormItem>
- )
- }}
- />
+ <PopoverContent className="w-full p-0">
+ <Command>
+ <CommandInput
+ placeholder="Search company..."
+ className="h-9"
+ />
+ <CommandList>
+ <CommandEmpty>No company found.</CommandEmpty>
+ <CommandGroup>
+ {companies.map((comp) => {
+ const compIdStr = String(comp.id)
+ const label = `${comp.name}${comp.taxID}`
+ const label2 = `${comp.name} ${comp.taxID}`
+ return (
+ <CommandItem
+ key={comp.id}
+ value={label2}
+ onSelect={() => {
+ field.onChange(Number(comp.id))
+ setPopoverOpen(false)
+ }}
+ >
+ {label2}
+ <Check
+ className={cn(
+ "ml-auto h-4 w-4",
+ selectedCompanyLabel === label2
+ ? "opacity-100"
+ : "opacity-0"
+ )}
+ />
+ </CommandItem>
+ )
+ })}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )
+ }}
+ />
)}
</div>
@@ -289,7 +336,12 @@ export function AddRoleDialog() {
</Button>
<Button
type="submit"
- disabled={form.formState.isSubmitting || isAddPending}
+ disabled={
+ form.formState.isSubmitting ||
+ isAddPending ||
+ !canSubmit ||
+ isCheckingRegularEvaluation
+ }
>
{isAddPending && (
<Loader
diff --git a/lib/roles/table/assign-roles-sheet.tsx b/lib/roles/table/assign-roles-sheet.tsx
index 11c6a1ff..d750081c 100644
--- a/lib/roles/table/assign-roles-sheet.tsx
+++ b/lib/roles/table/assign-roles-sheet.tsx
@@ -1,10 +1,7 @@
"use client"
import * as React from "react"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useForm } from "react-hook-form"
import { toast } from "sonner"
-
import {
Sheet,
SheetClose,
@@ -15,71 +12,178 @@ import {
SheetTitle,
} from "@/components/ui/sheet"
import { Button } from "@/components/ui/button"
-import { Loader } from "lucide-react"
+import { Loader, Plus, Minus, Users } from "lucide-react"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Badge } from "@/components/ui/badge"
import { AssginedUserTable } from "../userTable/assignedUsers-table"
-import { assignUsersToRole } from "@/lib/users/service"
+import { assignUsersToRole, removeUsersFromRole } from "@/lib/users/service"
import { RoleView } from "@/db/schema/users"
-export interface UpdateRoleSheetProps
+export interface ManageRoleSheetProps
extends React.ComponentPropsWithRef<typeof Sheet> {
role: RoleView | null
-
- // ★ 새로 추가: 테이블에 필요한 데이터 로딩 promise
- assignedTablePromises: Promise<[
- { data: any[]; pageCount: number }
-
- ]>
+ // 현재 구조에 맞춰 allUsersPromises 사용
+ allUsersPromises: Promise<[{ data: any[]; pageCount: number }]>
}
-export function AssignRolesSheet({ role, assignedTablePromises, ...props }: UpdateRoleSheetProps) {
-
+export function ManageRoleSheet({
+ role,
+ allUsersPromises,
+ ...props
+}: ManageRoleSheetProps) {
const [isUpdatePending, startUpdateTransition] = React.useTransition()
const [selectedUserIds, setSelectedUserIds] = React.useState<number[]>([])
+ const [activeTab, setActiveTab] = React.useState("assign")
- // 2) 자식에서 호출될 콜백
function handleSelectedChange(ids: number[]) {
setSelectedUserIds(ids)
}
async function handleAssign() {
+ if (!role || selectedUserIds.length === 0) {
+ toast.error("선택된 사용자가 없습니다.")
+ return
+ }
+
+ startUpdateTransition(async () => {
+ const { data, error } = await assignUsersToRole(role.id, selectedUserIds)
+ if (error) {
+ toast.error(error)
+ return
+ }
+
+ setSelectedUserIds([]) // 선택 초기화
+ toast.success(data?.message || `${selectedUserIds.length}명의 사용자가 "${role.name}" 롤에 할당되었습니다.`)
+
+ // 작업 완료 후 시트를 닫지 않고 유지 (계속 작업할 수 있도록)
+ })
+ }
+
+ async function handleRemove() {
+ if (!role || selectedUserIds.length === 0) {
+ toast.error("선택된 사용자가 없습니다.")
+ return
+ }
+
startUpdateTransition(async () => {
- if (!role) return
- const { error } = await assignUsersToRole(role.id, selectedUserIds)
+ const { data, error } = await removeUsersFromRole(role.id, selectedUserIds)
if (error) {
toast.error(error)
return
}
- props.onOpenChange?.(false)
- toast.success(`Assigned ${selectedUserIds.length} users!`)
+
+ setSelectedUserIds([]) // 선택 초기화
+ toast.success(data?.message || `${selectedUserIds.length}명의 사용자가 "${role.name}" 롤에서 제거되었습니다.`)
})
}
+ // 탭 변경시 선택 초기화
+ React.useEffect(() => {
+ setSelectedUserIds([])
+ }, [activeTab])
+
+ // 롤 변경시 선택 초기화
+ React.useEffect(() => {
+ setSelectedUserIds([])
+ setActiveTab("assign") // 기본적으로 assign 탭으로 리셋
+ }, [role?.id])
+
+ // 시트가 닫힐 때 상태 초기화
+ React.useEffect(() => {
+ if (!props.open) {
+ setSelectedUserIds([])
+ setActiveTab("assign")
+ }
+ }, [props.open])
+
return (
<Sheet {...props}>
- <SheetContent className="flex flex-col gap-6 sm:max-w-md">
+ <SheetContent className="flex flex-col gap-6 sm:max-w-md" style={{width: 1000, maxWidth: 1000}}>
<SheetHeader className="text-left">
- <SheetTitle>"{role?.name}"에 유저를 할당하세요</SheetTitle>
- <SheetDescription>
- 현재 {role?.name}에는 {role?.user_count}명이 할당되어있습니다. 이 롤은 다음과 같습니다.<br/> {role?.description}
+ <SheetTitle className="flex items-center gap-2">
+ <Users className="h-5 w-5" />
+ Manage "{role?.name}" Role
+ </SheetTitle>
+ <SheetDescription className="space-y-2">
+ <div className="flex items-center gap-2">
+ <span>Currently assigned:</span>
+ <Badge variant="secondary">{role?.user_count || 0} users</Badge>
+ </div>
+ <div className="text-sm">
+ {role?.description}
+ </div>
</SheetDescription>
</SheetHeader>
- <AssginedUserTable promises={assignedTablePromises} onSelectedChange={handleSelectedChange} />
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1">
+ <TabsList className="grid w-full grid-cols-2">
+ <TabsTrigger value="assign" className="flex items-center gap-2">
+ <Plus className="h-4 w-4" />
+ Assign Users
+ </TabsTrigger>
+ <TabsTrigger value="remove" className="flex items-center gap-2">
+ <Minus className="h-4 w-4" />
+ Remove Users
+ </TabsTrigger>
+ </TabsList>
+
+ <TabsContent value="assign" className="flex-1 mt-4">
+ <div className="mb-3 text-sm text-muted-foreground">
+ Select users to assign to this role:
+
+ </div>
+ <AssginedUserTable
+ promises={allUsersPromises}
+ onSelectedChange={handleSelectedChange}
+ mode="assign"
+ currentRoleName={role?.name}
+ />
+ </TabsContent>
+
+ <TabsContent value="remove" className="flex-1 mt-4">
+ <div className="mb-3 text-sm text-muted-foreground">
+ Select users to remove from this role:
+ </div>
+ <AssginedUserTable
+ promises={allUsersPromises}
+ onSelectedChange={handleSelectedChange}
+ mode="remove"
+ currentRoleName={role?.name}
+ />
+ </TabsContent>
+ </Tabs>
<SheetFooter className="gap-2 pt-2 sm:space-x-0">
- <SheetClose asChild>
+ <SheetClose asChild>
<Button type="button" variant="outline">
Cancel
</Button>
</SheetClose>
- {/* <Button disabled={isUpdatePending} onClick={onSubmitAssignUsers}> */}
- <Button disabled={isUpdatePending} onClick={handleAssign}>
- {isUpdatePending && (
- <Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
- )}
- Assign
- </Button>
+ {activeTab === "assign" ? (
+ <Button
+ disabled={isUpdatePending || selectedUserIds.length === 0}
+ onClick={handleAssign}
+ >
+ {isUpdatePending && (
+ <Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
+ )}
+ <Plus className="mr-2 h-4 w-4" />
+ Assign ({selectedUserIds.length})
+ </Button>
+ ) : (
+ <Button
+ disabled={isUpdatePending || selectedUserIds.length === 0}
+ onClick={handleRemove}
+ variant="destructive"
+ >
+ {isUpdatePending && (
+ <Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
+ )}
+ <Minus className="mr-2 h-4 w-4" />
+ Remove ({selectedUserIds.length})
+ </Button>
+ )}
</SheetFooter>
</SheetContent>
</Sheet>
diff --git a/lib/roles/table/roles-table.tsx b/lib/roles/table/roles-table.tsx
index cd7c2a3b..3386d439 100644
--- a/lib/roles/table/roles-table.tsx
+++ b/lib/roles/table/roles-table.tsx
@@ -13,17 +13,15 @@ import { DataTable } from "@/components/data-table/data-table"
import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"
-
import { getRolesWithCount } from "@/lib/roles/services"
import { getColumns } from "./roles-table-columns"
import { RoleTableToolbarActions } from "./role-table-toolbar-actions"
import { UpdateRolesSheet } from "./update-roles-sheet"
-import { AssignRolesSheet } from "./assign-roles-sheet"
+import { ManageRoleSheet } from "./assign-roles-sheet" // 업데이트된 컴포넌트
import { getUsersAll } from "@/lib/users/service"
import { DeleteRolesDialog } from "./delete-roles-dialog"
import { RoleView } from "@/db/schema/users"
-
interface RolesTableProps {
promises: Promise<
[
@@ -31,16 +29,15 @@ interface RolesTableProps {
]
>
promises2: Promise<
- [
- Awaited<ReturnType<typeof getUsersAll>>,
- ]
->
+ [
+ Awaited<ReturnType<typeof getUsersAll>>,
+ ]
+ >
}
-export function RolesTable({ promises ,promises2 }: RolesTableProps) {
+export function RolesTable({ promises, promises2 }: RolesTableProps) {
- const [{ data, pageCount }] =
- React.use(promises)
+ const [{ data, pageCount }] = React.use(promises)
const [rowAction, setRowAction] =
React.useState<DataTableRowAction<RoleView> | null>(null)
@@ -50,7 +47,6 @@ export function RolesTable({ promises ,promises2 }: RolesTableProps) {
[setRowAction]
)
-
/**
* This component can render either a faceted filter or a search filter based on the `options` prop.
*
@@ -68,7 +64,6 @@ export function RolesTable({ promises ,promises2 }: RolesTableProps) {
label: "Role Name",
placeholder: "Filter role name...",
},
-
]
/**
@@ -87,19 +82,16 @@ export function RolesTable({ promises ,promises2 }: RolesTableProps) {
label: "Role Name",
type: "text",
},
-
{
id: "domain",
label: "룰 도메인",
type: "text",
},
-
{
id: "company_name",
label: "회사명",
type: "text",
},
-
{
id: "created_at",
label: "Created at",
@@ -107,7 +99,6 @@ export function RolesTable({ promises ,promises2 }: RolesTableProps) {
},
]
-
const { table } = useDataTable({
data,
columns,
@@ -126,10 +117,7 @@ export function RolesTable({ promises ,promises2 }: RolesTableProps) {
return (
<>
- <DataTable
- table={table}
-
- >
+ <DataTable table={table}>
<DataTableAdvancedToolbar
table={table}
filterFields={advancedFilterFields}
@@ -137,21 +125,21 @@ export function RolesTable({ promises ,promises2 }: RolesTableProps) {
>
<RoleTableToolbarActions table={table} />
</DataTableAdvancedToolbar>
+ </DataTable>
- </DataTable>
-
- <UpdateRolesSheet
- open={rowAction?.type === "update"}
- onOpenChange={() => setRowAction(null)}
- role={rowAction?.row.original ?? null}
- />
+ <UpdateRolesSheet
+ open={rowAction?.type === "update"}
+ onOpenChange={() => setRowAction(null)}
+ role={rowAction?.row.original ?? null}
+ />
- <AssignRolesSheet
- open={rowAction?.type === "user"}
- onOpenChange={() => setRowAction(null)}
- role={rowAction?.row.original ?? null}
- assignedTablePromises={promises2}
- />
+ {/* 업데이트된 ManageRoleSheet - 모드 토글 지원 */}
+ <ManageRoleSheet
+ open={rowAction?.type === "user"}
+ allUsersPromises={promises2}
+ onOpenChange={() => setRowAction(null)}
+ role={rowAction?.row.original ?? null}
+ />
<DeleteRolesDialog
open={rowAction?.type === "delete"}
@@ -160,10 +148,6 @@ export function RolesTable({ promises ,promises2 }: RolesTableProps) {
showTrigger={false}
onSuccess={() => rowAction?.row.toggleSelected(false)}
/>
-
-
-
-
</>
)
-}
+} \ No newline at end of file
diff --git a/lib/roles/table/update-roles-sheet.tsx b/lib/roles/table/update-roles-sheet.tsx
index cbe20352..11eb1fc8 100644
--- a/lib/roles/table/update-roles-sheet.tsx
+++ b/lib/roles/table/update-roles-sheet.tsx
@@ -127,7 +127,7 @@ export function UpdateRolesSheet({ role, ...props }: UpdateRoleSheetProps) {
return (
<Sheet {...props}>
- <SheetContent className="flex flex-col gap-6 sm:max-w-md">
+ <SheetContent className="flex flex-col gap-6 sm:max-w-md" style={{width:1000, maxWidth:1000}}>
<SheetHeader className="text-left">
<SheetTitle>Update user</SheetTitle>
<SheetDescription>
diff --git a/lib/roles/userTable/assignedUsers-table.tsx b/lib/roles/userTable/assignedUsers-table.tsx
index 5ac52f13..565ddda2 100644
--- a/lib/roles/userTable/assignedUsers-table.tsx
+++ b/lib/roles/userTable/assignedUsers-table.tsx
@@ -1,7 +1,7 @@
"use client"
import * as React from "react"
-import { userRoles , type UserView} from "@/db/schema/users"
+import { userRoles, type UserView } from "@/db/schema/users"
import type {
DataTableAdvancedFilterField,
DataTableFilterField,
@@ -19,23 +19,98 @@ import type {
} from "@/lib//users/service"
import { getColumns } from "./assginedUsers-table-columns"
-
+type TableMode = "assign" | "remove"
interface UsersTableProps {
promises: Promise<
[
Awaited<ReturnType<typeof getUsersAll>>
-
]
>
- onSelectedChange:any
+ onSelectedChange: any
+ mode?: TableMode // 새로 추가: assign | remove
+ currentRoleName?: string // 새로 추가: 현재 선택된 롤 ID (필터링용)
+ showAllUsers?: boolean // 디버깅용: 모든 사용자 표시
}
-export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps) {
+export function AssginedUserTable({
+ promises,
+ onSelectedChange,
+ mode = "assign",
+ currentRoleName,
+ showAllUsers = false
+}: UsersTableProps) {
+
+ const [{ data: rawData, pageCount }] = React.use(promises)
+
+ // 모드에 따라 데이터 필터링
+ const filteredData = React.useMemo(() => {
+ console.log('🔍 Filtering Debug Info:', {
+ mode,
+ currentRoleName,
+ rawDataLength: rawData?.length,
+ sampleUser: rawData?.[0],
+ showAllUsers
+ })
+
+ // 디버깅용: 모든 사용자 표시
+ if (showAllUsers) {
+ console.log('🔧 Debug mode: showing all users')
+ return rawData
+ }
- const [{ data, pageCount }] =
- React.use(promises)
+ if (!currentRoleName || !rawData) {
+ console.log('❌ No currentRoleId or rawData, returning rawData')
+ return rawData
+ }
+ if (mode === "assign") {
+ // assign 모드: 현재 롤에 할당되지 않은 사용자들만 표시
+ const filtered = rawData.filter(user => {
+ if (!user.roles || !Array.isArray(user.roles)) {
+ console.log('✅ User has no roles, including in assign:', user.user_name)
+ return true
+ }
+
+ // 다양한 roles 구조 지원
+ const hasRole = user.roles.some(role => {
+ if (typeof role === 'string') return role === currentRoleName.toString()
+ return false
+ })
+
+ if (!hasRole) {
+ console.log('✅ User does not have role, including in assign:', user.user_name)
+ }
+ return !hasRole
+ })
+
+ console.log(`📊 Assign mode: ${filtered.length} users available`)
+ return filtered
+ } else {
+ // remove 모드: 현재 롤에 할당된 사용자들만 표시
+ const filtered = rawData.filter(user => {
+ if (!user.roles || !Array.isArray(user.roles)) {
+ console.log('❌ User has no roles, excluding from remove:', user.user_name)
+ return false
+ }
+
+ // 다양한 roles 구조 지원
+ const hasRole = user.roles.some(role => {
+ if (typeof role === 'string') return role === currentRoleName.toString()
+
+ return false
+ })
+
+ if (hasRole) {
+ console.log('✅ User has role, including in remove:', user.user_name, 'roles:', user.roles)
+ }
+ return hasRole
+ })
+
+ console.log(`📊 Remove mode: ${filtered.length} users with role`)
+ return filtered
+ }
+ }, [rawData, mode, currentRoleName, showAllUsers])
const [rowAction, setRowAction] =
React.useState<DataTableRowAction<UserView> | null>(null)
@@ -45,8 +120,6 @@ export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps
[setRowAction]
)
-
-
/**
* This component can render either a faceted filter or a search filter based on the `options` prop.
*
@@ -64,7 +137,6 @@ export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps
label: "Email",
placeholder: "Filter email...",
},
-
]
/**
@@ -88,8 +160,6 @@ export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps
label: "Email",
type: "text",
},
-
-
{
id: "created_at",
label: "Created at",
@@ -98,7 +168,7 @@ export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps
]
const { table } = useDataTable({
- data,
+ data: filteredData, // 필터링된 데이터 사용
columns,
pageCount,
filterFields,
@@ -122,6 +192,7 @@ export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps
}
return true
}
+
const previousUserIdsRef = React.useRef<number[]>([])
React.useEffect(() => {
@@ -138,11 +209,34 @@ export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps
}
}, [rowSelection, onSelectedChange])
+ // 모드 변경시 선택 초기화
+ React.useEffect(() => {
+ table.toggleAllPageRowsSelected(false)
+ setRowAction(null)
+ }, [mode, table])
+
return (
<>
+ {/* 빈 데이터 상태 메시지 */}
+ {filteredData && filteredData.length === 0 && (
+ <div className="flex flex-col items-center justify-center py-8 text-center border-2 border-dashed border-gray-200 rounded-lg">
+ <div className="text-gray-500 mb-2">
+ {mode === "assign"
+ ? "🎯 모든 사용자가 이미 이 롤에 할당되어 있습니다"
+ : "👥 이 롤에 할당된 사용자가 없습니다"
+ }
+ </div>
+ <div className="text-sm text-gray-400">
+ {mode === "assign"
+ ? "할당 가능한 사용자가 없습니다"
+ : "제거할 사용자가 없습니다"
+ }
+ </div>
+ </div>
+ )}
+
<DataTable
table={table}
-
>
<DataTableAdvancedToolbar
table={table}
@@ -150,10 +244,7 @@ export function AssginedUserTable({ promises ,onSelectedChange}: UsersTableProps
shallow={false}
>
</DataTableAdvancedToolbar>
-
</DataTable>
-
-
</>
)
-}
+} \ No newline at end of file