Merge branch 'iipbs/sn/dev' into iipbs/sn/ii
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
MultiBackend,
|
||||
Tree,
|
||||
getBackendOptions
|
||||
getBackendOptions,
|
||||
DndProvider
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { DndProvider } from "react-dnd";
|
||||
|
||||
import { useGisContext } from './Gis';
|
||||
import GisApp from './lib/gis.core';
|
||||
|
||||
@@ -42,10 +42,15 @@ GisApp.GOOGLE_SATELLITE_URL = `https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z
|
||||
GisApp.GOOGLE_HYBRID_URL = `https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`;
|
||||
GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${GisApp.GOOGLE_API_KEY}`;
|
||||
|
||||
// TODO: NGII 정보
|
||||
// 인터넷망: NGII 정보
|
||||
GisApp.NGII_API_KEY = '04trYP9_xwLAfALjwZ-B8g'; // NGII에서 발급받은 API 키
|
||||
GisApp.NGII_WMTS_URL = `https://map.ngii.go.kr/openapi/Gettile.do?apikey=${GisApp.NGII_API_KEY}`;
|
||||
|
||||
//행정망: NGII 정보
|
||||
//GisApp.NGII_API_KEY = 'BDE2BEF39DAFEC293AB6730150F222AC'; // NGII에서 발급받은 API 키
|
||||
//GisApp.NGII_WMTS_URL = `http://10.98.25.94:18080/openapi/Gettile.do?apikey=${GisApp.NGII_API_KEY}`;
|
||||
|
||||
|
||||
GisApp.EMAP_URL = '/emap/';
|
||||
GisApp.EMAP_SATELLITE_URL = '/emap/satellite/';
|
||||
GisApp.VWORLD_URL = 'https://xdworld.vworld.kr/2d/Base/service/{z}/{x}/{y}.png';
|
||||
|
||||
@@ -71,7 +71,7 @@ GisOlLayer.prototype = {
|
||||
/**
|
||||
* [사용]WFS 벡터 소스 반환
|
||||
*/
|
||||
getVectorSourceGeoserver: function (options) {
|
||||
getVectorSourceBbox: function (options) {
|
||||
let url = GisApp.GEOSERVER_URL;
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
let lyrTyNm = options.lyrTyNm;
|
||||
@@ -110,8 +110,11 @@ GisOlLayer.prototype = {
|
||||
getVectorTileSource: function (options) {
|
||||
let url = GisApp.GEOSERVER_URL;
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
let lyrTyNm = options.lyrTyNm;
|
||||
let typename = this.storageNm + lyrTyNm;
|
||||
|
||||
let params = {};
|
||||
params['TYPENAME'] = this.storageNm + options.typeName;
|
||||
params['TYPENAME'] = typename;
|
||||
params['SERVICE'] = 'WFS';
|
||||
params['VERSION'] = '1.1.0';
|
||||
params['REQUEST'] = 'GetFeature';
|
||||
@@ -119,19 +122,14 @@ GisOlLayer.prototype = {
|
||||
params['OUTPUTFORMAT'] = 'application/json';
|
||||
let strParams = new URLSearchParams(params).toString();
|
||||
|
||||
let source = new VectorTile({
|
||||
let source = new VectorSource({
|
||||
format: new GeoJSON(),
|
||||
|
||||
tileUrlFunction: function (tileCoord, pixelRatio, projection) {
|
||||
let filterStr = '&CQL_FILTER=bbox(GEOM,' + source.getTileGrid().getTileCoordExtent(tileCoord).join(',') + ",'" + projection.getCode() + "')";
|
||||
if (options.flterSql) {
|
||||
filterStr += ' AND ' + options.flterSql;
|
||||
}
|
||||
let paramWithUrl = url + '?' + strParams + filterStr;
|
||||
return paramWithUrl;
|
||||
url: function (extent) {
|
||||
return url + '?' + strParams;
|
||||
},
|
||||
tileGrid: createXYZ,
|
||||
strategy: all,
|
||||
});
|
||||
return source;
|
||||
|
||||
return source;
|
||||
},
|
||||
@@ -142,8 +140,9 @@ GisOlLayer.prototype = {
|
||||
option = option || {};
|
||||
//레이어 생성
|
||||
let returnLayer = this.makeLayer(option);
|
||||
|
||||
//레이어 소스 생성
|
||||
let source = this.getVectorSourceGeoserver(option);
|
||||
let source = this.getVectorSourceBbox(option);
|
||||
returnLayer.setSource(source);
|
||||
//스타일 추가
|
||||
returnLayer.setStyle(this.gisStyle.getLayerStyleFunction(returnLayer, option));
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.474.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getApiClient } from "./client";
|
||||
import { ENDPOINT } from "../config";
|
||||
import { getConfig } from "@common/config";
|
||||
|
||||
export interface MenuItem {
|
||||
menuCd: number;
|
||||
@@ -26,6 +27,8 @@ export interface TreeMenu extends MenuItem {
|
||||
}
|
||||
|
||||
const client = getApiClient();
|
||||
const { PROXY_HOST } = getConfig();
|
||||
const BASE_URL = `${PROXY_HOST}/server`;
|
||||
|
||||
export const getMenuList = async (siteId: string) => {
|
||||
const response = await client.get<MenuItem[]>(
|
||||
@@ -40,3 +43,18 @@ export const getPreviewTreeMenuList = async (siteId: string) => {
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAnonymouseMenuList = async (
|
||||
siteId: string,
|
||||
): Promise<MenuItem[]> => {
|
||||
const response = await fetch(
|
||||
`${BASE_URL}/${ENDPOINT.CMS_SERVICE.MENU_ANONYMOUS}/user/${siteId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getApiClient } from "./client";
|
||||
import { getConfig } from "@common/config";
|
||||
import { ENDPOINT } from "../config";
|
||||
|
||||
export interface SiteInfo {
|
||||
@@ -23,11 +23,18 @@ export interface SiteInfo {
|
||||
mdfrNm: string;
|
||||
}
|
||||
|
||||
const client = getApiClient();
|
||||
const { PROXY_HOST } = getConfig();
|
||||
const BASE_URL = `${PROXY_HOST}/server`;
|
||||
|
||||
export const getSiteInfo = async (siteId: string) => {
|
||||
const response = await client.get<SiteInfo>(
|
||||
`${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`,
|
||||
const response = await fetch(
|
||||
`${BASE_URL}/${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ export const ENDPOINT = {
|
||||
CMS_SERVICE: {
|
||||
MENUS: "/cms-service/api/v1/menus",
|
||||
MENU_ROLES: "/cms-service/api/v1/menu-roles",
|
||||
MENU_ANONYMOUS: "/cms-service/api/v1/menu/anonymous",
|
||||
SITES: "/cms-service/api/v1/sites",
|
||||
POPUPS: "/cms-service/api/v1/popups",
|
||||
IPS: "/cms-service/api/v1/site-ips",
|
||||
|
||||
@@ -3,6 +3,7 @@ import "./header.css";
|
||||
import { useEffect, useCallback, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { images } from "@common/assets";
|
||||
import { getConfig } from "@common/config";
|
||||
import { ENDPOINT } from "../../config";
|
||||
import { setCookie, getCookie } from "../../lib";
|
||||
import {
|
||||
@@ -22,7 +23,10 @@ import { Button } from "../button";
|
||||
import { SidebarTrigger } from "../sidebar";
|
||||
import { Skeleton } from "../skeleton";
|
||||
|
||||
const siteId = getCookie("site-id-key") ?? "";
|
||||
const appConfig = getConfig();
|
||||
const siteId = appConfig.SITE_ID ?? "";
|
||||
const userId = getCookie(appConfig.AUTH_USER_ID ?? "");
|
||||
const isLoggedIn = !!userId;
|
||||
|
||||
export function Header({ viewOnly }: { viewOnly?: boolean }) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -30,7 +34,7 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
queryFn: () => getUserInfo(),
|
||||
enabled: !viewOnly,
|
||||
enabled: isLoggedIn && !viewOnly,
|
||||
});
|
||||
|
||||
const { data: siteInfo } = useQuery({
|
||||
@@ -50,6 +54,7 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
|
||||
const { data: userPopupList } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.POPUPS],
|
||||
queryFn: () => getUserPopupList(),
|
||||
enabled: isLoggedIn && !viewOnly,
|
||||
});
|
||||
|
||||
const logout = async () => {
|
||||
@@ -130,30 +135,36 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className="group m-0 flex h-[3rem] items-center !bg-background px-4 py-0 text-label"
|
||||
data-trigger="dropdown"
|
||||
>
|
||||
<i className="diveicon di-account-circle static translate-y-0.5 pr-2 text-3xl" />
|
||||
{userInfo?.userName}
|
||||
<i className="diveicon di-chevron-down static p-0 transition-transform group-data-[state=open]:rotate-180" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>프로필 설정</DropdownMenuItem>
|
||||
<div className="flex flex-col items-start gap-0 px-4 py-3">
|
||||
<p className="text-base text-label">최종접속일</p>
|
||||
<p>{userInfo?.lastLoginDate}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-0 px-4 py-3">
|
||||
<p className="text-base text-label">접속 IP</p>
|
||||
<p></p>
|
||||
</div>
|
||||
<DropdownMenuItem onClick={logout}>로그아웃</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{mounted ? (
|
||||
isLoggedIn && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className="group m-0 flex h-[3rem] items-center !bg-background px-4 py-0 text-label"
|
||||
data-trigger="dropdown"
|
||||
>
|
||||
<i className="diveicon di-account-circle static translate-y-0.5 pr-2 text-3xl" />
|
||||
{userInfo?.userName}
|
||||
<i className="diveicon di-chevron-down static p-0 transition-transform group-data-[state=open]:rotate-180" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>프로필 설정</DropdownMenuItem>
|
||||
<div className="flex flex-col items-start gap-0 px-4 py-3">
|
||||
<p className="text-base text-label">최종접속일</p>
|
||||
<p>{userInfo?.lastLoginDate}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-0 px-4 py-3">
|
||||
<p className="text-base text-label">접속 IP</p>
|
||||
<p></p>
|
||||
</div>
|
||||
<DropdownMenuItem onClick={logout}>로그아웃</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
) : (
|
||||
<Skeleton className="mx-4 h-[3rem] w-[15rem]" />
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export * from "./carousel";
|
||||
export * from "./checkbox";
|
||||
export * from "./data-table";
|
||||
export * from "./date-picker";
|
||||
export * from "./input-date-picker";
|
||||
export * from "./date-range-picker";
|
||||
export * from "./dialog";
|
||||
export * from "./dropdown-menu";
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
"use client";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
|
||||
import { Button } from "../button";
|
||||
import { Calendar } from "../calendar";
|
||||
import { Input } from "../input";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
||||
|
||||
import "./input-date-picker.css";
|
||||
|
||||
export interface InputDatePickerProps {
|
||||
value?: Date;
|
||||
defaultValue?: Date;
|
||||
onChange?: (date: Date | undefined) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function InputDatePicker({
|
||||
value: controlledValue,
|
||||
defaultValue,
|
||||
onChange,
|
||||
placeholder,
|
||||
disabled,
|
||||
className,
|
||||
}: InputDatePickerProps) {
|
||||
const isControlled = controlledValue !== undefined;
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [internalDate, setInternalDate] = useState<Date | undefined>(
|
||||
defaultValue,
|
||||
);
|
||||
const [month, setMonth] = useState<Date | undefined>(
|
||||
isControlled ? controlledValue : defaultValue,
|
||||
);
|
||||
|
||||
// 실제로 사용할 date 값 (제어/비제어 모두 지원)
|
||||
const date = isControlled ? controlledValue : internalDate;
|
||||
|
||||
// 표시할 문자열 값
|
||||
const [displayValue, setDisplayValue] = useState(date);
|
||||
|
||||
// 제어 컴포넌트일 경우 외부 값 변경 감지
|
||||
useEffect(() => {
|
||||
if (isControlled) {
|
||||
setDisplayValue(controlledValue);
|
||||
setMonth(controlledValue);
|
||||
}
|
||||
}, [isControlled, controlledValue]);
|
||||
|
||||
// 내부 상태 변경 핸들러
|
||||
const handleDateChange = (newDate: Date | undefined) => {
|
||||
if (!isControlled) {
|
||||
setInternalDate(newDate);
|
||||
}
|
||||
|
||||
if (onChange) {
|
||||
onChange(newDate);
|
||||
}
|
||||
|
||||
setDisplayValue(newDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="relative flex gap-2">
|
||||
<Input
|
||||
id="date"
|
||||
type="date"
|
||||
value={displayValue ? dayjs(displayValue).format("YYYY-MM-DD") : ""}
|
||||
disabled={disabled}
|
||||
className={`input-date-picker bg-background pr-10 ${className || ""}`}
|
||||
onChange={(e) => {
|
||||
const newDate = new Date(e.target.value);
|
||||
setDisplayValue(newDate);
|
||||
|
||||
if (isValidDate(newDate)) {
|
||||
handleDateChange(newDate);
|
||||
setMonth(newDate);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setOpen(true);
|
||||
}
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date-picker"
|
||||
variant="ghost"
|
||||
color="white"
|
||||
disabled={disabled}
|
||||
className="absolute right-2 top-1/2 size-7 -translate-y-1/2 rounded-[0.4rem] border-none p-0"
|
||||
>
|
||||
<CalendarIcon className="!size-full" />
|
||||
<span className="sr-only">Select date</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto overflow-hidden p-0"
|
||||
align="end"
|
||||
alignOffset={-8}
|
||||
sideOffset={10}
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
captionLayout="dropdown"
|
||||
month={month}
|
||||
onMonthChange={setMonth}
|
||||
onSelect={(newDate) => {
|
||||
handleDateChange(newDate);
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function isValidDate(date: Date | undefined) {
|
||||
if (!date) {
|
||||
return false;
|
||||
}
|
||||
return !isNaN(date.getTime());
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.input-date-picker::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { getConfig } from "@common/config";
|
||||
import { getParentIds } from "./lib";
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -22,7 +23,17 @@ import {
|
||||
} from "../collapsible";
|
||||
import { ENDPOINT } from "../../config";
|
||||
import { cn, getCookie, setCookie } from "../../lib";
|
||||
import { getMenuList, getPreviewTreeMenuList, type MenuItem } from "../../api";
|
||||
import {
|
||||
getAnonymouseMenuList,
|
||||
getMenuList,
|
||||
getPreviewTreeMenuList,
|
||||
type MenuItem,
|
||||
} from "../../api";
|
||||
|
||||
const appConfig = getConfig();
|
||||
const appSiteId = appConfig.SITE_ID ?? "";
|
||||
const userId = getCookie(appConfig.AUTH_USER_ID ?? "");
|
||||
const isLoggedIn = !!userId;
|
||||
|
||||
export function LNB({
|
||||
viewOnly,
|
||||
@@ -42,24 +53,34 @@ export function LNB({
|
||||
handleMenuClick?: (item: MenuItem) => void;
|
||||
}) {
|
||||
const [defaultOpenMenuIds, setDefaultOpenMenuIds] = useState<number[]>([]);
|
||||
const [currentMenuCode, setCurrentMenuCode] = useState<number | null>(null);
|
||||
const prevPathRef = useRef(currentPath);
|
||||
|
||||
const cookieSiteId = getCookie("site-id-key") ?? "";
|
||||
const siteId = propSiteId ?? cookieSiteId;
|
||||
const siteId = propSiteId ?? appSiteId;
|
||||
|
||||
const { data: menuListData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
|
||||
queryFn: () => getMenuList(siteId),
|
||||
enabled: !viewOnly && !previewMode,
|
||||
enabled: isLoggedIn && !viewOnly && !previewMode,
|
||||
});
|
||||
|
||||
const { data: previewTreeMenuListData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId, "preview"],
|
||||
queryFn: () => getPreviewTreeMenuList(siteId),
|
||||
enabled: previewMode,
|
||||
enabled: isLoggedIn && previewMode,
|
||||
});
|
||||
|
||||
const menuList = previewMode ? previewTreeMenuListData : menuListData;
|
||||
const { data: anonymousMenuListData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ANONYMOUS, siteId],
|
||||
queryFn: () => getAnonymouseMenuList(siteId),
|
||||
enabled: !isLoggedIn,
|
||||
});
|
||||
|
||||
const menuList = isLoggedIn
|
||||
? previewMode
|
||||
? previewTreeMenuListData
|
||||
: menuListData
|
||||
: anonymousMenuListData;
|
||||
|
||||
function isPathMatched(path: string) {
|
||||
if (!path) return false;
|
||||
@@ -70,6 +91,7 @@ export function LNB({
|
||||
if (!menuList || !currentPath) return;
|
||||
|
||||
const matchedMenuIds: number[] = [];
|
||||
let matchedMenuCode: number | null = null;
|
||||
|
||||
const findMatchedMenus = (items: MenuItem[]) => {
|
||||
items.forEach((item) => {
|
||||
@@ -77,25 +99,32 @@ export function LNB({
|
||||
const parentIds = getParentIds(item.menuCd)(menuList);
|
||||
matchedMenuIds.push(...parentIds, item.menuCd);
|
||||
}
|
||||
if (item.children) findMatchedMenus(item.children);
|
||||
});
|
||||
};
|
||||
|
||||
if (item.children) {
|
||||
findMatchedMenus(item.children);
|
||||
}
|
||||
const findMatchedMenuCode = (items: MenuItem[]) => {
|
||||
items.forEach((item) => {
|
||||
if (matchedMenuCode) return;
|
||||
if (currentPath === item.urlPath) matchedMenuCode = item.menuCd;
|
||||
if (item.children) findMatchedMenuCode(item.children);
|
||||
});
|
||||
};
|
||||
|
||||
findMatchedMenus(menuList);
|
||||
findMatchedMenuCode(menuList);
|
||||
|
||||
if (matchedMenuIds.length > 0) {
|
||||
if (matchedMenuIds.length > 0)
|
||||
setDefaultOpenMenuIds((prev) => [
|
||||
...new Set([...prev, ...matchedMenuIds]),
|
||||
]);
|
||||
}
|
||||
|
||||
if (prevPathRef.current !== currentPath) {
|
||||
setDefaultOpenMenuIds(matchedMenuIds);
|
||||
prevPathRef.current = currentPath;
|
||||
}
|
||||
|
||||
if (matchedMenuCode) setCurrentMenuCode(matchedMenuCode);
|
||||
}, [menuList, currentPath]);
|
||||
|
||||
function MenuItem({
|
||||
@@ -106,7 +135,8 @@ export function LNB({
|
||||
isSubMenu?: boolean;
|
||||
}) {
|
||||
const { open: isSidebarOpen, setOpen } = useSidebar();
|
||||
const isMatched = isPathMatched(item.urlPath);
|
||||
const isMatched =
|
||||
isPathMatched(item.urlPath) && currentMenuCode === item.menuCd;
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isSidebarOpen) setOpen(true);
|
||||
|
||||
Reference in New Issue
Block a user