From 1a2241c40e10193c5ff7008a7b7b36cc1d855d96 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Tue, 25 Mar 2025 15:55:45 +0900
Subject: initial commit

---
 components/system/permissionDialog.tsx | 301 +++++++++++++++++++++++++++++++++
 1 file changed, 301 insertions(+)
 create mode 100644 components/system/permissionDialog.tsx

(limited to 'components/system/permissionDialog.tsx')

diff --git a/components/system/permissionDialog.tsx b/components/system/permissionDialog.tsx
new file mode 100644
index 00000000..f7247672
--- /dev/null
+++ b/components/system/permissionDialog.tsx
@@ -0,0 +1,301 @@
+"use client"
+
+import * as React from "react"
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Button } from "@/components/ui/button"
+import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"
+import { RoleView } from "@/db/schema/users"
+import {
+  getAllRoleView,
+  getMenuPermissions,
+  upsertPermissions
+} from "@/lib/roles/services"
+import { useToast } from "@/hooks/use-toast"
+import { Loader } from "lucide-react"
+import { permissionLabelMap } from "@/config/permissionsConfig"
+
+interface PermissionDialogProps {
+  open: boolean
+  onOpenChange: (val: boolean) => void
+  itemKey?: string
+  itemTitle?: string
+}
+
+export function PermissionDialog({
+  open,
+  onOpenChange,
+  itemKey,
+  itemTitle,
+}: PermissionDialogProps) {
+  // **(A)**: 체크박스에 의해 새로 추가할 권한(perms)
+  const [permissions, setPermissions] = React.useState<string[]>([])
+
+  // **(B)**: 체크된 Roles(새로 부여할 대상)
+  const [selectedRoles, setSelectedRoles] = React.useState<number[]>([])
+
+  // **(C)**: 전체 Role 목록
+  const [roles, setRoles] = React.useState<RoleView[]>([])
+
+  // **(D)**: Role별 이미 존재하는 권한들 → UI 표시용
+  const [rolePermsMap, setRolePermsMap] = React.useState<Record<number, string[]>>({})
+
+  const { toast } = useToast()
+  const [isPending, startTransition] = React.useTransition()
+
+  // 1) Role 목록 로드
+  React.useEffect(() => {
+    getAllRoleView("evcp").then((res) => {
+      setRoles(res)
+    })
+  }, [])
+
+  // 2) Dialog 열릴 때 → DB에서 “이미 부여된 권한” 로드
+  React.useEffect(() => {
+    if (open && itemKey) {
+      // 기존에 어떤 Role들이 itemKey 퍼미션을 가지고 있는지
+      getMenuPermissions(itemKey).then((rows) => {
+        // rows: { roleId, permKey: "itemKey.xxx" }
+        // rolePermsMap[r.roleId] = ["create","viewAll",...]
+        const rMap: Record<number, string[]> = {}
+        for (const row of rows) {
+          const splitted = row.permKey.split(".")
+          const shortPerm = splitted[1]
+          if (!rMap[row.roleId]) {
+            rMap[row.roleId] = []
+          }
+          rMap[row.roleId].push(shortPerm)
+        }
+        setRolePermsMap(rMap)
+
+        // 권한 체크박스(permissions)와 selectedRoles는
+        // "항상 비어있는 상태"로 시작 (새로 추가할 용도)
+        setPermissions([])
+        setSelectedRoles([])
+      })
+    } else if (!open) {
+      // Dialog가 닫힐 때 리셋
+      setPermissions([])
+      setSelectedRoles([])
+      setRolePermsMap({})
+    }
+  }, [open, itemKey])
+
+  // Checkbox toggle: 권한
+  function togglePermission(perm: string) {
+    setPermissions((prev) =>
+      prev.includes(perm) ? prev.filter((p) => p !== perm) : [...prev, perm]
+    )
+  }
+
+  // Checkbox toggle: Role
+  function toggleRole(roleId: number) {
+    setSelectedRoles((prev) =>
+      prev.includes(roleId) ? prev.filter((p) => p !== roleId) : [...prev, roleId]
+    )
+  }
+
+  async function handleSave() {
+    if (!itemKey) {
+      toast({
+        variant: "destructive",
+        title: "오류",
+        description: "선택한 메뉴가 없어 권한을 생성할 수 없습니다.",
+      })
+      onOpenChange(false)
+      return
+    }
+
+    // permission_key = itemKey.perm
+    const permissionKeys = permissions.map((perm) => `${itemKey}.${perm}`)
+
+    startTransition(async () => {
+      try {
+        await upsertPermissions({
+          roleIds: selectedRoles,
+          permissionKeys,
+          itemTitle,
+        })
+
+        toast({
+          variant: "default",
+          title: "권한 설정 완료",
+          description: "새 권한이 정상적으로 설정되었습니다.",
+        })
+        setPermissions([])
+        setSelectedRoles([])
+        setRolePermsMap({})
+        onOpenChange(false)
+      } catch (err) {
+        toast({
+          variant: "destructive",
+          title: "오류",
+          description: "권한 설정에 실패했습니다. 다시 시도해주세요.",
+        })
+      }
+    })
+  }
+
+  function handleCancel() {
+    setPermissions([])
+    setSelectedRoles([])
+    setRolePermsMap({})
+    onOpenChange(false)
+  }
+
+  const isDisabled = isPending
+
+  return (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-3xl">
+        <DialogHeader>
+          <DialogTitle>
+            권한 설정 - <span className="text-primary">{itemTitle}</span>
+          </DialogTitle>
+        </DialogHeader>
+
+        <div className="flex flex-col gap-6">
+          {/* 1) Role 표시: 이미 가진 권한은 slash 구분 */}
+          <section>
+            <h2 className="font-bold mb-2">Role & 이미 부여된 권한</h2>
+            <div className="max-h-[200px] overflow-y-auto border p-2 rounded space-y-2">
+              {roles.map((r) => {
+                const existPerms = rolePermsMap[r.id] || []
+                const permsText = existPerms
+                  .map((perm) => permissionLabelMap[perm] ?? perm)
+                  // ↑ 매핑에 없는 키일 경우 대비해 ?? perm 로 처리
+                  .join(" / ")
+
+                return (
+                  <div key={r.id} className="flex items-center gap-2">
+                    <Checkbox
+                      checked={selectedRoles.includes(r.id)}
+                      onCheckedChange={() => toggleRole(r.id)}
+                      disabled={isDisabled}
+                    />
+                    <Tooltip>
+                      <TooltipTrigger asChild>
+                        <span className="cursor-help text-sm font-medium">
+                          {r.name}
+                        </span>
+                      </TooltipTrigger>
+                      <TooltipContent>
+                        {r.description ?? "No description"}
+                      </TooltipContent>
+                    </Tooltip>
+                    {/* 이미 가진 권한 텍스트 */}
+                    {permsText && (
+                      <span className="ml-2 text-xs text-muted-foreground">
+                        {permsText}
+                      </span>
+                    )}
+                  </div>
+                )
+              })}
+            </div>
+          </section>
+
+          {/* 2) 새 권한 체크박스 */}
+          <section>
+            <h2 className="font-bold mb-2">새로 부여할 권한</h2>
+            <div className="grid grid-cols-2 gap-6 border p-2 rounded">
+              {/* 왼쪽 */}
+              <div className="space-y-4">
+                {/* 생성 */}
+                <div>
+                  <h3 className="mb-2 font-semibold">생성</h3>
+                  <div className="flex items-center gap-2">
+                    <Checkbox
+                      checked={permissions.includes("create")}
+                      onCheckedChange={() => togglePermission("create")}
+                      disabled={isDisabled}
+                    />
+                    <span className="text-sm">{permissionLabelMap["create"]}</span>
+                  </div>
+                </div>
+
+                {/* 보기 */}
+                <div>
+                  <h3 className="mb-2 font-semibold">보기</h3>
+                  <div className="flex items-center gap-2">
+                    <Checkbox
+                      checked={permissions.includes("viewAll")}
+                      onCheckedChange={() => togglePermission("viewAll")}
+                      disabled={isDisabled}
+                    />
+                    <span className="text-sm">{permissionLabelMap["viewAll"]}</span>
+                  </div>
+                  <div className="flex items-center gap-2">
+                    <Checkbox
+                      checked={permissions.includes("viewOwn")}
+                      onCheckedChange={() => togglePermission("viewOwn")}
+                      disabled={isDisabled}
+                    />
+                    <span className="text-sm">{permissionLabelMap["viewOwn"]}</span>
+                  </div>
+                </div>
+              </div>
+
+              {/* 오른쪽 */}
+              <div className="space-y-4">
+                {/* 편집 */}
+                <div>
+                  <h3 className="mb-2 font-semibold">편집</h3>
+                  <div className="flex items-center gap-2">
+                    <Checkbox
+                      checked={permissions.includes("editAll")}
+                      onCheckedChange={() => togglePermission("editAll")}
+                      disabled={isDisabled}
+                    />
+                    <span className="text-sm">{permissionLabelMap["editAll"]}</span>
+                  </div>
+                  <div className="flex items-center gap-2">
+                    <Checkbox
+                      checked={permissions.includes("editOwn")}
+                      onCheckedChange={() => togglePermission("editOwn")}
+                      disabled={isDisabled}
+                    />
+                    <span className="text-sm">{permissionLabelMap["editOwn"]}</span>
+                  </div>
+                </div>
+
+                {/* 삭제 */}
+                <div>
+                  <h3 className="mb-2 font-semibold">삭제</h3>
+                  <div className="flex items-center gap-2">
+                    <Checkbox
+                      checked={permissions.includes("deleteAll")}
+                      onCheckedChange={() => togglePermission("deleteAll")}
+                      disabled={isDisabled}
+                    />
+                    <span className="text-sm">{permissionLabelMap["deleteAll"]}</span>
+                  </div>
+                  <div className="flex items-center gap-2">
+                    <Checkbox
+                      checked={permissions.includes("deleteOwn")}
+                      onCheckedChange={() => togglePermission("deleteOwn")}
+                      disabled={isDisabled}
+                    />
+                    <span className="text-sm">{permissionLabelMap["deleteOwn"]}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </section>
+        </div>
+
+        <DialogFooter>
+          <Button variant="secondary" onClick={handleCancel} disabled={isDisabled}>
+            취소
+          </Button>
+          <Button variant="default" onClick={handleSave} disabled={isDisabled}>
+            {isPending && (
+              <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" />
+            )}
+            저장
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

