1
0
forked from baron/baron-sso

개발자 권한 신청 역할 확인 및 사이드바 순서 변경

This commit is contained in:
2026-05-27 15:51:58 +09:00
parent addded8942
commit f8d0cf411a
3 changed files with 101 additions and 10 deletions

View File

@@ -45,12 +45,6 @@ const navItems: ShellSidebarNavItem[] = [
icon: LayoutDashboard,
end: true,
},
{
labelKey: "ui.dev.nav.developer_request",
labelFallback: "Developer Access Request",
to: "/developer-requests",
icon: ClipboardCheck,
},
{
labelKey: "ui.dev.nav.clients",
labelFallback: "Clients",
@@ -63,6 +57,12 @@ const navItems: ShellSidebarNavItem[] = [
to: "/audit-logs",
icon: NotebookTabs,
},
{
labelKey: "ui.dev.nav.developer_request",
labelFallback: "Developer Access Request",
to: "/developer-requests",
icon: ClipboardCheck,
},
];
type SessionStatusProps = {

View File

@@ -1,7 +1,9 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react";
import * as React from "react";
import { useAuth } from "react-oidc-context";
import { useNavigate } from "react-router-dom";
import { parseAuditDetails } from "../../../../common/core/audit";
import { AuditLogTable } from "../../../../common/core/components/audit";
import { PageHeader } from "../../../../common/core/components/page";
@@ -17,9 +19,12 @@ import {
CardTitle,
} from "../../components/ui/card";
import { Input } from "../../components/ui/input";
import { fetchDeveloperRequestStatus } from "../../lib/devApi";
import type { DevAuditLog } from "../../lib/devApi";
import { fetchDevAuditLogs } from "../../lib/devApi";
import { t } from "../../lib/i18n";
import { resolveProfileRole } from "../../lib/role";
import { fetchMe } from "../auth/authApi";
function toCsv(logs: DevAuditLog[]) {
const header = [
@@ -65,6 +70,13 @@ function downloadCsv(content: string, filename: string) {
}
function AuditLogsPage() {
const navigate = useNavigate();
const auth = useAuth();
const hasAccessToken = Boolean(auth.user?.access_token);
const userProfile = auth.user?.profile as Record<string, unknown> | undefined;
const role = resolveProfileRole(userProfile);
const tenantId = userProfile?.tenant_id as string | undefined;
const [searchClientId, setSearchClientId] = React.useState("");
const [searchAction, setSearchAction] = React.useState("");
const [statusFilter, setStatusFilter] = React.useState("all");
@@ -73,6 +85,29 @@ function AuditLogsPage() {
const deferredSearchClientId = React.useDeferredValue(searchClientId.trim());
const deferredSearchAction = React.useDeferredValue(searchAction.trim());
const { data: me, isLoading: isLoadingMe } = useQuery({
queryKey: ["userMe"],
queryFn: fetchMe,
enabled: hasAccessToken,
});
const profileRole = me?.role?.trim() || role;
const { data: requestStatus, isLoading: isLoadingRequestStatus } = useQuery({
queryKey: ["developer-request", tenantId],
queryFn: () => fetchDeveloperRequestStatus(tenantId),
enabled: hasAccessToken && (profileRole === "user" || profileRole === "tenant_member"),
});
const hasDeveloperAccess =
profileRole === "super_admin" ||
profileRole === "tenant_admin" ||
profileRole === "rp_admin" ||
requestStatus?.status === "approved";
const isDeveloperRequestPending = requestStatus?.status === "pending";
const canRequestDeveloperAccess =
(profileRole === "user" || profileRole === "tenant_member") &&
!isLoadingRequestStatus &&
!hasDeveloperAccess &&
!isDeveloperRequestPending;
const query = useInfiniteQuery({
queryKey: [
"dev-audit-logs",
@@ -88,6 +123,7 @@ function AuditLogsPage() {
}),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.next_cursor || undefined,
enabled: hasDeveloperAccess,
});
const logs =
@@ -101,6 +137,60 @@ function AuditLogsPage() {
downloadCsv(csv, `dev-audit-logs-${stamp}.csv`);
};
if (
(profileRole === "user" || profileRole === "tenant_member") &&
(isLoadingMe || isLoadingRequestStatus)
) {
return (
<div className="p-8 text-center">
{t("ui.common.loading", "Loading...")}
</div>
);
}
if (!hasDeveloperAccess) {
return (
<div className="rounded-xl border border-border/60 bg-card p-8 text-center">
<div className="space-y-3">
<h2 className="text-2xl font-semibold tracking-tight">
{t("ui.common.audit.title", "Audit Logs")}
</h2>
<p className="font-medium text-foreground">
{isDeveloperRequestPending
? t(
"msg.dev.dashboard.access_pending",
"개발자 권한 신청을 검토 중입니다.",
)
: t(
"msg.dev.dashboard.access_denied",
"대시보드는 개발자 권한이 있어야 볼 수 있습니다.",
)}
</p>
<p className="text-sm text-muted-foreground">
{isDeveloperRequestPending
? t(
"msg.dev.dashboard.access_pending_detail",
"super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다.",
)
: t(
"msg.dev.dashboard.access_denied_detail",
"개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요.",
)}
</p>
{(isDeveloperRequestPending || canRequestDeveloperAccess) && (
<button
type="button"
className="font-bold text-primary hover:underline"
onClick={() => navigate("/developer-requests")}
>
{t("ui.dev.nav.developer_request", "개발자 권한 신청")}
</button>
)}
</div>
</div>
);
}
if (query.error) {
const axiosError = query.error as AxiosError<{ error?: string }>;
if (axiosError.response?.status === 403) {

View File

@@ -51,9 +51,9 @@ import { fetchMe } from "../auth/authApi";
export default function DeveloperRequestPage() {
const auth = useAuth();
const queryClient = useQueryClient();
const hasAccessToken = Boolean(auth.user?.access_token);
const userProfile = auth.user?.profile as Record<string, unknown> | undefined;
const role = resolveProfileRole(userProfile);
const isSuperAdmin = role === "super_admin";
const tenantId = userProfile?.tenant_id as string | undefined;
const companyCode = userProfile?.companyCode as string | undefined;
@@ -73,7 +73,7 @@ export default function DeveloperRequestPage() {
const { data: me } = useQuery({
queryKey: ["userMe"],
queryFn: fetchMe,
enabled: !!auth.user?.access_token,
enabled: hasAccessToken,
});
const currentTenant = tenants?.find(
@@ -87,7 +87,8 @@ export default function DeveloperRequestPage() {
(userProfile?.phone as string | undefined) ||
(userProfile?.phone_number as string | undefined) ||
"";
const profileRole = me?.role || role;
const profileRole = me?.role?.trim() || role;
const isSuperAdmin = profileRole === "super_admin";
const profileRoleLabel = t(`ui.admin.role.${profileRole}`, profileRole);
const approveMutation = useMutation({