forked from baron/baron-sso
개발자 권한 신청 역할 확인 및 사이드바 순서 변경
This commit is contained in:
@@ -45,12 +45,6 @@ const navItems: ShellSidebarNavItem[] = [
|
|||||||
icon: LayoutDashboard,
|
icon: LayoutDashboard,
|
||||||
end: true,
|
end: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
labelKey: "ui.dev.nav.developer_request",
|
|
||||||
labelFallback: "Developer Access Request",
|
|
||||||
to: "/developer-requests",
|
|
||||||
icon: ClipboardCheck,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
labelKey: "ui.dev.nav.clients",
|
labelKey: "ui.dev.nav.clients",
|
||||||
labelFallback: "Clients",
|
labelFallback: "Clients",
|
||||||
@@ -63,6 +57,12 @@ const navItems: ShellSidebarNavItem[] = [
|
|||||||
to: "/audit-logs",
|
to: "/audit-logs",
|
||||||
icon: NotebookTabs,
|
icon: NotebookTabs,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
labelKey: "ui.dev.nav.developer_request",
|
||||||
|
labelFallback: "Developer Access Request",
|
||||||
|
to: "/developer-requests",
|
||||||
|
icon: ClipboardCheck,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
type SessionStatusProps = {
|
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 type { AxiosError } from "axios";
|
||||||
import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react";
|
import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react";
|
||||||
import * as React from "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 { parseAuditDetails } from "../../../../common/core/audit";
|
||||||
import { AuditLogTable } from "../../../../common/core/components/audit";
|
import { AuditLogTable } from "../../../../common/core/components/audit";
|
||||||
import { PageHeader } from "../../../../common/core/components/page";
|
import { PageHeader } from "../../../../common/core/components/page";
|
||||||
@@ -17,9 +19,12 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../components/ui/card";
|
} from "../../components/ui/card";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
|
import { fetchDeveloperRequestStatus } from "../../lib/devApi";
|
||||||
import type { DevAuditLog } from "../../lib/devApi";
|
import type { DevAuditLog } from "../../lib/devApi";
|
||||||
import { fetchDevAuditLogs } from "../../lib/devApi";
|
import { fetchDevAuditLogs } from "../../lib/devApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
|
import { resolveProfileRole } from "../../lib/role";
|
||||||
|
import { fetchMe } from "../auth/authApi";
|
||||||
|
|
||||||
function toCsv(logs: DevAuditLog[]) {
|
function toCsv(logs: DevAuditLog[]) {
|
||||||
const header = [
|
const header = [
|
||||||
@@ -65,6 +70,13 @@ function downloadCsv(content: string, filename: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function AuditLogsPage() {
|
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 [searchClientId, setSearchClientId] = React.useState("");
|
||||||
const [searchAction, setSearchAction] = React.useState("");
|
const [searchAction, setSearchAction] = React.useState("");
|
||||||
const [statusFilter, setStatusFilter] = React.useState("all");
|
const [statusFilter, setStatusFilter] = React.useState("all");
|
||||||
@@ -73,6 +85,29 @@ function AuditLogsPage() {
|
|||||||
const deferredSearchClientId = React.useDeferredValue(searchClientId.trim());
|
const deferredSearchClientId = React.useDeferredValue(searchClientId.trim());
|
||||||
const deferredSearchAction = React.useDeferredValue(searchAction.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({
|
const query = useInfiniteQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
"dev-audit-logs",
|
"dev-audit-logs",
|
||||||
@@ -88,6 +123,7 @@ function AuditLogsPage() {
|
|||||||
}),
|
}),
|
||||||
initialPageParam: undefined as string | undefined,
|
initialPageParam: undefined as string | undefined,
|
||||||
getNextPageParam: (lastPage) => lastPage.next_cursor || undefined,
|
getNextPageParam: (lastPage) => lastPage.next_cursor || undefined,
|
||||||
|
enabled: hasDeveloperAccess,
|
||||||
});
|
});
|
||||||
|
|
||||||
const logs =
|
const logs =
|
||||||
@@ -101,6 +137,60 @@ function AuditLogsPage() {
|
|||||||
downloadCsv(csv, `dev-audit-logs-${stamp}.csv`);
|
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) {
|
if (query.error) {
|
||||||
const axiosError = query.error as AxiosError<{ error?: string }>;
|
const axiosError = query.error as AxiosError<{ error?: string }>;
|
||||||
if (axiosError.response?.status === 403) {
|
if (axiosError.response?.status === 403) {
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ import { fetchMe } from "../auth/authApi";
|
|||||||
export default function DeveloperRequestPage() {
|
export default function DeveloperRequestPage() {
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const hasAccessToken = Boolean(auth.user?.access_token);
|
||||||
const userProfile = auth.user?.profile as Record<string, unknown> | undefined;
|
const userProfile = auth.user?.profile as Record<string, unknown> | undefined;
|
||||||
const role = resolveProfileRole(userProfile);
|
const role = resolveProfileRole(userProfile);
|
||||||
const isSuperAdmin = role === "super_admin";
|
|
||||||
const tenantId = userProfile?.tenant_id as string | undefined;
|
const tenantId = userProfile?.tenant_id as string | undefined;
|
||||||
const companyCode = userProfile?.companyCode as string | undefined;
|
const companyCode = userProfile?.companyCode as string | undefined;
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ export default function DeveloperRequestPage() {
|
|||||||
const { data: me } = useQuery({
|
const { data: me } = useQuery({
|
||||||
queryKey: ["userMe"],
|
queryKey: ["userMe"],
|
||||||
queryFn: fetchMe,
|
queryFn: fetchMe,
|
||||||
enabled: !!auth.user?.access_token,
|
enabled: hasAccessToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentTenant = tenants?.find(
|
const currentTenant = tenants?.find(
|
||||||
@@ -87,7 +87,8 @@ export default function DeveloperRequestPage() {
|
|||||||
(userProfile?.phone as string | undefined) ||
|
(userProfile?.phone as string | undefined) ||
|
||||||
(userProfile?.phone_number 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 profileRoleLabel = t(`ui.admin.role.${profileRole}`, profileRole);
|
||||||
|
|
||||||
const approveMutation = useMutation({
|
const approveMutation = useMutation({
|
||||||
|
|||||||
Reference in New Issue
Block a user