style(rolesList): 다른 메뉴 톤에 맞춰 사이즈/글씨 축소
본문 텍스트 text-sm → text-xs, 헤더 보조 텍스트 text-xs → text-[11px], 카드 헤더 p-3 → p-2.5, 좌측 권한 목록 그리드 260px → 220px. Input/Select h-8 → h-7 (메인) / h-7 → h-6 (서브), 메뉴 트리 row py-2 → py-1.5, 트리 들여쓰기 level*20+12 → level*16+10, chevron h-3.5 → h-3. 4분할 직원 카드 영역 clamp(220, 32vh, 320) → clamp(200, 28vh, 280). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -516,40 +516,40 @@ export default function RolesPage() {
|
||||
return (
|
||||
<React.Fragment key={String(node.objid)}>
|
||||
<tr className="border-t hover:bg-muted/30 transition-colors">
|
||||
<td className="py-2 pr-2" style={{ paddingLeft: `${node.level * 20 + 12}px` }}>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<td className="py-1.5 pr-2" style={{ paddingLeft: `${node.level * 16 + 10}px` }}>
|
||||
<div className="flex items-center gap-1">
|
||||
{hasChildren ? (
|
||||
<button
|
||||
onClick={() => toggleExpand(String(node.objid))}
|
||||
className="hover:bg-muted rounded p-0.5"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<ChevronDown className="h-3 w-3 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<span className="inline-block w-4" />
|
||||
<span className="inline-block w-3" />
|
||||
)}
|
||||
<span className={cn("text-sm", hasChildren && "font-semibold")}>
|
||||
<span className={cn("text-xs", hasChildren && "font-semibold")}>
|
||||
{node.menu_name}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-2 text-center">
|
||||
<td className="py-1.5 text-center">
|
||||
<Checkbox
|
||||
checked={editChecked}
|
||||
onCheckedChange={(c) => handleEditCol(String(node.objid), c === true)}
|
||||
/>
|
||||
</td>
|
||||
<td className="py-2 text-center">
|
||||
<td className="py-1.5 text-center">
|
||||
<Checkbox
|
||||
checked={perm.delete_yn === "Y"}
|
||||
onCheckedChange={(c) => handleDeleteCol(String(node.objid), c === true)}
|
||||
/>
|
||||
</td>
|
||||
<td className="py-2 text-center">
|
||||
<td className="py-1.5 text-center">
|
||||
<Checkbox
|
||||
checked={perm.read_yn === "Y"}
|
||||
onCheckedChange={(c) => handleReadCol(String(node.objid), c === true)}
|
||||
@@ -617,34 +617,34 @@ export default function RolesPage() {
|
||||
)}
|
||||
|
||||
{/* 상단 4분할: 권한목록 | 권한있는직원 | 이동버튼 | 권한없는직원 */}
|
||||
<div className="grid shrink-0 grid-cols-1 gap-4 xl:grid-cols-[260px_1fr_auto_1fr]">
|
||||
<div className="grid shrink-0 grid-cols-1 gap-3 xl:grid-cols-[220px_1fr_auto_1fr]">
|
||||
{/* 권한 목록 */}
|
||||
<div className="bg-card flex flex-col rounded-lg border shadow-sm">
|
||||
<div className="flex items-center justify-between border-b p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="text-primary h-4 w-4" />
|
||||
<h2 className="text-sm font-semibold">권한 목록</h2>
|
||||
<div className="flex items-center justify-between border-b p-2.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Shield className="text-primary h-3.5 w-3.5" />
|
||||
<h2 className="text-xs font-semibold">권한 목록</h2>
|
||||
</div>
|
||||
<Button size="sm" onClick={handleCreateRole} className="h-7 gap-1 px-2 text-xs">
|
||||
<Button size="sm" onClick={handleCreateRole} className="h-6 gap-1 px-2 text-[11px]">
|
||||
<Plus className="h-3 w-3" />
|
||||
생성
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2 border-b p-2">
|
||||
<div className="space-y-1.5 border-b p-2">
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-3.5 w-3.5 -translate-y-1/2" />
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2 h-3 w-3 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder="검색..."
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
className="h-8 pl-8 text-xs"
|
||||
className="h-7 pl-7 text-[11px]"
|
||||
/>
|
||||
</div>
|
||||
{isSuperAdmin && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Filter className="text-muted-foreground h-3.5 w-3.5 flex-shrink-0" />
|
||||
<Filter className="text-muted-foreground h-3 w-3 flex-shrink-0" />
|
||||
<Select value={selectedCompany} onValueChange={setSelectedCompany}>
|
||||
<SelectTrigger className="h-8 flex-1 text-xs">
|
||||
<SelectTrigger className="h-7 flex-1 text-[11px]">
|
||||
<SelectValue placeholder="회사 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -661,7 +661,7 @@ export default function RolesPage() {
|
||||
</div>
|
||||
<div
|
||||
className="flex-1 overflow-y-auto"
|
||||
style={{ maxHeight: "clamp(220px, 32vh, 320px)" }}
|
||||
style={{ maxHeight: "clamp(200px, 28vh, 280px)" }}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex h-32 items-center justify-center">
|
||||
@@ -682,7 +682,7 @@ export default function RolesPage() {
|
||||
key={`${role.company_code ?? "_"}-${role.objid}`}
|
||||
onClick={() => setSelectedRole(role)}
|
||||
className={cn(
|
||||
"group cursor-pointer p-2.5 transition-colors",
|
||||
"group cursor-pointer p-2 transition-colors",
|
||||
isSelected ? "bg-primary/10" : "hover:bg-muted/50",
|
||||
)}
|
||||
>
|
||||
@@ -690,7 +690,7 @@ export default function RolesPage() {
|
||||
<div className="min-w-0 flex-1">
|
||||
<div
|
||||
className={cn(
|
||||
"truncate text-sm font-semibold",
|
||||
"truncate text-xs font-semibold",
|
||||
isSelected && "text-primary",
|
||||
)}
|
||||
>
|
||||
@@ -738,12 +738,12 @@ export default function RolesPage() {
|
||||
|
||||
{/* 권한있는 직원 */}
|
||||
<div className="bg-card flex flex-col rounded-lg border shadow-sm">
|
||||
<div className="border-b p-3">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<Users className="text-primary h-4 w-4" />
|
||||
<h2 className="text-sm font-semibold">권한있는 직원 ({members.length})</h2>
|
||||
<div className="border-b p-2.5">
|
||||
<div className="mb-1.5 flex items-center gap-1.5">
|
||||
<Users className="text-primary h-3.5 w-3.5" />
|
||||
<h2 className="text-xs font-semibold">권한있는 직원 ({members.length})</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Checkbox
|
||||
checked={
|
||||
filteredMembers.length > 0 &&
|
||||
@@ -752,14 +752,14 @@ export default function RolesPage() {
|
||||
onCheckedChange={(c) => handleMembersSelectAll(c === true)}
|
||||
disabled={!selectedRole}
|
||||
/>
|
||||
<span className="text-muted-foreground text-xs">전체선택</span>
|
||||
<span className="text-muted-foreground text-[11px]">전체선택</span>
|
||||
<div className="relative ml-auto flex-1 max-w-[180px]">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2 h-3 w-3 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder="검색"
|
||||
value={memberSearch}
|
||||
onChange={(e) => setMemberSearch(e.target.value)}
|
||||
className="h-7 pl-7 text-xs"
|
||||
className="h-6 pl-6 text-[11px]"
|
||||
disabled={!selectedRole}
|
||||
/>
|
||||
</div>
|
||||
@@ -767,7 +767,7 @@ export default function RolesPage() {
|
||||
</div>
|
||||
<div
|
||||
className="flex-1 overflow-y-auto"
|
||||
style={{ height: "clamp(220px, 32vh, 320px)" }}
|
||||
style={{ height: "clamp(200px, 28vh, 280px)" }}
|
||||
>
|
||||
{!selectedRole ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
@@ -788,7 +788,7 @@ export default function RolesPage() {
|
||||
key={u.user_id}
|
||||
onClick={() => toggleMemberCheck(u.user_id)}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-2 p-2 transition-colors",
|
||||
"flex cursor-pointer items-center gap-1.5 p-1.5 transition-colors",
|
||||
checkedMembers.has(u.user_id) ? "bg-muted" : "hover:bg-muted/50",
|
||||
)}
|
||||
>
|
||||
@@ -798,7 +798,7 @@ export default function RolesPage() {
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm">{u.user_name || u.user_id}</div>
|
||||
<div className="truncate text-xs">{u.user_name || u.user_id}</div>
|
||||
{u.dept_name && (
|
||||
<div className="text-muted-foreground truncate text-[10px]">
|
||||
{u.dept_name}
|
||||
@@ -813,13 +813,13 @@ export default function RolesPage() {
|
||||
</div>
|
||||
|
||||
{/* 이동 버튼: --> 삭제 / <-- 추가 */}
|
||||
<div className="flex flex-row items-center justify-center gap-2 xl:flex-col">
|
||||
<div className="flex flex-row items-center justify-center gap-1.5 xl:flex-col">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleRemoveMembers}
|
||||
disabled={!selectedRole || checkedMembers.size === 0}
|
||||
className="gap-1 text-xs"
|
||||
className="h-7 gap-1 px-2 text-[11px]"
|
||||
>
|
||||
<span>--></span>
|
||||
<span>삭제</span>
|
||||
@@ -829,7 +829,7 @@ export default function RolesPage() {
|
||||
size="sm"
|
||||
onClick={handleAddMembers}
|
||||
disabled={!selectedRole || checkedNonMembers.size === 0}
|
||||
className="gap-1 text-xs"
|
||||
className="h-7 gap-1 px-2 text-[11px]"
|
||||
>
|
||||
<span><--</span>
|
||||
<span>추가</span>
|
||||
@@ -838,12 +838,12 @@ export default function RolesPage() {
|
||||
|
||||
{/* 권한없는 직원 */}
|
||||
<div className="bg-card flex flex-col rounded-lg border shadow-sm">
|
||||
<div className="border-b p-3">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<Users className="text-muted-foreground h-4 w-4" />
|
||||
<h2 className="text-sm font-semibold">권한없는 직원 ({nonMembers.length})</h2>
|
||||
<div className="border-b p-2.5">
|
||||
<div className="mb-1.5 flex items-center gap-1.5">
|
||||
<Users className="text-muted-foreground h-3.5 w-3.5" />
|
||||
<h2 className="text-xs font-semibold">권한없는 직원 ({nonMembers.length})</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Checkbox
|
||||
checked={
|
||||
filteredNonMembers.length > 0 &&
|
||||
@@ -852,14 +852,14 @@ export default function RolesPage() {
|
||||
onCheckedChange={(c) => handleNonMembersSelectAll(c === true)}
|
||||
disabled={!selectedRole}
|
||||
/>
|
||||
<span className="text-muted-foreground text-xs">전체선택</span>
|
||||
<span className="text-muted-foreground text-[11px]">전체선택</span>
|
||||
<div className="relative ml-auto flex-1 max-w-[180px]">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2 h-3 w-3 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder="검색"
|
||||
value={nonMemberSearch}
|
||||
onChange={(e) => setNonMemberSearch(e.target.value)}
|
||||
className="h-7 pl-7 text-xs"
|
||||
className="h-6 pl-6 text-[11px]"
|
||||
disabled={!selectedRole}
|
||||
/>
|
||||
</div>
|
||||
@@ -867,7 +867,7 @@ export default function RolesPage() {
|
||||
</div>
|
||||
<div
|
||||
className="flex-1 overflow-y-auto"
|
||||
style={{ height: "clamp(220px, 32vh, 320px)" }}
|
||||
style={{ height: "clamp(200px, 28vh, 280px)" }}
|
||||
>
|
||||
{!selectedRole ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
@@ -888,7 +888,7 @@ export default function RolesPage() {
|
||||
key={u.user_id}
|
||||
onClick={() => toggleNonMemberCheck(u.user_id)}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-2 p-2 transition-colors",
|
||||
"flex cursor-pointer items-center gap-1.5 p-1.5 transition-colors",
|
||||
checkedNonMembers.has(u.user_id) ? "bg-muted" : "hover:bg-muted/50",
|
||||
)}
|
||||
>
|
||||
@@ -898,7 +898,7 @@ export default function RolesPage() {
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm">{u.user_name || u.user_id}</div>
|
||||
<div className="truncate text-xs">{u.user_name || u.user_id}</div>
|
||||
{u.dept_name && (
|
||||
<div className="text-muted-foreground truncate text-[10px]">
|
||||
{u.dept_name}
|
||||
@@ -918,14 +918,14 @@ export default function RolesPage() {
|
||||
className="bg-card flex min-h-0 flex-1 flex-col rounded-lg border shadow-sm"
|
||||
style={{ maxHeight: "clamp(280px, 40vh, 430px)" }}
|
||||
>
|
||||
<div className="shrink-0 border-b p-3">
|
||||
<h2 className="text-sm font-semibold">
|
||||
<div className="shrink-0 border-b p-2.5">
|
||||
<h2 className="text-xs font-semibold">
|
||||
메뉴 전체 트리구조{" "}
|
||||
{selectedRole && (
|
||||
<span className="text-muted-foreground text-xs">({selectedRole.auth_name})</span>
|
||||
<span className="text-muted-foreground text-[11px]">({selectedRole.auth_name})</span>
|
||||
)}
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-0.5 text-[11px]">
|
||||
<p className="text-muted-foreground mt-0.5 text-[10px]">
|
||||
체크된 것들만 시스템에서 해당 버튼이 노출됩니다 · 체크 즉시 서버 반영
|
||||
</p>
|
||||
</div>
|
||||
@@ -943,14 +943,14 @@ export default function RolesPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="min-h-0 flex-1 overflow-auto">
|
||||
<table className="w-full text-sm">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-muted sticky top-0 z-10">
|
||||
<tr>
|
||||
<th className="py-2.5 pl-3 text-left text-xs font-semibold w-[40%]">
|
||||
<th className="py-1.5 pl-2.5 text-left text-[11px] font-semibold w-[40%]">
|
||||
메뉴 전체 트리구조
|
||||
</th>
|
||||
<th className="py-2.5 text-center text-xs font-semibold w-[20%]">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<th className="py-1.5 text-center text-[11px] font-semibold w-[20%]">
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span>등록/수정</span>
|
||||
<Checkbox
|
||||
checked={isColumnAllChecked("edit")}
|
||||
@@ -958,8 +958,8 @@ export default function RolesPage() {
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th className="py-2.5 text-center text-xs font-semibold w-[20%]">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<th className="py-1.5 text-center text-[11px] font-semibold w-[20%]">
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span>삭제</span>
|
||||
<Checkbox
|
||||
checked={isColumnAllChecked("delete")}
|
||||
@@ -967,8 +967,8 @@ export default function RolesPage() {
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th className="py-2.5 text-center text-xs font-semibold w-[20%]">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<th className="py-1.5 text-center text-[11px] font-semibold w-[20%]">
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<span>조회</span>
|
||||
<Checkbox
|
||||
checked={isColumnAllChecked("read")}
|
||||
|
||||
Reference in New Issue
Block a user