Files
nice-app-web/src/entities/menu/ui/menu-category.tsx
Jay Sheen 8fbc7d2f74 즐겨찾기 추가/삭제 로직 개선 및 스크롤 위치 수정
- 즐겨찾기 삭제 시 최소 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>
2025-11-13 17:24:29 +09:00

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>
</>
);
};