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:
hjjeong
2026-05-12 14:17:39 +09:00
parent 7315603f0f
commit 081feff51f
@@ -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>--&gt;</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>&lt;--</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")}