1
0
forked from baron/baron-sso

개발자 권한을 페이지별로 선택/부여 가능하도록 개선

This commit is contained in:
2026-06-09 16:47:20 +09:00
parent 3ed9e912e6
commit 437a3ad98d
18 changed files with 782 additions and 91 deletions

View File

@@ -36,6 +36,12 @@ import {
import { t } from "../../lib/i18n";
import { resolveProfileRole } from "../../lib/role";
import { fetchMe } from "../auth/authApi";
import {
developerAccessPageOptions,
normalizeDeveloperAccessPages,
normalizeDeveloperAccessPageSelection,
type DeveloperAccessPage,
} from "../developer-access/developerAccessPages";
function formatUserLabel(user: DevAssignableUser) {
const primary = user.name.trim() || user.email.trim();
@@ -62,6 +68,9 @@ export default function DeveloperGrantsPage() {
const [selectedUser, setSelectedUser] = useState<DevAssignableUser | null>(
null,
);
const [selectedAccessPages, setSelectedAccessPages] = useState<
DeveloperAccessPage[]
>(["all"]);
const [grantNotes, setGrantNotes] = useState("");
const [adminNotes, setAdminNotes] = useState<Record<number, string>>({});
@@ -122,6 +131,7 @@ export default function DeveloperGrantsPage() {
);
setSelectedUser(null);
setUserSearch("");
setSelectedAccessPages(["all"]);
setGrantNotes("");
},
onError: (err: AxiosError<{ error?: string }> | Error) => {
@@ -212,12 +222,28 @@ export default function DeveloperGrantsPage() {
tenantId,
reason: grantNotes.trim() || "직접 부여",
adminNotes: grantNotes.trim(),
accessPages: normalizeDeveloperAccessPageSelection(selectedAccessPages),
});
};
const handleSelectUser = (user: DevAssignableUser) => {
setSelectedUser(user);
setUserSearch(formatUserLabel(user));
setSelectedAccessPages(["all"]);
};
const handleAccessPageToggle = (page: DeveloperAccessPage) => {
setSelectedAccessPages((current) => {
if (page === "all") {
return ["all"];
}
const withoutAll = current.filter((item) => item !== "all");
if (withoutAll.includes(page)) {
const next = withoutAll.filter((item) => item !== page);
return next.length > 0 ? next : ["all"];
}
return normalizeDeveloperAccessPageSelection([...withoutAll, page]);
});
};
return (
@@ -430,6 +456,40 @@ export default function DeveloperGrantsPage() {
)}
/>
</div>
<div className="space-y-2">
<Label>
{t("ui.dev.grants.pages", "권한 페이지")}{" "}
<span className="text-destructive">*</span>
</Label>
<div className="grid gap-2 rounded-lg border border-border/60 bg-muted/20 p-3">
{developerAccessPageOptions.map((option) => {
const checked =
option.value === "all"
? selectedAccessPages.includes("all")
: selectedAccessPages.includes(option.value);
return (
<label
key={option.value}
className="flex items-center gap-3 rounded-md px-2 py-1.5 text-sm hover:bg-background/60"
>
<input
type="checkbox"
checked={checked}
onChange={() => handleAccessPageToggle(option.value)}
/>
<span className="font-medium">{option.label}</span>
</label>
);
})}
</div>
<p className="text-xs text-muted-foreground">
{t(
"msg.dev.grants.pages_hint",
"전체를 선택하면 개요, 연동 앱 추가, 감사로그가 모두 포함됩니다.",
)}
</p>
</div>
</CardContent>
</Card>
</div>
@@ -518,6 +578,7 @@ export default function DeveloperGrantsPage() {
<TableHead>{t("ui.dev.grants.user", "사용자")}</TableHead>
<TableHead>{t("ui.dev.grants.tenant", "테넌트")}</TableHead>
<TableHead>{t("ui.dev.grants.reason", "부여 사유")}</TableHead>
<TableHead>{t("ui.dev.grants.pages", "권한 페이지")}</TableHead>
<TableHead>{t("ui.dev.grants.status", "상태")}</TableHead>
<TableHead>{t("ui.dev.grants.date", "부여일")}</TableHead>
<TableHead className="text-right">
@@ -536,7 +597,7 @@ export default function DeveloperGrantsPage() {
<div className="text-xs text-muted-foreground">
{grant.email || grant.userId}
</div>
<div className="font-mono text-xs text-muted-foreground">
<div className="font-mono text-xs text-muted-foreground">
{grant.userId}
</div>
</div>
@@ -561,6 +622,20 @@ export default function DeveloperGrantsPage() {
</div>
)}
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{(grant.accessPages?.length
? normalizeDeveloperAccessPages(grant.accessPages)
: ["all"]
).map((page) => (
<Badge key={page} variant="outline">
{developerAccessPageOptions.find(
(option) => option.value === page,
)?.label ?? page}
</Badge>
))}
</div>
</TableCell>
<TableCell>
<Badge variant="success">
{t("ui.dev.grants.approved", "승인됨")}