feat: 메뉴 재배치 및 UX 개선
- 메뉴: 즐겨찾기를 할일 좌측으로 이동 - 게시판: 답글 있는 게시글 삭제 방지 (댓글은 허용) - 즐겨찾기: ESC 키로 다이얼로그 닫기 지원 - 프로젝트: 기본 필터를 검토/진행/완료로 변경 (보류 해제)
This commit is contained in:
@@ -301,6 +301,19 @@ namespace Project.Web
|
|||||||
return JsonConvert.SerializeObject(new { Success = false, Message = "삭제 권한이 없습니다." });
|
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);
|
var cmd = new SqlCommand("DELETE FROM EETGW_Board WHERE idx = @idx", conn);
|
||||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,19 @@ export function FavoriteDialog({ isOpen, onClose }: FavoriteDialogProps) {
|
|||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [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 () => {
|
const loadFavorites = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -80,13 +80,12 @@ const leftDropdownMenus: DropdownMenuConfig[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 좌측 단독 액션 버튼 (즐겨찾기)
|
// 좌측 단독 액션 버튼
|
||||||
const leftActionItems: NavItem[] = [
|
const leftActionItems: NavItem[] = [];
|
||||||
{ icon: Star, label: '즐겨찾기', action: 'favorite' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 우측 메뉴 항목
|
// 우측 메뉴 항목
|
||||||
const rightNavItems: NavItem[] = [
|
const rightNavItems: NavItem[] = [
|
||||||
|
{ icon: Star, label: '즐겨찾기', action: 'favorite' },
|
||||||
{ path: '/todo', icon: CheckSquare, label: '할일' },
|
{ path: '/todo', icon: CheckSquare, label: '할일' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -490,11 +489,12 @@ export function Header(_props: HeaderProps) {
|
|||||||
<DropdownNavMenu key={menu.label} menu={menu} onAction={handleAction} />
|
<DropdownNavMenu key={menu.label} menu={menu} onAction={handleAction} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* 우측 메뉴들 (할일) */}
|
{/* 우측 메뉴들 (즐겨찾기, 할일) */}
|
||||||
{rightNavItems.map((item) => (
|
{rightNavItems.map((item) => (
|
||||||
|
item.path ? (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.path}
|
key={item.path}
|
||||||
to={item.path!}
|
to={item.path}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
clsx(
|
clsx(
|
||||||
'flex items-center space-x-2 px-4 py-2 rounded-lg transition-all duration-200 text-sm font-medium',
|
'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" />
|
<item.icon className="w-4 h-4" />
|
||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
</NavLink>
|
</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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -573,11 +583,12 @@ export function Header(_props: HeaderProps) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* 우측 메뉴들 (할일) */}
|
{/* 우측 메뉴들 (즐겨찾기, 할일) */}
|
||||||
{rightNavItems.map((item) => (
|
{rightNavItems.map((item) => (
|
||||||
|
item.path ? (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.path}
|
key={item.path}
|
||||||
to={item.path!}
|
to={item.path}
|
||||||
onClick={() => setIsMobileMenuOpen(false)}
|
onClick={() => setIsMobileMenuOpen(false)}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
clsx(
|
clsx(
|
||||||
@@ -591,6 +602,19 @@ export function Header(_props: HeaderProps) {
|
|||||||
<item.icon className="w-5 h-5" />
|
<item.icon className="w-5 h-5" />
|
||||||
<span className="font-medium">{item.label}</span>
|
<span className="font-medium">{item.label}</span>
|
||||||
</NavLink>
|
</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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -575,6 +575,7 @@ export function BoardList({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{(userLevel >= 9 || userId === '395552') && (
|
{(userLevel >= 9 || userId === '395552') && (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={handleEditClick}
|
onClick={handleEditClick}
|
||||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors flex items-center"
|
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" />
|
<Edit3 className="w-4 h-4 mr-2" />
|
||||||
편집
|
편집
|
||||||
</button>
|
</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
|
<button
|
||||||
onClick={() => setShowModal(false)}
|
onClick={() => setShowModal(false)}
|
||||||
|
|||||||
@@ -620,11 +620,11 @@ export function Jobreport() {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-white text-sm">
|
<td className="px-4 py-3 text-white text-sm">
|
||||||
{item.hrs || 0}h
|
{item.hrs || 0}
|
||||||
</td>
|
</td>
|
||||||
{canViewOT && (
|
{canViewOT && (
|
||||||
<td className="px-4 py-3 text-white text-sm">
|
<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>
|
||||||
)}
|
)}
|
||||||
<td className="px-4 py-3 text-white text-sm">{item.name || item.id || '-'}</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({
|
const [statusChecks, setStatusChecks] = useState({
|
||||||
검토: true,
|
검토: true,
|
||||||
진행: true,
|
진행: true,
|
||||||
대기: true,
|
대기: false,
|
||||||
보류: true,
|
보류: false,
|
||||||
완료: false,
|
완료: true,
|
||||||
'완료(보고)': false,
|
'완료(보고)': false,
|
||||||
취소: false,
|
취소: false,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user