forked from baron/baron-sso
개발자 권한을 페이지별로 선택/부여 가능하도록 개선
This commit is contained in:
@@ -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", "신청 사유")}{" "}
|
||||
|
||||
Reference in New Issue
Block a user