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

@@ -47,6 +47,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";
export default function DeveloperRequestPage() {
const auth = useAuth();
@@ -153,7 +159,7 @@ export default function DeveloperRequestPage() {
}
const hasActiveRequest = requests?.some(
(r) => r.status === "pending" || r.status === "approved",
(r) => r.status === "pending",
);
const approvedRequestCount =
requests?.filter((request) => request.status === "approved").length ?? 0;
@@ -218,6 +224,9 @@ export default function DeveloperRequestPage() {
<TableHead>
{t("ui.dev.request.table.reason", "신청 사유")}
</TableHead>
<TableHead>
{t("ui.dev.request.table.pages", "권한 페이지")}
</TableHead>
<TableHead>
{t("ui.dev.request.table.status", "상태")}
</TableHead>
@@ -235,7 +244,7 @@ export default function DeveloperRequestPage() {
{!requests || requests.length === 0 ? (
<TableRow>
<TableCell
colSpan={isSuperAdmin ? 6 : 4}
colSpan={isSuperAdmin ? 7 : 5}
className="h-32 text-center text-muted-foreground"
>
{t("msg.dev.request.empty", "신청 내역이 없습니다.")}
@@ -272,6 +281,25 @@ export default function DeveloperRequestPage() {
</div>
)}
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{req.accessPages?.length ? (
normalizeDeveloperAccessPages(req.accessPages).map(
(page) => (
<Badge key={page} variant="outline">
{developerAccessPageOptions.find(
(option) => option.value === page,
)?.label ?? page}
</Badge>
),
)
) : (
<Badge variant="secondary">
{t("ui.common.na", "없음")}
</Badge>
)}
</div>
</TableCell>
<TableCell>
<StatusBadge status={req.status} />
</TableCell>
@@ -449,12 +477,16 @@ function RequestAccessModal({
const [name, setName] = useState(initialName);
const [organization, setOrganization] = useState(initialOrg);
const [reason, setReason] = useState("");
const [accessPages, setAccessPages] = useState<DeveloperAccessPage[]>([
"all",
]);
const organizationDisplay = organization.trim() || t("ui.common.na", "없음");
useEffect(() => {
if (!isOpen) return;
setName(initialName);
setOrganization(initialOrg);
setAccessPages(["all"]);
}, [initialName, initialOrg, isOpen]);
const mutation = useMutation({
@@ -471,6 +503,21 @@ function RequestAccessModal({
organization,
reason,
tenantId,
accessPages: normalizeDeveloperAccessPageSelection(accessPages),
});
};
const handleAccessPageToggle = (page: DeveloperAccessPage) => {
setAccessPages((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]);
});
};
@@ -562,6 +609,39 @@ function RequestAccessModal({
className="focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-input"
/>
</div>
<div className="grid gap-3">
<Label>
{t("ui.dev.request.modal.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"
? accessPages.includes("all")
: accessPages.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.request.modal.pages_hint",
"전체를 선택하면 개요, 연동 앱 추가, 감사로그가 모두 포함됩니다.",
)}
</p>
</div>
<div className="grid gap-2">
<Label htmlFor="reason">
{t("ui.dev.request.modal.reason", "신청 사유")}{" "}