[st] - fix: 메뉴 권한체크 로직 수정
This commit is contained in:
@@ -82,13 +82,14 @@ export default function WithBaseLayout({
|
||||
|
||||
const activeId = getTabStoreState().activeTabId;
|
||||
if (activeId) {
|
||||
setActiveTab(activeId);
|
||||
const activeTab = appTabs.find((t) => t.id === activeId);
|
||||
const gridActiveId = activeTab?.menuItem.menuEngNm;
|
||||
if (gridActiveId) {
|
||||
getGridTabStoreState().setActiveTab(gridActiveId);
|
||||
}
|
||||
}
|
||||
}, [menuList, tabs]);
|
||||
}, [menuList, tabs, setActiveTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pathname) return;
|
||||
@@ -103,7 +104,7 @@ export default function WithBaseLayout({
|
||||
setActiveTabForGrid(gridActiveId);
|
||||
}
|
||||
}
|
||||
}, [pathname, setActiveTab, setActiveTabForGrid]);
|
||||
}, [pathname, setActiveTab, setActiveTabForGrid, tabs.length, activeTabId]);
|
||||
|
||||
// 활성 탭의 메뉴를 찾아서 urlPath를 가져옴. 탭이 전부 닫히면 ''를 넘겨 LNB에서 메뉴 강조 제거
|
||||
const currentPath = useMemo(() => {
|
||||
|
||||
@@ -466,7 +466,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
gridRef: MutableRefObject<RealGridReact | null>,
|
||||
) => {
|
||||
if (!loading && rows.length === 0) {
|
||||
return <p className="p-6 text-center text-[1.3rem] text-muted-foreground">{emptyMessage}</p>;
|
||||
return <p className="p-6 text-center text-[1.3rem] text-muted-foreground h-[65vh] flex justify-center items-center">{emptyMessage}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -380,7 +380,7 @@ export function VerificationResultDialog({
|
||||
</DialogHeader>
|
||||
<div className="max-h-[85vh] space-y-5 overflow-auto">
|
||||
{errorMessage && (
|
||||
<p className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[1.2rem] text-destructive">
|
||||
<p className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[1.2rem] text-destructive h-[65vh] flex justify-center items-center">
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -468,7 +468,7 @@ export function VerificationResultHistoryDialog({
|
||||
const gridRef = gridRefByTab[tab];
|
||||
|
||||
if (!loading && rows.length === 0) {
|
||||
return <p className="p-6 text-center text-[1.3rem] text-muted-foreground">{emptyMessage}</p>;
|
||||
return <p className="p-6 text-center text-[1.3rem] text-muted-foreground h-[65vh] flex justify-center items-center">{emptyMessage}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { RealGridReact } from "realgrid-react";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
|
||||
import { useAlertStore, useStatisticsInputStatusConfirmGroupGate } from "@/shared/lib";
|
||||
import { useActiveTabRegAuthorityAllowed, useAlertStore } from "@/shared/lib";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -62,7 +62,7 @@ const StatsInputStatusInp = () => {
|
||||
ExceptionProcessingHistoryQueryContext | null
|
||||
>(null);
|
||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||
const confirmGroupToolbarAllowed = useStatisticsInputStatusConfirmGroupGate();
|
||||
const regAuthorityToolbarAllowed = useActiveTabRegAuthorityAllowed();
|
||||
|
||||
const swrKey = searchParams
|
||||
? (["stats-input-status", searchParams] as const)
|
||||
@@ -214,6 +214,10 @@ const StatsInputStatusInp = () => {
|
||||
|
||||
const openExceptionHistoryForRow = useCallback(
|
||||
(row: Record<string, string | number>) => {
|
||||
if (!regAuthorityToolbarAllowed) {
|
||||
alert("권한이 없습니다.");
|
||||
return;
|
||||
}
|
||||
const ctx = lastSearchedValues;
|
||||
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd) {
|
||||
alert("먼저 조회를 진행해주세요.");
|
||||
@@ -233,7 +237,7 @@ const StatsInputStatusInp = () => {
|
||||
});
|
||||
setExceptionHistoryOpen(true);
|
||||
},
|
||||
[alert, lastSearchedValues],
|
||||
[alert, lastSearchedValues, regAuthorityToolbarAllowed],
|
||||
);
|
||||
|
||||
const handleExceptionSubmitHistoryCellClick = useCallback(
|
||||
@@ -266,7 +270,7 @@ const StatsInputStatusInp = () => {
|
||||
gugunOkOrNotDoList &&
|
||||
inputPeriodCurrentRef.current &&
|
||||
!allRowsLaeSbmsnApproved &&
|
||||
confirmGroupToolbarAllowed;
|
||||
regAuthorityToolbarAllowed;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -6,7 +6,8 @@ export { useGlobalProgressStore } from "./use-global-progress-store";
|
||||
export { useMenuStore } from "./use-menu-store";
|
||||
export { useUserStore } from "./use-user-store";
|
||||
export {
|
||||
useStatisticsInputStatusConfirmGroupGate,
|
||||
isRegAuthorityAllowed,
|
||||
useActiveTabRegAuthorityAllowed,
|
||||
useStatisticsInputToolbarGroupGate,
|
||||
} from "./use-statistics-input-toolbar-group-gate";
|
||||
export { usePrompt } from "./use-prompt";
|
||||
|
||||
@@ -1,68 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
import {
|
||||
checkStatisticsInputStatusConfirmGroupMembership,
|
||||
checkStatisticsInputToolbarGroupMembership,
|
||||
} from "@/entities/group-user/lib/check-statistics-input-toolbar-group-membership";
|
||||
export function isRegAuthorityAllowed(
|
||||
pageMenuAuthority: { regAuthrtYn: string } | null | undefined,
|
||||
): boolean {
|
||||
return pageMenuAuthority?.regAuthrtYn === "Y";
|
||||
}
|
||||
|
||||
import { useUserStore } from "./use-user-store";
|
||||
/**
|
||||
* 활성 탭의 `getPageMenuAuthority` 결과에서 `regAuthrtYn === "Y"`일 때만 true.
|
||||
* 권한은 탭 추가·활성화 시 tab-store에서 조회합니다.
|
||||
*/
|
||||
export function useActiveTabRegAuthorityAllowed(): boolean {
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
const pageMenuAuthority = useTabStore((s) => {
|
||||
if (!activeTabId) return undefined;
|
||||
return s.tabs.find((tab) => tab.id === activeTabId)?.pageMenuAuthority;
|
||||
});
|
||||
|
||||
/** 통계 입력 상단 툴바(저장·제출·검증결과 등) — 그룹 멤버십 만족 시에만 true */
|
||||
return isRegAuthorityAllowed(pageMenuAuthority);
|
||||
}
|
||||
|
||||
/** 통계 입력 화면 상단 툴바용 — {@link useActiveTabRegAuthorityAllowed}와 동일 */
|
||||
export function useStatisticsInputToolbarGroupGate(): boolean {
|
||||
const userInfo = useUserStore((s) => s.userInfo);
|
||||
const [allowed, setAllowed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const name = userInfo?.userName?.trim() ?? "";
|
||||
const uid = userInfo?.userUnqId?.trim() ?? "";
|
||||
if (!name || !uid) {
|
||||
setAllowed(false);
|
||||
return;
|
||||
}
|
||||
void (async () => {
|
||||
try {
|
||||
const ok = await checkStatisticsInputToolbarGroupMembership(name, uid);
|
||||
if (!cancelled) setAllowed(ok);
|
||||
} catch {
|
||||
if (!cancelled) setAllowed(false);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [userInfo?.userName, userInfo?.userUnqId]);
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
/** 통계 입력 현황 — `GRP_ST_CONFIRM` / `GRP_ADMIN` 멤버일 때만 true */
|
||||
export function useStatisticsInputStatusConfirmGroupGate(): boolean {
|
||||
const userInfo = useUserStore((s) => s.userInfo);
|
||||
const [allowed, setAllowed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const name = userInfo?.userName?.trim() ?? "";
|
||||
const uid = userInfo?.userUnqId?.trim() ?? "";
|
||||
if (!name || !uid) {
|
||||
setAllowed(false);
|
||||
return;
|
||||
}
|
||||
void (async () => {
|
||||
try {
|
||||
const ok = await checkStatisticsInputStatusConfirmGroupMembership(name, uid);
|
||||
if (!cancelled) setAllowed(ok);
|
||||
} catch {
|
||||
if (!cancelled) setAllowed(false);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [userInfo?.userName, userInfo?.userUnqId]);
|
||||
|
||||
return allowed;
|
||||
return useActiveTabRegAuthorityAllowed();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { create } from "zustand";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { MenuAuthorityApi } from "@/entities/menu-authority";
|
||||
import type { PageMenuAuthority } from "@/entities/menu-authority/model/menu-authority-interface";
|
||||
|
||||
export interface Tab {
|
||||
id: string; // 고유 ID (urlPath)
|
||||
@@ -10,6 +12,69 @@ export interface Tab {
|
||||
urlPath: string; // url 경로
|
||||
parentEngNm: string; // 부모 menuEngNm
|
||||
menuItem: MenuItem; // 메뉴 정보
|
||||
/** undefined: 미조회, null: 조회했으나 없음/실패 */
|
||||
pageMenuAuthority?: PageMenuAuthority | null;
|
||||
}
|
||||
|
||||
async function fetchTabPageMenuAuthority(
|
||||
menuCd: number,
|
||||
apply: (authority: PageMenuAuthority | null) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const response = await MenuAuthorityApi.getPageMenuAuthority(menuCd);
|
||||
apply(response?.[0] ?? null);
|
||||
} catch {
|
||||
apply(null);
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleTabPageMenuAuthorityFetch(
|
||||
get: () => TabStore,
|
||||
set: (
|
||||
partial:
|
||||
| Partial<TabStore>
|
||||
| ((state: TabStore) => Partial<TabStore>),
|
||||
) => void,
|
||||
tabId: string,
|
||||
menuCd: number,
|
||||
): void {
|
||||
void fetchTabPageMenuAuthority(menuCd, (authority) => {
|
||||
const { tabs } = get();
|
||||
if (!tabs.some((tab) => tab.id === tabId)) return;
|
||||
set((state) => ({
|
||||
tabs: state.tabs.map((tab) =>
|
||||
tab.id === tabId ? { ...tab, pageMenuAuthority: authority } : tab,
|
||||
),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
type TabStoreSetState = (
|
||||
partial:
|
||||
| Partial<TabStore>
|
||||
| ((state: TabStore) => Partial<TabStore>),
|
||||
) => void;
|
||||
|
||||
const tabStoreSetState: TabStoreSetState = (partial) => {
|
||||
useTabStoreHook.setState(partial);
|
||||
};
|
||||
|
||||
function ensureTabPageMenuAuthority(
|
||||
get: () => TabStore,
|
||||
set: TabStoreSetState,
|
||||
tab: Tab,
|
||||
): void {
|
||||
if (tab.pageMenuAuthority !== undefined) return;
|
||||
scheduleTabPageMenuAuthorityFetch(get, set, tab.id, tab.menuItem.menuCd);
|
||||
}
|
||||
|
||||
/** persist 복원·새로고침 후 활성 탭 메뉴 권한이 없으면 조회 */
|
||||
export function hydrateActiveTabPageMenuAuthority(): void {
|
||||
const { tabs, activeTabId } = getTabStoreState();
|
||||
if (!activeTabId) return;
|
||||
const tab = tabs.find((t) => t.id === activeTabId);
|
||||
if (!tab) return;
|
||||
ensureTabPageMenuAuthority(getTabStoreState, tabStoreSetState, tab);
|
||||
}
|
||||
|
||||
interface TabStore {
|
||||
@@ -32,6 +97,7 @@ function tabForPersist(tab: Tab): Tab {
|
||||
return {
|
||||
...tab,
|
||||
menuItem: { ...tab.menuItem, children: null },
|
||||
pageMenuAuthority: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,6 +137,7 @@ const useTabStoreHook = create<TabStore>()(
|
||||
|
||||
if (existingTab) {
|
||||
set({ activeTabId: tabId });
|
||||
ensureTabPageMenuAuthority(get, set, existingTab);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -85,6 +152,7 @@ const useTabStoreHook = create<TabStore>()(
|
||||
(m) => m.menuCd === menuItem.parentId,
|
||||
)?.menuEngNm || "",
|
||||
menuItem,
|
||||
pageMenuAuthority: undefined,
|
||||
};
|
||||
|
||||
set({
|
||||
@@ -92,6 +160,13 @@ const useTabStoreHook = create<TabStore>()(
|
||||
activeTabId: tabId,
|
||||
});
|
||||
|
||||
scheduleTabPageMenuAuthorityFetch(
|
||||
get,
|
||||
set,
|
||||
tabId,
|
||||
menuItem.menuCd,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -122,9 +197,15 @@ const useTabStoreHook = create<TabStore>()(
|
||||
tabs: newTabs,
|
||||
activeTabId: newActiveTabId,
|
||||
});
|
||||
if (newActiveTabId) {
|
||||
const nextActive = get().tabs.find((t) => t.id === newActiveTabId);
|
||||
if (nextActive) ensureTabPageMenuAuthority(get, set, nextActive);
|
||||
}
|
||||
},
|
||||
setActiveTab: (tabId: string) => {
|
||||
set({ activeTabId: tabId });
|
||||
const tab = get().tabs.find((t) => t.id === tabId);
|
||||
if (tab) ensureTabPageMenuAuthority(get, set, tab);
|
||||
},
|
||||
setMenuList: (menuList: MenuItem[] | null) => {
|
||||
set({ menuList });
|
||||
@@ -173,6 +254,10 @@ const useTabStoreHook = create<TabStore>()(
|
||||
activeTabId,
|
||||
};
|
||||
},
|
||||
onRehydrateStorage: () => (state, error) => {
|
||||
if (error || !state) return;
|
||||
queueMicrotask(() => hydrateActiveTabPageMenuAuthority());
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user