Files
Groupware/Project/frontend/src/pages/PatchList.tsx

346 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { FileText, Search, RefreshCw, Calendar, Edit3, User } from 'lucide-react';
import { comms } from '@/communication';
import { BoardItem } from '@/types';
export function PatchList() {
const [boardList, setBoardList] = useState<BoardItem[]>([]);
const [loading, setLoading] = useState(false);
const [searchKey, setSearchKey] = useState('');
const [selectedItem, setSelectedItem] = useState<BoardItem | null>(null);
const [showModal, setShowModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [editFormData, setEditFormData] = useState<BoardItem | null>(null);
const [userLevel, setUserLevel] = useState(0);
const [userId, setUserId] = useState('');
useEffect(() => {
loadUserInfo();
loadData();
}, []);
const loadUserInfo = async () => {
try {
const response = await comms.checkLoginStatus();
if (response.Success && response.User) {
setUserLevel(response.User.Level);
setUserId(response.User.Id);
}
} catch (error) {
console.error('사용자 정보 로드 오류:', error);
}
};
const loadData = async () => {
setLoading(true);
try {
console.log('패치내역 조회:', { bidx: 5, searchKey });
const response = await comms.getBoardList(5, searchKey); // bidx=5: 패치내역
console.log('패치내역 응답:', response);
if (response.Success && response.Data) {
setBoardList(response.Data);
} else {
console.warn('패치내역 없음:', response.Message);
setBoardList([]);
}
} catch (error) {
console.error('패치내역 로드 오류:', error);
alert('데이터를 불러오는 중 오류가 발생했습니다.');
} finally {
setLoading(false);
}
};
const handleSearch = () => {
loadData();
};
const handleRowClick = async (item: BoardItem) => {
try {
const response = await comms.getBoardDetail(item.idx);
if (response.Success && response.Data) {
setSelectedItem(response.Data);
// 개발자(레벨 >= 9) 또는 사번 395552이면 바로 편집 모드
if (userLevel >= 9 || userId === '395552') {
setEditFormData(response.Data);
setShowEditModal(true);
} else {
setShowModal(true);
}
}
} catch (error) {
console.error('상세 조회 오류:', error);
alert('데이터를 불러오는 중 오류가 발생했습니다.');
}
};
const handleEditClick = () => {
if (selectedItem) {
setEditFormData(selectedItem);
setShowModal(false);
setShowEditModal(true);
}
};
const handleEditSave = async () => {
if (!editFormData) return;
try {
// TODO: Board_Edit API 구현 필요
alert('게시판 수정 API가 아직 구현되지 않았습니다.');
console.log('수정할 데이터:', editFormData);
} catch (error) {
console.error('저장 오류:', error);
alert('저장 중 오류가 발생했습니다.');
}
};
const formatDate = (dateStr: string | null) => {
if (!dateStr) return '-';
try {
const date = new Date(dateStr);
const yy = String(date.getFullYear()).slice(-2);
const mm = String(date.getMonth() + 1).padStart(2, '0');
const dd = String(date.getDate()).padStart(2, '0');
return `${yy}.${mm}.${dd}`;
} catch {
return dateStr;
}
};
return (
<div className="space-y-6 animate-fade-in">
{/* 검색 필터 */}
<div className="glass-effect rounded-2xl p-6">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 flex-1">
<label className="text-white/70 text-sm font-medium whitespace-nowrap"></label>
<input
type="text"
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
placeholder="제목, 내용, 작성자 등"
className="flex-1 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
/>
</div>
<button
onClick={handleSearch}
disabled={loading}
className="h-10 bg-primary-500 hover:bg-primary-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center disabled:opacity-50"
>
{loading ? (
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
) : (
<Search className="w-4 h-4 mr-2" />
)}
</button>
</div>
</div>
{/* 패치내역 목록 */}
<div className="glass-effect rounded-2xl overflow-hidden">
<div className="px-6 py-4 border-b border-white/10 flex items-center justify-between">
<h3 className="text-lg font-semibold text-white flex items-center">
<FileText className="w-5 h-5 mr-2" />
</h3>
<span className="text-white/60 text-sm">{boardList.length}</span>
</div>
<div className="divide-y divide-white/10 max-h-[calc(100vh-300px)] overflow-y-auto">
{loading ? (
<div className="px-6 py-8 text-center">
<div className="flex items-center justify-center">
<RefreshCw className="w-5 h-5 mr-2 animate-spin text-white/50" />
<span className="text-white/50"> ...</span>
</div>
</div>
) : boardList.length === 0 ? (
<div className="px-6 py-8 text-center">
<FileText className="w-12 h-12 mx-auto mb-3 text-white/30" />
<p className="text-white/50"> .</p>
</div>
) : (
boardList.map((item) => (
<div
key={item.idx}
className="px-6 py-3 hover:bg-white/5 transition-colors cursor-pointer"
onClick={() => handleRowClick(item)}
>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 flex-shrink-0">
{item.header && (
<span className="px-2 py-0.5 bg-primary-500/20 text-primary-400 text-xs rounded whitespace-nowrap">
{item.header}
</span>
)}
{item.cate && (
<span className="px-2 py-0.5 bg-white/10 text-white/70 text-xs rounded whitespace-nowrap">
{item.cate}
</span>
)}
</div>
<h4 className="text-[#87CEEB] font-medium flex-1 min-w-0 truncate">{item.title}</h4>
<div className="flex items-center text-white/60 text-xs flex-shrink-0">
<Calendar className="w-3 h-3 mr-1" />
{formatDate(item.wdate)}
</div>
</div>
</div>
))
)}
</div>
</div>
{/* 상세 모달 */}
{showModal && selectedItem && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden border border-white/10">
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
<div className="flex items-center gap-2">
{selectedItem.header && (
<span className="px-2 py-1 bg-primary-500/20 text-primary-400 text-sm rounded">
{selectedItem.header}
</span>
)}
{selectedItem.cate && (
<span className="px-2 py-1 bg-white/10 text-white/70 text-sm rounded">
{selectedItem.cate}
</span>
)}
<h2 className="text-xl font-bold text-white ml-2">{selectedItem.title}</h2>
</div>
<button
onClick={() => setShowModal(false)}
className="text-white/50 hover:text-white transition-colors"
>
<span className="text-2xl">×</span>
</button>
</div>
<div className="px-6 py-4 border-b border-white/10 flex items-center gap-4 text-sm text-white/60">
<div className="flex items-center">
<User className="w-4 h-4 mr-1" />
{selectedItem.wuid_name || selectedItem.wuid}
</div>
<div className="flex items-center">
<Calendar className="w-4 h-4 mr-1" />
{formatDate(selectedItem.wdate)}
</div>
</div>
<div className="overflow-y-auto max-h-[calc(90vh-180px)] p-6">
<div className="prose prose-invert max-w-none">
<div className="text-white whitespace-pre-wrap">{selectedItem.contents}</div>
</div>
</div>
<div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-white/10 bg-white/5">
{(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"
>
<Edit3 className="w-4 h-4 mr-2" />
</button>
)}
<button
onClick={() => setShowModal(false)}
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
>
</button>
</div>
</div>
</div>
)}
{/* 편집 모달 */}
{showEditModal && editFormData && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden border border-white/10">
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
<h2 className="text-xl font-bold text-white flex items-center">
<Edit3 className="w-5 h-5 mr-2" />
</h2>
<button
onClick={() => setShowEditModal(false)}
className="text-white/50 hover:text-white transition-colors"
>
<span className="text-2xl">×</span>
</button>
</div>
<div className="overflow-y-auto max-h-[calc(90vh-180px)] p-6 space-y-4">
<div>
<label className="block text-white/70 text-sm font-medium mb-2"></label>
<input
type="text"
value={editFormData.header || ''}
onChange={(e) => setEditFormData({ ...editFormData, header: e.target.value })}
className="w-full h-10 bg-white/10 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
placeholder="예: v2.0.0"
/>
</div>
<div>
<label className="block text-white/70 text-sm font-medium mb-2"></label>
<input
type="text"
value={editFormData.cate || ''}
onChange={(e) => setEditFormData({ ...editFormData, cate: e.target.value })}
className="w-full h-10 bg-white/10 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
placeholder="예: 기능개선"
/>
</div>
<div>
<label className="block text-white/70 text-sm font-medium mb-2"></label>
<input
type="text"
value={editFormData.title || ''}
onChange={(e) => setEditFormData({ ...editFormData, title: e.target.value })}
className="w-full h-10 bg-white/10 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
placeholder="패치 제목"
/>
</div>
<div>
<label className="block text-white/70 text-sm font-medium mb-2"></label>
<textarea
value={editFormData.contents || ''}
onChange={(e) => setEditFormData({ ...editFormData, contents: e.target.value })}
rows={15}
className="w-full bg-white/10 border border-white/30 rounded-lg px-3 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 resize-none"
placeholder="패치 내용을 입력하세요..."
/>
</div>
</div>
<div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-white/10 bg-white/5">
<button
onClick={() => setShowEditModal(false)}
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
>
</button>
<button
onClick={handleEditSave}
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors"
>
</button>
</div>
</div>
</div>
)}
</div>
);
}