- 즐겨찾기 삭제 시 최소 1개 유지 검증 추가 (favorite-wrapper.tsx) - 즐겨찾기 추가/삭제 로직 분리 및 개선 (menu-category.tsx) - 추가 시: 최대 10개만 체크 - 삭제 시: 최소 1개 유지 체크 - 각 조건에서 early return으로 명확한 흐름 구성 - 메뉴 오픈 시 즐겨찾기 목록 스크롤을 맨 앞으로 초기화 - prevMenuOn 상태로 메뉴 오픈 감지 - 추가/삭제 시에만 마지막 아이템으로 스크롤 - 로컬라이제이션 키 추가 - cannotDeleteLastItem: 최소 1개 유지 메시지 - cannotAddMoreThan10: 최대 10개 제한 메시지 - snackBar import 추가 및 showAlert에서 snackBar로 변경 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
191 lines
5.5 KiB
TypeScript
191 lines
5.5 KiB
TypeScript
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
|
|
import { useFavoriteEditOnStore, useMenuIds, useMenuOnStore, useStore } from '@/shared/model/store';
|
|
import { UserFavorite } from '@/entities/user/model/types';
|
|
import { RefObject, useEffect, useState } from 'react';
|
|
import { MenuItem } from '../model/types';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { MenuItems } from '@/entities/common/model/constant';
|
|
import { showAlert } from '@/widgets/show-alert';
|
|
import { checkGrant } from '@/shared/lib/check-grant';
|
|
import { snackBar } from '@/shared/lib';
|
|
|
|
export interface MenuCategoryProps {
|
|
menuId?: number;
|
|
menuName?: string;
|
|
iconFilePath?: string;
|
|
subMenu?: Array<MenuItem>;
|
|
changeMenuId?: string;
|
|
setChangeMenuId: (menuIdChecked?: string) => void;
|
|
buttonRefs: RefObject<Array<HTMLDivElement>>;
|
|
itemIndex: number;
|
|
};
|
|
|
|
export const MenuCategory = ({
|
|
menuId,
|
|
iconFilePath,
|
|
menuName,
|
|
subMenu,
|
|
changeMenuId,
|
|
setChangeMenuId,
|
|
buttonRefs,
|
|
itemIndex
|
|
}: MenuCategoryProps) => {
|
|
const { navigate } = useNavigate();
|
|
const { t, i18n } = useTranslation();
|
|
|
|
const [favoriteItems, setFavoriteItems] = useState<Array<UserFavorite>>([]);
|
|
// const [menuIds, setMenuIds] = useState<Array<number | undefined>>([]);
|
|
|
|
const { menuOn, setMenuOn } = useMenuOnStore();
|
|
const { favoriteEditOn, setFavoriteEditOn } = useFavoriteEditOnStore();
|
|
const { menuIds, setMenuIds, deleteMenuId} = useMenuIds();
|
|
|
|
const onClickToNavigate = (menuId?: number, path?: string) => {
|
|
if(menuId && checkGrant(menuId, 'R')){
|
|
if(!!path && !favoriteEditOn){
|
|
setMenuOn(false);
|
|
navigate(path);
|
|
}
|
|
}
|
|
else{
|
|
showAlert(t('common.nopermission'));
|
|
}
|
|
};
|
|
|
|
const favoriteSetting = (
|
|
checked: boolean,
|
|
menuId?: number,
|
|
menuName?: string,
|
|
menuNameEng?: string,
|
|
iconFilePath?: string,
|
|
programPath?: string,
|
|
) => {
|
|
let userFavorite = useStore.getState().UserStore.userFavorite;
|
|
|
|
// 추가 시: 최대 10개 체크
|
|
if(checked && userFavorite.length >= 10){
|
|
snackBar(t('favorite.cannotAddMoreThan10'));
|
|
return;
|
|
}
|
|
|
|
// 삭제 시: 최소 1개 체크
|
|
if(!checked && userFavorite.length <= 1){
|
|
snackBar(t('favorite.cannotDeleteLastItem'));
|
|
return;
|
|
}
|
|
|
|
// 추가 또는 삭제 실행
|
|
if(checked){
|
|
userFavorite = [
|
|
...userFavorite,
|
|
{
|
|
menuId: menuId,
|
|
menuName: menuName,
|
|
menuNameEng: menuNameEng,
|
|
iconFilePath: iconFilePath,
|
|
programPath: programPath
|
|
}
|
|
];
|
|
}
|
|
else{
|
|
userFavorite = userFavorite.filter((value, _) => {
|
|
return value.menuId !== menuId
|
|
});
|
|
}
|
|
|
|
useStore.getState().UserStore.setUserFavorite(userFavorite);
|
|
setChangeMenuId(`${menuId}-${checked}`);
|
|
callFavoiteItems();
|
|
};
|
|
|
|
const callFavoiteItems = () => {
|
|
let userFavorite = useStore.getState().UserStore.userFavorite;
|
|
setFavoriteItems(userFavorite);
|
|
let newArr: Array<number | undefined> = userFavorite.map((value, index) => {
|
|
return value.menuId;
|
|
});
|
|
setMenuIds(newArr);
|
|
};
|
|
|
|
const getMenuItems = () => {
|
|
let rs = [];
|
|
if(subMenu){
|
|
for(let i=0;i<subMenu.length;i++){
|
|
const displayName = i18n.language === 'en' && subMenu[i]?.menuNameEng
|
|
? subMenu[i]?.menuNameEng
|
|
: subMenu[i]?.menuName;
|
|
|
|
if(!!favoriteEditOn && subMenu[i] && subMenu[i]?.menuId){
|
|
rs.push(
|
|
<li
|
|
key={ `menu-item-key-${menuId}-${i}` }
|
|
onClick={ () => onClickToNavigate(subMenu[i]?.menuId, subMenu[i]?.programPath) }
|
|
>
|
|
<span>{ displayName }</span>
|
|
<div className="check_box_scrap">
|
|
<input
|
|
id={ `menu-item-checkbox-${subMenu[i]?.menuId}-${i}` }
|
|
className="checkbox"
|
|
type="checkbox"
|
|
checked={ menuIds.includes(subMenu[i]?.menuId)? true: false }
|
|
onChange={ (e) => favoriteSetting(
|
|
e.target.checked,
|
|
subMenu[i]?.menuId,
|
|
subMenu[i]?.menuName,
|
|
subMenu[i]?.menuNameEng,
|
|
subMenu[i]?.iconFilePath,
|
|
subMenu[i]?.programPath,
|
|
)}
|
|
/>
|
|
<label
|
|
className="gtr"
|
|
htmlFor={ `menu-item-checkbox-${subMenu[i]?.menuId}-${i}` }
|
|
></label>
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|
|
else{
|
|
rs.push(
|
|
<li
|
|
key={ `menu-item-key-${i}` }
|
|
onClick={ () => onClickToNavigate(subMenu[i]?.menuId, subMenu[i]?.programPath) }
|
|
>{ displayName }</li>
|
|
);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return rs;
|
|
};
|
|
|
|
const displayCategoryName = i18n.language === 'en'
|
|
? MenuItems.find(item => item.menuId === menuId)?.menuNameEng || menuName
|
|
: menuName;
|
|
|
|
useEffect(() => {
|
|
callFavoiteItems();
|
|
}, [changeMenuId]);
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className={`menu-category ${menuId}`}
|
|
ref={ (element: HTMLDivElement) => {
|
|
if (element) {
|
|
buttonRefs.current[itemIndex] = element;
|
|
}
|
|
} }
|
|
>
|
|
<div className="category-header">
|
|
<div className={ 'category-icon ' + iconFilePath }></div>
|
|
<span>{ displayCategoryName }</span>
|
|
</div>
|
|
<ul className="category-items">
|
|
{ getMenuItems() }
|
|
</ul>
|
|
</div>
|
|
</>
|
|
);
|
|
}; |