diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-15 10:07:09 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-15 10:07:09 +0000 |
| commit | 4eb7532f822c821fb6b69bf103bd075fefba769b (patch) | |
| tree | b4bcf6c0bf791d71569f3f35498ed256bf7cfaf3 /lib/roles | |
| parent | 660c7888d885badab7af3e96f9c16bd0172ad0f1 (diff) | |
(대표님) 20250715 협력사 정기평가, spreadJS, roles 서비스에 함수 추가
Diffstat (limited to 'lib/roles')
| -rw-r--r-- | lib/roles/services.ts | 104 | ||||
| -rw-r--r-- | lib/roles/table/add-role-dialog.tsx | 248 | ||||
| -rw-r--r-- | lib/roles/table/assign-roles-sheet.tsx | 168 | ||||
| -rw-r--r-- | lib/roles/table/roles-table.tsx | 60 | ||||
| -rw-r--r-- | lib/roles/table/update-roles-sheet.tsx | 2 | ||||
| -rw-r--r-- | lib/roles/userTable/assignedUsers-table.tsx | 127 |
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 |
