Merge remote-tracking branch 'origin/main' into iipbs/sn/dev
This commit is contained in:
@@ -54,9 +54,9 @@ interface PageableList<T> {
|
||||
|
||||
const client = getApiClient();
|
||||
|
||||
export const getPopupList = async () => {
|
||||
export const getUserPopupList = async () => {
|
||||
const response = await client.get<PageableList<PopupInfo>>(
|
||||
`${ENDPOINT.CMS_SERVICE.POPUPS}`,
|
||||
`${ENDPOINT.CMS_SERVICE.POPUPS}/user/list`,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ENDPOINT } from "../../config";
|
||||
import { setCookie, getCookie } from "../../lib";
|
||||
import {
|
||||
getPopupInfo,
|
||||
getPopupList,
|
||||
getUserPopupList,
|
||||
getSiteInfo,
|
||||
getUserInfo,
|
||||
type PopupInfo,
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
} from "../dropdown-menu";
|
||||
import { Button } from "../button";
|
||||
import { SidebarTrigger } from "../sidebar";
|
||||
|
||||
const { SITE_ID } = getConfig();
|
||||
|
||||
@@ -46,9 +47,9 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
|
||||
enabled: !viewOnly,
|
||||
});
|
||||
|
||||
const { data: popupList } = useQuery({
|
||||
const { data: userPopupList } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.POPUPS],
|
||||
queryFn: () => getPopupList(),
|
||||
queryFn: () => getUserPopupList(),
|
||||
});
|
||||
|
||||
const openPopup = useCallback(async (popup: PopupInfo) => {
|
||||
@@ -80,20 +81,21 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!popupList?.content.length) return;
|
||||
if (!userPopupList?.content?.length) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
popupList.content.forEach((popup) => {
|
||||
userPopupList.content.forEach((popup) => {
|
||||
openPopup(popup);
|
||||
});
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [popupList, openPopup]);
|
||||
}, [userPopupList, openPopup]);
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-30 box-border flex h-[5rem] w-full items-center justify-between border-b border-solid border-border bg-background">
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center px-6">
|
||||
<SidebarTrigger className="size-12 !bg-transparent p-2 hover:!bg-accent [&>svg]:!size-full [&>svg]:text-foreground" />
|
||||
<div className="MobMenuBtn">
|
||||
<img src={images.menuBtn} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { getConfig } from "@common/config";
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
useSidebar,
|
||||
} from "../sidebar";
|
||||
import {
|
||||
Collapsible,
|
||||
@@ -39,17 +42,58 @@ export function LNB({
|
||||
enabled: !viewOnly,
|
||||
});
|
||||
|
||||
const [openMenuIds, setOpenMenuIds] = useState<number[]>([]);
|
||||
const [defaultOpenMenuIds, setDefaultOpenMenuIds] = useState<number[]>([]);
|
||||
const prevPathRef = useRef(currentPath);
|
||||
|
||||
function isPathMatched(path: string) {
|
||||
if (!path) return false;
|
||||
return currentPath?.startsWith(path);
|
||||
}
|
||||
|
||||
function MenuItem({ item }: { item: MenuItem }) {
|
||||
useEffect(() => {
|
||||
if (!menuList || !currentPath) return;
|
||||
|
||||
const matchedMenuIds: number[] = [];
|
||||
|
||||
const findMatchedMenus = (items: MenuItem[]) => {
|
||||
items.forEach((item) => {
|
||||
if (isPathMatched(item.urlPath)) {
|
||||
const parentIds = getParentIds(item.menuCd)(menuList);
|
||||
matchedMenuIds.push(...parentIds, item.menuCd);
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
findMatchedMenus(item.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
findMatchedMenus(menuList);
|
||||
|
||||
if (matchedMenuIds.length > 0) {
|
||||
setDefaultOpenMenuIds((prev) => [
|
||||
...new Set([...prev, ...matchedMenuIds]),
|
||||
]);
|
||||
}
|
||||
|
||||
if (prevPathRef.current !== currentPath) {
|
||||
setDefaultOpenMenuIds(matchedMenuIds);
|
||||
prevPathRef.current = currentPath;
|
||||
}
|
||||
}, [menuList, currentPath]);
|
||||
|
||||
function MenuItem({
|
||||
item,
|
||||
isSubMenu,
|
||||
}: {
|
||||
item: MenuItem;
|
||||
isSubMenu?: boolean;
|
||||
}) {
|
||||
const { open: isSidebarOpen, setOpen } = useSidebar();
|
||||
const isMatched = isPathMatched(item.urlPath);
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isSidebarOpen) setOpen(true);
|
||||
setCookie("menu-code-key", item.menuCd.toString());
|
||||
handleMenuClick?.(item);
|
||||
};
|
||||
@@ -58,66 +102,82 @@ export function LNB({
|
||||
return <MenuGroup item={item} />;
|
||||
}
|
||||
|
||||
const MenuItem = isSubMenu ? SidebarMenuSubItem : SidebarMenuItem;
|
||||
const MenuButton = isSubMenu ? SidebarMenuSubButton : SidebarMenuButton;
|
||||
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
<MenuItem>
|
||||
<MenuButton
|
||||
className={cn(
|
||||
"h-auto py-4 text-[1.4rem] text-label transition-colors hover:bg-accent",
|
||||
"h-auto cursor-pointer py-4 text-[1.4rem] text-label transition-colors hover:bg-accent",
|
||||
isMatched && "bg-accent !text-primary",
|
||||
!isSidebarOpen && "justify-center",
|
||||
)}
|
||||
data-matched={isMatched}
|
||||
onClick={handleClick}
|
||||
tooltip={item.menuKornNm}
|
||||
>
|
||||
<i className={cn("diveicon ml-2", `di-${item.iconNm}`)} />
|
||||
{item.menuKornNm}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<i
|
||||
className={cn(
|
||||
"diveicon",
|
||||
`di-${item.iconNm}`,
|
||||
isSidebarOpen && "ml-2",
|
||||
)}
|
||||
/>
|
||||
<span className={cn("whitespace-nowrap", !isSidebarOpen && "hidden")}>
|
||||
{item.menuKornNm}
|
||||
</span>
|
||||
</MenuButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
function MenuGroup({ item }: { item: MenuItem }) {
|
||||
const hasMatchedChild = item.children?.some((child) =>
|
||||
isPathMatched(child.urlPath),
|
||||
);
|
||||
|
||||
const isOpen = openMenuIds.includes(item.menuCd) || hasMatchedChild;
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (open) {
|
||||
if (!openMenuIds.includes(item.menuCd)) {
|
||||
const parentIds = getParentIds(item.menuCd)(menuList ?? []);
|
||||
setOpenMenuIds([...parentIds, item.menuCd]);
|
||||
}
|
||||
} else {
|
||||
setOpenMenuIds((prev) => prev.filter((id) => id !== item.menuCd));
|
||||
}
|
||||
};
|
||||
const { open: isSidebarOpen } = useSidebar();
|
||||
|
||||
return (
|
||||
<Collapsible open={isOpen} onOpenChange={handleOpenChange}>
|
||||
<Collapsible
|
||||
defaultOpen={defaultOpenMenuIds.includes(item.menuCd)}
|
||||
className="group/collapsible"
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<div className="group">
|
||||
<SidebarMenuButton className="group/menu-button flex h-auto items-center justify-between py-4 pr-2 text-[1.4rem] group-data-[state=open]:bg-transparent">
|
||||
<div className="flex items-center gap-2">
|
||||
<i
|
||||
className={cn(
|
||||
"diveicon ml-2 text-label",
|
||||
`di-${item.iconNm}`,
|
||||
)}
|
||||
/>
|
||||
<span className="text-label group-hover/menu-button:text-foreground">
|
||||
{item.menuKornNm}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronRight className="!size-6 text-label group-data-[state=open]:rotate-90" />
|
||||
</SidebarMenuButton>
|
||||
</div>
|
||||
<SidebarMenuButton
|
||||
className={cn(
|
||||
"flex h-auto items-center py-4 pr-2 text-[1.4rem] group-data-[state=open]/collapsible:bg-transparent",
|
||||
!isSidebarOpen && "justify-center",
|
||||
)}
|
||||
tooltip={item.menuKornNm}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<i
|
||||
className={cn(
|
||||
"diveicon text-label",
|
||||
`di-${item.iconNm}`,
|
||||
isSidebarOpen && "ml-2",
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"whitespace-nowrap text-label group-hover/menu-button:text-foreground",
|
||||
!isSidebarOpen && "hidden",
|
||||
)}
|
||||
>
|
||||
{item.menuKornNm}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
"ml-auto !size-6 text-label group-data-[state=open]/collapsible:rotate-90",
|
||||
!isSidebarOpen && "hidden",
|
||||
)}
|
||||
/>
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub className="mr-0">
|
||||
<SidebarMenuSub>
|
||||
{item.children?.map((child) => (
|
||||
<MenuItem key={child.menuCd} item={child} />
|
||||
<MenuItem key={child.menuCd} item={child} isSubMenu />
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
@@ -127,7 +187,10 @@ export function LNB({
|
||||
}
|
||||
|
||||
return (
|
||||
<Sidebar className="border-border bg-background px-4 py-[6rem]">
|
||||
<Sidebar
|
||||
collapsible="icon"
|
||||
className="border-border bg-background px-4 py-[6rem]"
|
||||
>
|
||||
<SidebarContent className="bg-background">
|
||||
<SidebarMenu className="gap-0">
|
||||
{menuList?.map((item) => <MenuItem key={item.menuCd} item={item} />)}
|
||||
|
||||
@@ -28,7 +28,7 @@ const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||
const SIDEBAR_WIDTH = "22rem";
|
||||
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
||||
const SIDEBAR_WIDTH_ICON = "3rem";
|
||||
const SIDEBAR_WIDTH_ICON = "6rem";
|
||||
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
||||
|
||||
type SidebarContextProps = {
|
||||
@@ -252,7 +252,7 @@ const Sidebar = React.forwardRef<
|
||||
<div
|
||||
data-sidebar="sidebar-wrapper"
|
||||
className={cn(
|
||||
"fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
|
||||
"fixed inset-y-0 z-20 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
|
||||
side === "left"
|
||||
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
@@ -530,7 +530,7 @@ const SidebarMenuItem = React.forwardRef<
|
||||
SidebarMenuItem.displayName = "SidebarMenuItem";
|
||||
|
||||
const sidebarMenuButtonVariants = cva(
|
||||
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-16 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -602,6 +602,7 @@ const SidebarMenuButton = React.forwardRef<
|
||||
side="right"
|
||||
align="center"
|
||||
hidden={state !== "collapsed" || isMobile}
|
||||
className="text-lg"
|
||||
{...tooltip}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -23,12 +23,19 @@
|
||||
background: none !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
[data-sidebar="menu-button"]:hover {
|
||||
[data-sidebar="trigger"]:hover,
|
||||
[data-sidebar="menu-button"]:hover,
|
||||
[data-sidebar="menu-sub-button"]:hover {
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
[data-sidebar="menu-button"][data-matched="true"] {
|
||||
[data-sidebar="trigger"][data-matched="true"],
|
||||
[data-sidebar="menu-button"][data-matched="true"],
|
||||
[data-sidebar="menu-sub-button"][data-matched="true"] {
|
||||
background: rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
[data-sidebar="trigger"] > svg {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="red"] {
|
||||
@@ -58,11 +65,18 @@
|
||||
background: none !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
[data-sidebar="menu-button"]:hover {
|
||||
[data-sidebar="trigger"]:hover,
|
||||
[data-sidebar="menu-button"]:hover,
|
||||
[data-sidebar="menu-sub-button"]:hover {
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
[data-sidebar="menu-button"][data-matched="true"] {
|
||||
[data-sidebar="trigger"][data-matched="true"],
|
||||
[data-sidebar="menu-button"][data-matched="true"],
|
||||
[data-sidebar="menu-sub-button"][data-matched="true"] {
|
||||
background: rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
[data-sidebar="trigger"] > svg {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user