forked from baron/baron-sso
개발자 권한 신청 역할 확인 및 사이드바 순서 변경
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user