- MailService.cs 추가: ServiceBase 상속받는 Windows 서비스 클래스 - Program.cs 수정: 서비스/콘솔 모드 지원, 설치/제거 기능 추가 - 프로젝트 설정: System.ServiceProcess 참조 추가 - 배치 파일 추가: 서비스 설치/제거/콘솔실행 스크립트 주요 기능: - Windows 서비스로 백그라운드 실행 - 명령행 인수로 모드 선택 (-install, -uninstall, -console) - EventLog를 통한 서비스 로깅 - 안전한 서비스 시작/중지 처리 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
559 lines
30 KiB
JavaScript
559 lines
30 KiB
JavaScript
// CommonApp.jsx - React Common Code Management Component for GroupWare
|
|
const { useState, useEffect, useRef } = React;
|
|
|
|
const CommonApp = () => {
|
|
// 상태 관리
|
|
const [groupData, setGroupData] = useState([]);
|
|
const [currentData, setCurrentData] = useState([]);
|
|
const [selectedGroupCode, setSelectedGroupCode] = useState(null);
|
|
const [selectedGroupName, setSelectedGroupName] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [showEditModal, setShowEditModal] = useState(false);
|
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
|
const [deleteTargetIdx, setDeleteTargetIdx] = useState(null);
|
|
const [editMode, setEditMode] = useState('add');
|
|
|
|
// 편집 폼 데이터
|
|
const [editData, setEditData] = useState({
|
|
idx: '',
|
|
grp: '',
|
|
code: '',
|
|
svalue: '',
|
|
ivalue: '',
|
|
fvalue: '',
|
|
svalue2: '',
|
|
memo: ''
|
|
});
|
|
|
|
// 페이지 로드시 초기 데이터 로드
|
|
useEffect(() => {
|
|
loadGroups();
|
|
}, []);
|
|
|
|
// API 호출 함수들
|
|
const loadGroups = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await fetch('http://127.0.0.1:7979/Common/GetGroups');
|
|
const data = await response.json();
|
|
setGroupData(data || []);
|
|
} catch (error) {
|
|
console.error('그룹 데이터 로드 중 오류 발생:', error);
|
|
showNotification('그룹 데이터 로드 중 오류가 발생했습니다.', 'error');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadDataByGroup = async (grp) => {
|
|
setLoading(true);
|
|
try {
|
|
let url = 'http://127.0.0.1:7979/Common/GetList';
|
|
if (grp) {
|
|
url += '?grp=' + encodeURIComponent(grp);
|
|
}
|
|
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
setCurrentData(data || []);
|
|
} catch (error) {
|
|
console.error('데이터 로드 중 오류 발생:', error);
|
|
showNotification('데이터 로드 중 오류가 발생했습니다.', 'error');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const saveData = async () => {
|
|
try {
|
|
const data = {
|
|
idx: parseInt(editData.idx) || 0,
|
|
grp: editData.grp,
|
|
code: editData.code,
|
|
svalue: editData.svalue,
|
|
ivalue: parseInt(editData.ivalue) || 0,
|
|
fvalue: parseFloat(editData.fvalue) || 0.0,
|
|
svalue2: editData.svalue2,
|
|
memo: editData.memo
|
|
};
|
|
|
|
setLoading(true);
|
|
const response = await fetch('http://127.0.0.1:7979/Common/Save', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.Success) {
|
|
showNotification(result.Message, 'success');
|
|
setShowEditModal(false);
|
|
if (selectedGroupCode) {
|
|
loadDataByGroup(selectedGroupCode);
|
|
}
|
|
} else {
|
|
showNotification(result.Message, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('저장 중 오류 발생:', error);
|
|
showNotification('저장 중 오류가 발생했습니다.', 'error');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const deleteItem = async () => {
|
|
if (!deleteTargetIdx) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
const response = await fetch('http://127.0.0.1:7979/Common/Delete', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ idx: deleteTargetIdx })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.Success) {
|
|
showNotification(data.Message, 'success');
|
|
setShowDeleteModal(false);
|
|
if (selectedGroupCode) {
|
|
loadDataByGroup(selectedGroupCode);
|
|
}
|
|
} else {
|
|
showNotification(data.Message, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('삭제 중 오류 발생:', error);
|
|
showNotification('삭제 중 오류가 발생했습니다.', 'error');
|
|
} finally {
|
|
setLoading(false);
|
|
setDeleteTargetIdx(null);
|
|
}
|
|
};
|
|
|
|
// 이벤트 핸들러들
|
|
const selectGroup = (code, name) => {
|
|
setSelectedGroupCode(code);
|
|
setSelectedGroupName(name);
|
|
loadDataByGroup(code);
|
|
};
|
|
|
|
const openAddModal = () => {
|
|
if (!selectedGroupCode) {
|
|
showNotification('먼저 코드그룹을 선택하세요.', 'warning');
|
|
return;
|
|
}
|
|
|
|
setEditMode('add');
|
|
setEditData({
|
|
idx: '',
|
|
grp: selectedGroupCode,
|
|
code: '',
|
|
svalue: '',
|
|
ivalue: '',
|
|
fvalue: '',
|
|
svalue2: '',
|
|
memo: ''
|
|
});
|
|
setShowEditModal(true);
|
|
};
|
|
|
|
const openEditModal = (item) => {
|
|
setEditMode('edit');
|
|
setEditData({
|
|
idx: item.idx,
|
|
grp: item.grp || '',
|
|
code: item.code || '',
|
|
svalue: item.svalue || '',
|
|
ivalue: item.ivalue || '',
|
|
fvalue: item.fvalue || '',
|
|
svalue2: item.svalue2 || '',
|
|
memo: item.memo || ''
|
|
});
|
|
setShowEditModal(true);
|
|
};
|
|
|
|
const openDeleteModal = (idx) => {
|
|
setDeleteTargetIdx(idx);
|
|
setShowDeleteModal(true);
|
|
setShowEditModal(false);
|
|
};
|
|
|
|
const closeModals = () => {
|
|
setShowEditModal(false);
|
|
setShowDeleteModal(false);
|
|
setDeleteTargetIdx(null);
|
|
};
|
|
|
|
const handleInputChange = (field, value) => {
|
|
setEditData(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const showNotification = (message, type = 'info') => {
|
|
// 기존 알림 제거
|
|
const existing = document.querySelectorAll('.notification-toast');
|
|
existing.forEach(el => el.remove());
|
|
|
|
const colors = {
|
|
info: 'bg-blue-500/90',
|
|
success: 'bg-green-500/90',
|
|
warning: 'bg-yellow-500/90',
|
|
error: 'bg-red-500/90'
|
|
};
|
|
|
|
const icons = {
|
|
info: '🔵',
|
|
success: '✅',
|
|
warning: '⚠️',
|
|
error: '❌'
|
|
};
|
|
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification-toast fixed top-4 right-4 ${colors[type]} backdrop-blur-sm text-white px-4 py-3 rounded-lg z-50 shadow-lg border border-white/20`;
|
|
notification.innerHTML = `<div class="flex items-center"><span class="mr-2">${icons[type]}</span>${message}</div>`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
notification.style.opacity = '0';
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 3000);
|
|
};
|
|
|
|
// 키보드 이벤트
|
|
useEffect(() => {
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === 'Escape') {
|
|
closeModals();
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
|
|
{/* Navigation Component */}
|
|
<CommonNavigation currentPage="common" />
|
|
|
|
<div className="container mx-auto px-4 py-8">
|
|
{/* 2열 구조 메인 컨테이너 */}
|
|
<div className="flex gap-6 h-[calc(100vh-200px)]">
|
|
{/* 좌측: 코드그룹 리스트 */}
|
|
<div className="w-80">
|
|
<div className="glass-effect rounded-2xl h-full card-hover animate-slide-up flex flex-col">
|
|
<div className="p-4 border-b border-white/10">
|
|
<h3 className="text-lg font-semibold text-white flex items-center">
|
|
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 11H5m14-7l-7 7-7-7M19 21l-7-7-7 7"></path>
|
|
</svg>
|
|
코드그룹 목록
|
|
</h3>
|
|
</div>
|
|
<div className="flex-1 overflow-y-auto custom-scrollbar p-2">
|
|
<div className="space-y-1">
|
|
{groupData.length === 0 ? (
|
|
<div className="text-white/70 text-center py-4">그룹 데이터가 없습니다.</div>
|
|
) : (
|
|
groupData.map(group => (
|
|
<div
|
|
key={group.code}
|
|
className={`cursor-pointer p-3 rounded-lg border border-white/20 hover:bg-white/10 transition-all ${
|
|
selectedGroupCode === group.code ? 'bg-white/20' : ''
|
|
}`}
|
|
onClick={() => selectGroup(group.code, group.memo)}
|
|
>
|
|
<div className="flex items-center">
|
|
<div className="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center mr-3">
|
|
<span className="text-white text-sm font-medium">{group.code}</span>
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="text-white font-medium">{group.memo}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 우측: 상세 데이터 */}
|
|
<div className="flex-1">
|
|
<div className="glass-effect rounded-2xl h-full card-hover animate-slide-up flex flex-col">
|
|
{/* 상단 헤더 */}
|
|
<div className="p-4 border-b border-white/10 flex items-center justify-between">
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-white flex items-center">
|
|
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
<span>
|
|
{selectedGroupCode ? `${selectedGroupCode} - ${selectedGroupName}` : '코드그룹을 선택하세요'}
|
|
</span>
|
|
</h3>
|
|
<p className="text-white/70 text-sm mt-1">
|
|
총 <span className="text-white font-medium">{currentData.length}</span>건
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={openAddModal}
|
|
className="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg transition-all border border-white/30 flex items-center text-sm"
|
|
>
|
|
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
추가
|
|
</button>
|
|
</div>
|
|
|
|
{/* 데이터 테이블 */}
|
|
<div className="flex-1 overflow-x-auto overflow-y-auto custom-scrollbar">
|
|
<table className="w-full">
|
|
<thead className="bg-white/10 sticky top-0">
|
|
<tr>
|
|
<th className="w-24 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">코드</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">비고</th>
|
|
<th className="w-32 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(문자열)</th>
|
|
<th className="w-20 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(숫자)</th>
|
|
<th className="w-20 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(실수)</th>
|
|
<th className="w-24 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값2</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-white/10">
|
|
{currentData.length === 0 ? (
|
|
<tr>
|
|
<td colSpan="6" className="px-4 py-8 text-center text-white/70">
|
|
<svg className="w-12 h-12 mx-auto mb-2 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
{selectedGroupCode ? '데이터가 없습니다.' : '좌측에서 코드그룹을 선택하세요'}
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
currentData.map(item => (
|
|
<tr
|
|
key={item.idx}
|
|
className="hover:bg-white/5 transition-colors cursor-pointer"
|
|
onClick={() => openEditModal(item)}
|
|
>
|
|
<td className="px-4 py-4 whitespace-nowrap text-sm text-white">{item.code || '-'}</td>
|
|
<td className="px-4 py-4 text-sm text-white">{item.memo || '-'}</td>
|
|
<td className="px-4 py-4 text-sm text-white max-w-32 truncate" title={item.svalue || '-'}>
|
|
{item.svalue || '-'}
|
|
</td>
|
|
<td className="px-4 py-4 whitespace-nowrap text-sm text-white">{item.ivalue || '0'}</td>
|
|
<td className="px-4 py-4 whitespace-nowrap text-sm text-white">{item.fvalue || '0.0'}</td>
|
|
<td className="px-4 py-4 whitespace-nowrap text-sm text-white">{item.svalue2 || '-'}</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 로딩 인디케이터 */}
|
|
{loading && (
|
|
<div className="fixed top-4 right-4 bg-white/20 backdrop-blur-sm rounded-full px-4 py-2 text-white text-sm z-40">
|
|
<div className="flex items-center">
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
|
데이터 로딩 중...
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 추가/편집 모달 */}
|
|
{showEditModal && (
|
|
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50">
|
|
<div className="flex items-center justify-center min-h-screen p-4">
|
|
<div className="glass-effect rounded-2xl w-full max-w-2xl animate-slide-up">
|
|
{/* 모달 헤더 */}
|
|
<div className="px-6 py-4 border-b border-white/10 flex items-center justify-between">
|
|
<h2 className="text-xl font-semibold text-white">
|
|
{editMode === 'add' ? '공용코드 추가' : '공용코드 편집'}
|
|
</h2>
|
|
<button
|
|
onClick={closeModals}
|
|
className="text-white/70 hover:text-white transition-colors"
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* 모달 내용 */}
|
|
<div className="p-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-white/70 mb-2">코드그룹 *</label>
|
|
<select
|
|
value={editData.grp}
|
|
onChange={(e) => handleInputChange('grp', e.target.value)}
|
|
required
|
|
className="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
|
>
|
|
<option value="" className="bg-gray-800 text-white">선택하세요</option>
|
|
{groupData.map(group => (
|
|
<option key={group.code} value={group.code} className="bg-gray-800 text-white">
|
|
{group.code}-{group.memo}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-white/70 mb-2">코드 *</label>
|
|
<input
|
|
type="text"
|
|
value={editData.code}
|
|
onChange={(e) => handleInputChange('code', e.target.value)}
|
|
required
|
|
className="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
|
placeholder="코드를 입력하세요"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-white/70 mb-2">값(문자열)</label>
|
|
<input
|
|
type="text"
|
|
value={editData.svalue}
|
|
onChange={(e) => handleInputChange('svalue', e.target.value)}
|
|
className="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
|
placeholder="문자열 값"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-white/70 mb-2">값(숫자)</label>
|
|
<input
|
|
type="number"
|
|
value={editData.ivalue}
|
|
onChange={(e) => handleInputChange('ivalue', e.target.value)}
|
|
className="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
|
placeholder="숫자 값"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-white/70 mb-2">값(실수)</label>
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
value={editData.fvalue}
|
|
onChange={(e) => handleInputChange('fvalue', e.target.value)}
|
|
className="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
|
placeholder="실수 값"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-white/70 mb-2">값2</label>
|
|
<input
|
|
type="text"
|
|
value={editData.svalue2}
|
|
onChange={(e) => handleInputChange('svalue2', e.target.value)}
|
|
className="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
|
placeholder="추가 문자열 값"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4">
|
|
<label className="block text-sm font-medium text-white/70 mb-2">비고</label>
|
|
<textarea
|
|
value={editData.memo}
|
|
onChange={(e) => handleInputChange('memo', e.target.value)}
|
|
rows="3"
|
|
className="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
|
placeholder="비고사항을 입력하세요"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 모달 푸터 */}
|
|
<div className="px-6 py-4 border-t border-white/10 flex justify-between">
|
|
{editMode === 'edit' && (
|
|
<button
|
|
onClick={() => openDeleteModal(editData.idx)}
|
|
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center"
|
|
>
|
|
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
</svg>
|
|
삭제
|
|
</button>
|
|
)}
|
|
<div className="flex gap-2 ml-auto">
|
|
<button
|
|
onClick={closeModals}
|
|
className="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors"
|
|
>
|
|
취소
|
|
</button>
|
|
<button
|
|
onClick={saveData}
|
|
className="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors"
|
|
>
|
|
저장
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 삭제 확인 모달 */}
|
|
{showDeleteModal && (
|
|
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50">
|
|
<div className="flex items-center justify-center min-h-screen p-4">
|
|
<div className="glass-effect rounded-2xl w-full max-w-md animate-slide-up">
|
|
<div className="p-6">
|
|
<div className="flex items-center mb-4">
|
|
<div className="w-12 h-12 bg-red-100/20 rounded-full flex items-center justify-center mr-4">
|
|
<svg className="w-6 h-6 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 18.5c-.77.833.192 2.5 1.732 2.5z"></path>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-medium text-white">삭제 확인</h3>
|
|
<p className="text-sm text-white/70">이 작업은 되돌릴 수 없습니다.</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-white/80 mb-6">
|
|
선택한 공용코드를 삭제하시겠습니까?<br/>
|
|
<span className="text-sm text-white/60">이 작업은 되돌릴 수 없습니다.</span>
|
|
</p>
|
|
<div className="flex justify-end gap-2">
|
|
<button
|
|
onClick={closeModals}
|
|
className="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors"
|
|
>
|
|
취소
|
|
</button>
|
|
<button
|
|
onClick={deleteItem}
|
|
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg transition-colors"
|
|
>
|
|
삭제
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}; |