feat: 메뉴 재배치 및 UX 개선
- 메뉴: 즐겨찾기를 할일 좌측으로 이동 - 게시판: 답글 있는 게시글 삭제 방지 (댓글은 허용) - 즐겨찾기: ESC 키로 다이얼로그 닫기 지원 - 프로젝트: 기본 필터를 검토/진행/완료로 변경 (보류 해제)
This commit is contained in:
@@ -301,6 +301,19 @@ namespace Project.Web
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "삭제 권한이 없습니다." });
|
||||
}
|
||||
|
||||
// 답글 존재 여부 확인 (is_comment=false인 답글만)
|
||||
var replyCheckCmd = new SqlCommand(@"
|
||||
SELECT COUNT(*)
|
||||
FROM EETGW_Board
|
||||
WHERE root_idx = @idx AND depth > 0 AND (is_comment IS NULL OR is_comment = 0)", conn);
|
||||
replyCheckCmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
var replyCount = (int)replyCheckCmd.ExecuteScalar();
|
||||
|
||||
if (replyCount > 0)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "답글이 있는 게시글은 삭제할 수 없습니다. 답글을 먼저 삭제해주세요." });
|
||||
}
|
||||
|
||||
var cmd = new SqlCommand("DELETE FROM EETGW_Board WHERE idx = @idx", conn);
|
||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
|
||||
|
||||
@@ -93,6 +93,19 @@ export function FavoriteDialog({ isOpen, onClose }: FavoriteDialogProps) {
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && isOpen) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
window.addEventListener('keydown', handleEscape);
|
||||
return () => window.removeEventListener('keydown', handleEscape);
|
||||
}
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
const loadFavorites = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
|
||||
@@ -80,13 +80,12 @@ const leftDropdownMenus: DropdownMenuConfig[] = [
|
||||
},
|
||||
];
|
||||
|
||||
// 좌측 단독 액션 버튼 (즐겨찾기)
|
||||
const leftActionItems: NavItem[] = [
|
||||
{ icon: Star, label: '즐겨찾기', action: 'favorite' },
|
||||
];
|
||||
// 좌측 단독 액션 버튼
|
||||
const leftActionItems: NavItem[] = [];
|
||||
|
||||
// 우측 메뉴 항목
|
||||
const rightNavItems: NavItem[] = [
|
||||
{ icon: Star, label: '즐겨찾기', action: 'favorite' },
|
||||
{ path: '/todo', icon: CheckSquare, label: '할일' },
|
||||
];
|
||||
|
||||
@@ -490,11 +489,12 @@ export function Header(_props: HeaderProps) {
|
||||
<DropdownNavMenu key={menu.label} menu={menu} onAction={handleAction} />
|
||||
))}
|
||||
|
||||
{/* 우측 메뉴들 (할일) */}
|
||||
{/* 우측 메뉴들 (즐겨찾기, 할일) */}
|
||||
{rightNavItems.map((item) => (
|
||||
item.path ? (
|
||||
<NavLink
|
||||
key={item.path}
|
||||
to={item.path!}
|
||||
to={item.path}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
'flex items-center space-x-2 px-4 py-2 rounded-lg transition-all duration-200 text-sm font-medium',
|
||||
@@ -507,6 +507,16 @@ export function Header(_props: HeaderProps) {
|
||||
<item.icon className="w-4 h-4" />
|
||||
<span>{item.label}</span>
|
||||
</NavLink>
|
||||
) : (
|
||||
<button
|
||||
key={item.label}
|
||||
onClick={() => item.action && handleAction(item.action)}
|
||||
className="flex items-center space-x-2 px-4 py-2 rounded-lg transition-all duration-200 text-sm font-medium text-white/70 hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
<item.icon className="w-4 h-4" />
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
)
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
@@ -573,11 +583,12 @@ export function Header(_props: HeaderProps) {
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* 우측 메뉴들 (할일) */}
|
||||
{/* 우측 메뉴들 (즐겨찾기, 할일) */}
|
||||
{rightNavItems.map((item) => (
|
||||
item.path ? (
|
||||
<NavLink
|
||||
key={item.path}
|
||||
to={item.path!}
|
||||
to={item.path}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
clsx(
|
||||
@@ -591,6 +602,19 @@ export function Header(_props: HeaderProps) {
|
||||
<item.icon className="w-5 h-5" />
|
||||
<span className="font-medium">{item.label}</span>
|
||||
</NavLink>
|
||||
) : (
|
||||
<button
|
||||
key={item.label}
|
||||
onClick={() => {
|
||||
if (item.action) handleAction(item.action);
|
||||
setIsMobileMenuOpen(false);
|
||||
}}
|
||||
className="flex items-center space-x-3 px-4 py-3 rounded-lg transition-all duration-200 text-white/70 hover:bg-white/10 hover:text-white w-full text-left"
|
||||
>
|
||||
<item.icon className="w-5 h-5" />
|
||||
<span className="font-medium">{item.label}</span>
|
||||
</button>
|
||||
)
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -575,6 +575,7 @@ export function BoardList({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{(userLevel >= 9 || userId === '395552') && (
|
||||
<>
|
||||
<button
|
||||
onClick={handleEditClick}
|
||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors flex items-center"
|
||||
@@ -582,6 +583,28 @@ export function BoardList({
|
||||
<Edit3 className="w-4 h-4 mr-2" />
|
||||
편집
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!selectedItem) return;
|
||||
if (!confirm('정말 삭제하시겠습니까?')) return;
|
||||
try {
|
||||
const response = await comms.deleteBoard(selectedItem.idx);
|
||||
if (response.Success) {
|
||||
setShowModal(false);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('삭제 오류:', error);
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
}}
|
||||
className="px-4 py-2 rounded-lg bg-red-500 hover:bg-red-600 text-white transition-colors"
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
|
||||
@@ -620,11 +620,11 @@ export function Jobreport() {
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-white text-sm">
|
||||
{item.hrs || 0}h
|
||||
{item.hrs || 0}
|
||||
</td>
|
||||
{canViewOT && (
|
||||
<td className="px-4 py-3 text-white text-sm">
|
||||
{item.ot ? <span className="text-warning-400">{item.ot}h</span> : '-'}
|
||||
{item.ot ? <span className="text-warning-400">{item.ot}</span> : '-'}
|
||||
</td>
|
||||
)}
|
||||
<td className="px-4 py-3 text-white text-sm">{item.name || item.id || '-'}</td>
|
||||
|
||||
@@ -42,9 +42,9 @@ export function Project() {
|
||||
const [statusChecks, setStatusChecks] = useState({
|
||||
검토: true,
|
||||
진행: true,
|
||||
대기: true,
|
||||
보류: true,
|
||||
완료: false,
|
||||
대기: false,
|
||||
보류: false,
|
||||
완료: true,
|
||||
'완료(보고)': false,
|
||||
취소: false,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user