feat: MachineBridge 추가 및 fetch API를 HostObject 호출로 전환
- WebView2 HostObject 기반 MachineBridge 브릿지 클래스 추가 - MachineBridge.cs (메인), Login, Dashboard, Todo, Common, Jobreport, Kuntae, Project 모듈 - WebSocketServer.cs 추가 (실시간 통신용) - fDashboardNew 다이얼로그 추가 - Jobreport/index.html, Project/index.html의 fetch API를 machine HostObject 호출로 전환 - DashBoardController.cs의 gcode null 처리 추가 - 사용하지 않는 파일 삭제 (navigation.html, common-nav.js, navigation.js, _add_to_project.py, _project_updater.js) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -462,151 +462,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 공통 네비게이션 -->
|
||||
<script src="/js/common-navigation.js"></script>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 공통 네비게이션 컴포넌트
|
||||
*/
|
||||
class CommonNavigation {
|
||||
constructor(currentPage = '') {
|
||||
this.currentPage = currentPage;
|
||||
this.menuItems = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.loadMenuItems();
|
||||
this.createNavigation();
|
||||
this.addEventListeners();
|
||||
} catch (error) {
|
||||
console.error('Navigation initialization failed:', error);
|
||||
this.createFallbackNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
async loadMenuItems() {
|
||||
try {
|
||||
const response = await fetch('/Common/GetNavigationMenu');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
if (data.Success && data.Data) {
|
||||
this.menuItems = data.Data;
|
||||
} else {
|
||||
throw new Error(data.Message || 'Failed to load menu items');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load navigation menu:', error);
|
||||
this.menuItems = this.getDefaultMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultMenuItems() {
|
||||
return [
|
||||
{ key: 'dashboard', title: '대시보드', url: '/Dashboard/', icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z', isVisible: true, sortOrder: 1 },
|
||||
{ key: 'common', title: '공용코드', url: '/Common', icon: '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', isVisible: true, sortOrder: 2 },
|
||||
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2', isVisible: true, sortOrder: 3 },
|
||||
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z', isVisible: true, sortOrder: 4 },
|
||||
{ key: 'todo', title: '할일관리', url: '/Todo/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4', isVisible: true, sortOrder: 5 }
|
||||
];
|
||||
}
|
||||
|
||||
createNavigation() {
|
||||
const nav = document.createElement('nav');
|
||||
nav.className = 'glass-effect border-b border-white/10';
|
||||
nav.innerHTML = this.getNavigationHTML();
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
}
|
||||
|
||||
createFallbackNavigation() {
|
||||
this.createNavigation();
|
||||
}
|
||||
|
||||
getNavigationHTML() {
|
||||
const visibleItems = this.menuItems.filter(item => item.isVisible).sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
return `
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center space-x-8">
|
||||
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
|
||||
</svg>
|
||||
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||
</a>
|
||||
<nav class="hidden md:flex space-x-1">
|
||||
${visibleItems.map(item => `
|
||||
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
this.currentPage === item.key
|
||||
? 'bg-white/20 text-white'
|
||||
: 'text-white/60 hover:text-white hover:bg-white/10'
|
||||
}">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||
</svg>
|
||||
${item.title}
|
||||
</a>
|
||||
`).join('')}
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-sm text-white/60">
|
||||
<span id="currentUser">사용자</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getMenuItemHTML(item) {
|
||||
const isActive = this.currentPage === item.key;
|
||||
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
|
||||
return `
|
||||
<a href="${item.url}" class="${activeClass} transition-colors px-3 py-2 rounded-lg">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||
</svg>
|
||||
${item.title}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
getMobileMenuItemHTML(item) {
|
||||
const isActive = this.currentPage === item.key;
|
||||
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
|
||||
return `
|
||||
<a href="${item.url}" class="block ${activeClass} transition-colors px-3 py-2 rounded-lg mb-2">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||
</svg>
|
||||
${item.title}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
if (mobileMenuButton && mobileMenu) {
|
||||
mobileMenuButton.addEventListener('click', function() {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initNavigation(currentPage = '') {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new CommonNavigation(currentPage);
|
||||
});
|
||||
} else {
|
||||
new CommonNavigation(currentPage);
|
||||
}
|
||||
}
|
||||
// Machine HostObject 초기화
|
||||
const machine = window.chrome.webview.hostObjects.machine;
|
||||
|
||||
// 전역 변수
|
||||
let jobData = [];
|
||||
@@ -743,46 +604,37 @@
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
const selectedUser = document.getElementById('userFilter').value;
|
||||
|
||||
// API URL 구성 - 새로운 GetJobData API 사용
|
||||
let url = '/Jobreport/GetJobData';
|
||||
const params = new URLSearchParams();
|
||||
if (startDate) params.append('startDate', startDate);
|
||||
if (endDate) params.append('endDate', endDate);
|
||||
if (selectedUser) params.append('user', selectedUser);
|
||||
|
||||
if (params.toString()) {
|
||||
url += '?' + params.toString();
|
||||
}
|
||||
|
||||
console.log('Fetching data from:', url);
|
||||
const response = await fetch(url);
|
||||
console.log('Response status:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: 데이터를 불러오는데 실패했습니다.`);
|
||||
}
|
||||
|
||||
// JSON 응답 직접 파싱
|
||||
const responseText = await response.text();
|
||||
console.log('Raw response:', responseText);
|
||||
|
||||
responseData = JSON.parse(responseText);
|
||||
|
||||
console.log('Loading job data with params:', { startDate, endDate, selectedUser });
|
||||
|
||||
// machine.Jobreport_GetList(sd, ed, uid, cate, doit) 호출
|
||||
const jsonStr = await machine.Jobreport_GetList(
|
||||
startDate || '',
|
||||
endDate || '',
|
||||
selectedUser || '',
|
||||
'', // cate (빈 값)
|
||||
'' // doit (빈 값)
|
||||
);
|
||||
|
||||
responseData = JSON.parse(jsonStr);
|
||||
console.log('Parsed response:', responseData);
|
||||
|
||||
// 응답 데이터가 배열인지 확인
|
||||
if (Array.isArray(responseData)) {
|
||||
jobData = responseData;
|
||||
|
||||
// MachineBridge 응답 형식: { Success: true, Data: [...] }
|
||||
if (responseData.Success && Array.isArray(responseData.Data)) {
|
||||
jobData = responseData.Data;
|
||||
} else if (responseData.error) {
|
||||
throw new Error(responseData.error);
|
||||
} else if (Array.isArray(responseData)) {
|
||||
// 하위 호환성: 배열이 직접 반환되는 경우
|
||||
jobData = responseData;
|
||||
} else {
|
||||
// 데이터가 배열이 아닌 경우 빈 배열로 초기화
|
||||
console.warn('Response is not an array, initializing empty array');
|
||||
// 데이터가 없는 경우 빈 배열로 초기화
|
||||
console.warn('Response has no data, initializing empty array');
|
||||
jobData = [];
|
||||
}
|
||||
|
||||
|
||||
filteredData = [...jobData];
|
||||
|
||||
|
||||
updateStatistics();
|
||||
updateProjectFilter();
|
||||
sortData();
|
||||
@@ -867,7 +719,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadUserList() {
|
||||
async function loadUserList() {
|
||||
const userSelect = document.getElementById('userFilter');
|
||||
|
||||
// 기존 옵션 제거 (전체 옵션 제외)
|
||||
@@ -875,74 +727,72 @@
|
||||
userSelect.removeChild(userSelect.lastChild);
|
||||
}
|
||||
|
||||
// 서버에서 사용자 목록 가져오기
|
||||
fetch('/Jobreport/GetUsers')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: 사용자 목록을 불러오는데 실패했습니다.`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('사용자 목록 데이터:', data);
|
||||
if (data && Array.isArray(data)) {
|
||||
data.forEach(user => {
|
||||
const option = document.createElement('option');
|
||||
option.value = user.id;
|
||||
option.textContent = `${user.name} [${user.id}] ${user.process}`;
|
||||
userSelect.appendChild(option);
|
||||
});
|
||||
console.log('사용자 목록 로드 완료:', userSelect.children.length - 1, '명');
|
||||
} else {
|
||||
console.warn('사용자 목록 데이터가 배열이 아닙니다:', data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('사용자 목록 로드 중 오류:', error);
|
||||
// 에러 시 기본 사용자 옵션 추가
|
||||
const option = document.createElement('option');
|
||||
option.value = FCOMMON?.info?.Login?.no || '';
|
||||
option.textContent = '현재 사용자';
|
||||
userSelect.appendChild(option);
|
||||
});
|
||||
try {
|
||||
// machine.Jobreport_GetUsers() 호출
|
||||
const jsonStr = await machine.Jobreport_GetUsers();
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
console.log('사용자 목록 데이터:', data);
|
||||
|
||||
// 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
|
||||
const users = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
|
||||
|
||||
if (users.length > 0) {
|
||||
users.forEach(user => {
|
||||
const option = document.createElement('option');
|
||||
option.value = user.id;
|
||||
option.textContent = `${user.name} [${user.id}] ${user.process}`;
|
||||
userSelect.appendChild(option);
|
||||
});
|
||||
console.log('사용자 목록 로드 완료:', userSelect.children.length - 1, '명');
|
||||
} else {
|
||||
console.warn('사용자 목록 데이터가 없습니다:', data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('사용자 목록 로드 중 오류:', error);
|
||||
// 에러 시 기본 사용자 옵션 추가
|
||||
const option = document.createElement('option');
|
||||
option.value = FCOMMON?.info?.Login?.no || '';
|
||||
option.textContent = '현재 사용자';
|
||||
userSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
// 공용코드 12번(상태코드) 로드
|
||||
function loadStatusCodes() {
|
||||
fetch('/Common/GetList?grp=12')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: 상태코드를 불러오는데 실패했습니다.`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('상태코드 원본 데이터:', data);
|
||||
if (data && Array.isArray(data)) {
|
||||
statusCodes = data;
|
||||
console.log('상태코드 로드 완료:', statusCodes.length, '개');
|
||||
console.log('첫번째 상태코드 샘플:', statusCodes[0]);
|
||||
} else {
|
||||
console.warn('상태코드 데이터가 배열이 아닙니다:', data);
|
||||
// 기본 상태코드 설정
|
||||
statusCodes = [
|
||||
{ svalue: '진행 중' },
|
||||
{ svalue: '완료' },
|
||||
{ svalue: '대기' },
|
||||
{ svalue: '보류' }
|
||||
];
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('상태코드 로드 중 오류:', error);
|
||||
// 에러 시 기본 상태코드 사용
|
||||
async function loadStatusCodes() {
|
||||
try {
|
||||
const jsonStr = await machine.Common_GetList('12');
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
console.log('상태코드 원본 데이터:', data);
|
||||
|
||||
// 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
|
||||
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
|
||||
|
||||
if (codes.length > 0) {
|
||||
statusCodes = codes;
|
||||
console.log('상태코드 로드 완료:', statusCodes.length, '개');
|
||||
console.log('첫번째 상태코드 샘플:', statusCodes[0]);
|
||||
} else {
|
||||
console.warn('상태코드 데이터가 없습니다:', data);
|
||||
// 기본 상태코드 설정
|
||||
statusCodes = [
|
||||
{ svalue: '진행 중' },
|
||||
{ svalue: '완료' },
|
||||
{ svalue: '대기' },
|
||||
{ svalue: '보류' }
|
||||
];
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('상태코드 로드 중 오류:', error);
|
||||
// 에러 시 기본 상태코드 사용
|
||||
statusCodes = [
|
||||
{ svalue: '진행 중' },
|
||||
{ svalue: '완료' },
|
||||
{ svalue: '대기' },
|
||||
{ svalue: '보류' }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 상태코드 드롭다운 옵션 생성 헬퍼 함수
|
||||
@@ -962,78 +812,95 @@
|
||||
}
|
||||
|
||||
// 공용코드 13번(요청부서) 로드
|
||||
function loadRequestDeptCodes() {
|
||||
fetch('/Common/GetList?grp=13')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && Array.isArray(data)) {
|
||||
requestDeptCodes = data;
|
||||
console.log('요청부서 코드 로드 완료:', requestDeptCodes.length, '개');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('요청부서 코드 로드 중 오류:', error);
|
||||
async function loadRequestDeptCodes() {
|
||||
try {
|
||||
const jsonStr = await machine.Common_GetList('13');
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
// 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
|
||||
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
|
||||
|
||||
if (codes.length > 0) {
|
||||
requestDeptCodes = codes;
|
||||
console.log('요청부서 코드 로드 완료:', requestDeptCodes.length, '개');
|
||||
} else {
|
||||
requestDeptCodes = [];
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('요청부서 코드 로드 중 오류:', error);
|
||||
requestDeptCodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 공용코드 14번(패키지) 로드
|
||||
function loadPackageCodes() {
|
||||
fetch('/Common/GetList?grp=14')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && Array.isArray(data)) {
|
||||
packageCodes = data;
|
||||
console.log('패키지 코드 로드 완료:', packageCodes.length, '개');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('패키지 코드 로드 중 오류:', error);
|
||||
async function loadPackageCodes() {
|
||||
try {
|
||||
const jsonStr = await machine.Common_GetList('14');
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
// 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
|
||||
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
|
||||
|
||||
if (codes.length > 0) {
|
||||
packageCodes = codes;
|
||||
console.log('패키지 코드 로드 완료:', packageCodes.length, '개');
|
||||
} else {
|
||||
packageCodes = [];
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('패키지 코드 로드 중 오류:', error);
|
||||
packageCodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 공용코드 15번(업무형태 - 트리구조) 로드
|
||||
function loadJobTypeCodes() {
|
||||
fetch('/Common/GetList?grp=15')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('업무형태 원본 데이터:', data);
|
||||
if (data && Array.isArray(data)) {
|
||||
jobTypeCodes = data;
|
||||
console.log('업무형태 코드 로드 완료:', jobTypeCodes.length, '개');
|
||||
if (jobTypeCodes.length > 0) {
|
||||
console.log('업무형태 첫번째 샘플:', jobTypeCodes[0]);
|
||||
console.log('업무형태 데이터 구조 확인:');
|
||||
console.log(' - svalue2 (프로세스):', jobTypeCodes[0].svalue2);
|
||||
console.log(' - svalue (분류):', jobTypeCodes[0].svalue);
|
||||
console.log(' - memo (항목):', jobTypeCodes[0].memo);
|
||||
}
|
||||
} else {
|
||||
console.warn('업무형태 데이터가 배열이 아닙니다:', data);
|
||||
jobTypeCodes = [];
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('업무형태 코드 로드 중 오류:', error);
|
||||
async function loadJobTypeCodes() {
|
||||
try {
|
||||
const jsonStr = await machine.Common_GetList('15');
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
console.log('업무형태 원본 데이터:', data);
|
||||
|
||||
// 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
|
||||
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
|
||||
|
||||
if (codes.length > 0) {
|
||||
jobTypeCodes = codes;
|
||||
console.log('업무형태 코드 로드 완료:', jobTypeCodes.length, '개');
|
||||
console.log('업무형태 첫번째 샘플:', jobTypeCodes[0]);
|
||||
console.log('업무형태 데이터 구조 확인:');
|
||||
console.log(' - svalue2 (프로세스):', jobTypeCodes[0].svalue2);
|
||||
console.log(' - svalue (분류):', jobTypeCodes[0].svalue);
|
||||
console.log(' - memo (항목):', jobTypeCodes[0].memo);
|
||||
} else {
|
||||
console.warn('업무형태 데이터가 없습니다:', data);
|
||||
jobTypeCodes = [];
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('업무형태 코드 로드 중 오류:', error);
|
||||
jobTypeCodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 공용코드 16번(프로세스) 로드
|
||||
function loadProcessCodes() {
|
||||
fetch('/Common/GetList?grp=16')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && Array.isArray(data)) {
|
||||
processCodes = data;
|
||||
console.log('프로세스 코드 로드 완료:', processCodes.length, '개');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('프로세스 코드 로드 중 오류:', error);
|
||||
async function loadProcessCodes() {
|
||||
try {
|
||||
const jsonStr = await machine.Common_GetList('16');
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
// 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
|
||||
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
|
||||
|
||||
if (codes.length > 0) {
|
||||
processCodes = codes;
|
||||
console.log('프로세스 코드 로드 완료:', processCodes.length, '개');
|
||||
} else {
|
||||
processCodes = [];
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('프로세스 코드 로드 중 오류:', error);
|
||||
processCodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 드롭다운 옵션 생성 헬퍼 함수들
|
||||
@@ -1392,15 +1259,20 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 전체 상세 정보를 서버에서 가져오기
|
||||
try {
|
||||
const response = await fetch(`/Jobreport/GetJobDetail?id=${item.idx}`);
|
||||
if (response.ok) {
|
||||
const fullItem = await response.json();
|
||||
// 전체 정보가 있으면 사용, 없으면 기존 item 사용
|
||||
item = fullItem.error ? item : fullItem;
|
||||
const jsonStr = await machine.Jobreport_GetDetail(item.idx);
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
// 응답 형식 처리: { Success: true, Data: {...} } 또는 객체 직접
|
||||
if (data.Success && data.Data) {
|
||||
item = data.Data;
|
||||
} else if (!data.error && typeof data === 'object') {
|
||||
// 하위 호환성: 객체가 직접 반환되는 경우
|
||||
item = data;
|
||||
}
|
||||
// error가 있으면 기존 item 유지
|
||||
} catch (error) {
|
||||
console.warn('Failed to load full details, using truncated data:', error);
|
||||
}
|
||||
@@ -1679,57 +1551,37 @@
|
||||
|
||||
async function handleAddSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// URLSearchParams 사용 (application/x-www-form-urlencoded)
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('pdate', document.getElementById('editPdate').value);
|
||||
formData.append('status', document.getElementById('editStatus').value);
|
||||
formData.append('projectName', document.getElementById('editProjectName').value);
|
||||
formData.append('requestpart', document.getElementById('editRequestpart').value);
|
||||
formData.append('package', document.getElementById('editPackage').value);
|
||||
formData.append('jobprocess', document.getElementById('editJobProcess').value);
|
||||
formData.append('jobtype', document.getElementById('editJobType').value);
|
||||
formData.append('jobgrp', document.getElementById('editJobGrp').value);
|
||||
formData.append('jobprocess2', document.getElementById('editJobProcess2').value);
|
||||
formData.append('hrs', document.getElementById('editHrs').value);
|
||||
formData.append('ot', document.getElementById('editOt').value);
|
||||
formData.append('otStart', document.getElementById('editOtStart').value);
|
||||
formData.append('otEnd', document.getElementById('editOtEnd').value);
|
||||
formData.append('description', document.getElementById('editDescription').value);
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch('/Jobreport/Add', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: 추가에 실패했습니다.`);
|
||||
}
|
||||
|
||||
const result = await response.text();
|
||||
// 폼 데이터 수집
|
||||
const jdate = document.getElementById('editPdate').value;
|
||||
const cate = document.getElementById('editJobType').value || '';
|
||||
const title = document.getElementById('editProjectName').value;
|
||||
const doit = document.getElementById('editDescription').value;
|
||||
const remark = document.getElementById('editStatus').value || '';
|
||||
const jfrom = document.getElementById('editOtStart').value || '';
|
||||
const jto = document.getElementById('editOtEnd').value || '';
|
||||
|
||||
console.log('Adding job with params:', { jdate, cate, title, doit, remark, jfrom, jto });
|
||||
|
||||
// machine.Jobreport_Add(jdate, cate, title, doit, remark, jfrom, jto) 호출
|
||||
const jsonStr = await machine.Jobreport_Add(jdate, cate, title, doit, remark, jfrom, jto);
|
||||
const result = JSON.parse(jsonStr);
|
||||
|
||||
console.log('Add result:', result);
|
||||
|
||||
// JSON 응답인지 확인
|
||||
try {
|
||||
const jsonResult = JSON.parse(result);
|
||||
if (!jsonResult.success) {
|
||||
throw new Error(jsonResult.message || '추가에 실패했습니다.');
|
||||
}
|
||||
// 성공시 alert 없이 바로 진행
|
||||
} catch (parseError) {
|
||||
// JSON이 아닌 경우도 성공으로 처리 (alert 없음)
|
||||
|
||||
// 응답 형식 처리: { Success: true } 또는 { success: true }
|
||||
if (result.Success === false || result.success === false) {
|
||||
throw new Error(result.Message || result.message || '추가에 실패했습니다.');
|
||||
}
|
||||
|
||||
|
||||
// 성공시 alert 없이 바로 진행
|
||||
// 모달 닫기
|
||||
closeModal();
|
||||
|
||||
|
||||
// 데이터 새로고침
|
||||
await loadJobData();
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error adding job:', error);
|
||||
alert('업무일지 추가 중 오류가 발생했습니다: ' + error.message);
|
||||
@@ -1738,58 +1590,38 @@
|
||||
|
||||
async function handleEditSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// URLSearchParams 사용 (application/x-www-form-urlencoded)
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('idx', document.getElementById('editIdx').value);
|
||||
formData.append('pdate', document.getElementById('editPdate').value);
|
||||
formData.append('status', document.getElementById('editStatus').value);
|
||||
formData.append('projectName', document.getElementById('editProjectName').value);
|
||||
formData.append('requestpart', document.getElementById('editRequestpart').value);
|
||||
formData.append('package', document.getElementById('editPackage').value);
|
||||
formData.append('jobprocess', document.getElementById('editJobProcess').value);
|
||||
formData.append('jobtype', document.getElementById('editJobType').value);
|
||||
formData.append('jobgrp', document.getElementById('editJobGrp').value);
|
||||
formData.append('jobprocess2', document.getElementById('editJobProcess2').value);
|
||||
formData.append('hrs', document.getElementById('editHrs').value);
|
||||
formData.append('ot', document.getElementById('editOt').value);
|
||||
formData.append('otStart', document.getElementById('editOtStart').value);
|
||||
formData.append('otEnd', document.getElementById('editOtEnd').value);
|
||||
formData.append('description', document.getElementById('editDescription').value);
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch('/Jobreport/Edit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: 업데이트에 실패했습니다.`);
|
||||
}
|
||||
|
||||
const result = await response.text();
|
||||
// 폼 데이터 수집
|
||||
const idx = document.getElementById('editIdx').value;
|
||||
const jdate = document.getElementById('editPdate').value;
|
||||
const cate = document.getElementById('editJobType').value || '';
|
||||
const title = document.getElementById('editProjectName').value;
|
||||
const doit = document.getElementById('editDescription').value;
|
||||
const remark = document.getElementById('editStatus').value || '';
|
||||
const jfrom = document.getElementById('editOtStart').value || '';
|
||||
const jto = document.getElementById('editOtEnd').value || '';
|
||||
|
||||
console.log('Editing job with params:', { idx, jdate, cate, title, doit, remark, jfrom, jto });
|
||||
|
||||
// machine.Jobreport_Edit(idx, jdate, cate, title, doit, remark, jfrom, jto) 호출
|
||||
const jsonStr = await machine.Jobreport_Edit(idx, jdate, cate, title, doit, remark, jfrom, jto);
|
||||
const result = JSON.parse(jsonStr);
|
||||
|
||||
console.log('Edit result:', result);
|
||||
|
||||
// JSON 응답인지 확인
|
||||
try {
|
||||
const jsonResult = JSON.parse(result);
|
||||
if (!jsonResult.success) {
|
||||
throw new Error(jsonResult.message || '수정에 실패했습니다.');
|
||||
}
|
||||
// 성공시 alert 없이 바로 진행
|
||||
} catch (parseError) {
|
||||
// JSON이 아닌 경우도 성공으로 처리 (alert 없음)
|
||||
|
||||
// 응답 형식 처리: { Success: true } 또는 { success: true }
|
||||
if (result.Success === false || result.success === false) {
|
||||
throw new Error(result.Message || result.message || '수정에 실패했습니다.');
|
||||
}
|
||||
|
||||
|
||||
// 성공시 alert 없이 바로 진행
|
||||
// 모달 닫기
|
||||
closeModal();
|
||||
|
||||
|
||||
// 데이터 새로고침
|
||||
await loadJobData();
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating job:', error);
|
||||
alert('업무일지 수정 중 오류가 발생했습니다: ' + error.message);
|
||||
@@ -1798,35 +1630,34 @@
|
||||
|
||||
async function handleDeleteJob() {
|
||||
const idx = document.getElementById('editIdx').value;
|
||||
|
||||
|
||||
if (!idx) {
|
||||
alert('삭제할 수 없는 항목입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!confirm('정말로 이 업무일지를 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/Jobreport/Delete/${idx}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('삭제에 실패했습니다.');
|
||||
console.log('Deleting job with idx:', idx);
|
||||
|
||||
// machine.Jobreport_Delete(idx) 호출
|
||||
const jsonStr = await machine.Jobreport_Delete(idx);
|
||||
const result = JSON.parse(jsonStr);
|
||||
|
||||
console.log('Delete result:', result);
|
||||
|
||||
// 응답 형식 처리: { Success: true } 또는 { success: true }
|
||||
if (result.Success === false || result.success === false) {
|
||||
throw new Error(result.Message || result.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// 성공시 alert 없이 바로 모달 닫기
|
||||
closeModal();
|
||||
await loadJobData();
|
||||
} else {
|
||||
throw new Error(result.message || '삭제에 실패했습니다.');
|
||||
}
|
||||
|
||||
|
||||
// 성공시 alert 없이 바로 모달 닫기
|
||||
closeModal();
|
||||
await loadJobData();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting job:', error);
|
||||
alert('업무일지 삭제 중 오류가 발생했습니다: ' + error.message);
|
||||
|
||||
@@ -473,6 +473,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 공통 네비게이션 -->
|
||||
<script src="/js/common-navigation.js"></script>
|
||||
|
||||
<script>
|
||||
let projects = [];
|
||||
let currentProjectIdx = null;
|
||||
@@ -483,109 +486,14 @@
|
||||
let itemsPerPage = 10;
|
||||
let filteredProjects = [];
|
||||
|
||||
// machine HostObject 참조
|
||||
const machine = window.chrome.webview.hostObjects.machine;
|
||||
|
||||
// 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeApp();
|
||||
});
|
||||
|
||||
// 네비게이션 클래스
|
||||
class CommonNavigation {
|
||||
constructor(currentPage = '') {
|
||||
this.currentPage = currentPage;
|
||||
this.createNavigation();
|
||||
}
|
||||
|
||||
async createNavigation() {
|
||||
try {
|
||||
const response = await fetch('/Common/GetNavigationMenu');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.Success && data.Data) {
|
||||
this.createNavigationFromData(data.Data);
|
||||
} else {
|
||||
this.createFallbackNavigation();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Navigation initialization failed:', error);
|
||||
this.createFallbackNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
createNavigationFromData(menuItems) {
|
||||
const nav = document.createElement('nav');
|
||||
nav.className = 'glass-effect border-b border-white/10';
|
||||
nav.innerHTML = this.getNavigationHTML(menuItems);
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
}
|
||||
|
||||
createFallbackNavigation() {
|
||||
this.createNavigation();
|
||||
}
|
||||
|
||||
createNavigation() {
|
||||
const nav = document.createElement('nav');
|
||||
nav.className = 'glass-effect border-b border-white/10';
|
||||
nav.innerHTML = this.getNavigationHTML();
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
}
|
||||
|
||||
getNavigationHTML(menuItems = null) {
|
||||
if (!menuItems) {
|
||||
menuItems = [
|
||||
{ key: 'dashboard', title: '대시보드', url: '/Dashboard/', icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z' },
|
||||
{ key: 'common', title: '공용코드', url: '/Common', icon: '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' },
|
||||
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' },
|
||||
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z' },
|
||||
{ key: 'todo', title: '할일관리', url: '/Todo/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4' },
|
||||
{ key: 'project', title: '프로젝트', url: '/Project/', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10' }
|
||||
];
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center space-x-8">
|
||||
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||
</a>
|
||||
<nav class="hidden md:flex space-x-1">
|
||||
${menuItems.map(item => `
|
||||
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
this.currentPage === item.key
|
||||
? 'bg-white/20 text-white'
|
||||
: 'text-white/60 hover:text-white hover:bg-white/10'
|
||||
}">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||
</svg>
|
||||
${item.title}
|
||||
</a>
|
||||
`).join('')}
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-sm text-white/60">
|
||||
<span id="currentUser">사용자</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function initNavigation(currentPage = '') {
|
||||
try {
|
||||
new CommonNavigation(currentPage);
|
||||
} catch (error) {
|
||||
console.error('Navigation initialization failed:', error);
|
||||
new CommonNavigation(currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeApp() {
|
||||
initNavigation('project');
|
||||
getCurrentUser();
|
||||
@@ -593,24 +501,24 @@
|
||||
setupEventListeners();
|
||||
}
|
||||
|
||||
function getCurrentUser() {
|
||||
async function getCurrentUser() {
|
||||
// 현재 로그인된 사용자 정보 가져오기
|
||||
fetch('/Common/GetCurrentUser')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.Success && data.Data) {
|
||||
currentUser = data.Data.userName || data.Data.name || '사용자';
|
||||
document.getElementById('currentUser').textContent = currentUser;
|
||||
} else {
|
||||
currentUser = '사용자';
|
||||
document.getElementById('currentUser').textContent = currentUser;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error getting current user:', error);
|
||||
try {
|
||||
const jsonStr = await machine.GetCurrentUser();
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
if (data.Success && data.Data) {
|
||||
currentUser = data.Data.userName || data.Data.name || '사용자';
|
||||
document.getElementById('currentUser').textContent = currentUser;
|
||||
} else {
|
||||
currentUser = '사용자';
|
||||
document.getElementById('currentUser').textContent = currentUser;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting current user:', error);
|
||||
currentUser = '사용자';
|
||||
document.getElementById('currentUser').textContent = currentUser;
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
@@ -648,31 +556,31 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadProjects() {
|
||||
async function loadProjects() {
|
||||
const status = document.getElementById('statusFilter').value;
|
||||
const userFilter = document.getElementById('managerFilter').value;
|
||||
|
||||
fetch(`/Project/GetProjects?status=${status}&userFilter=${userFilter}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.Success) {
|
||||
console.error('Error:', data.Message);
|
||||
document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>';
|
||||
return;
|
||||
}
|
||||
projects = data.Data || [];
|
||||
if (data.CurrentUser) {
|
||||
currentUser = data.CurrentUser;
|
||||
document.getElementById('currentUser').textContent = currentUser;
|
||||
}
|
||||
currentPage = 1;
|
||||
filterData();
|
||||
updateStatusCounts();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
|
||||
try {
|
||||
const jsonStr = await machine.Project_GetProjects(status, userFilter);
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
if (!data.Success) {
|
||||
console.error('Error:', data.Message);
|
||||
document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>';
|
||||
});
|
||||
return;
|
||||
}
|
||||
projects = data.Data || [];
|
||||
if (data.CurrentUser) {
|
||||
currentUser = data.CurrentUser;
|
||||
document.getElementById('currentUser').textContent = currentUser;
|
||||
}
|
||||
currentPage = 1;
|
||||
filterData();
|
||||
updateStatusCounts();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatusCounts() {
|
||||
@@ -830,37 +738,37 @@
|
||||
document.getElementById('projectModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function editProject(idx) {
|
||||
async function editProject(idx) {
|
||||
currentProjectIdx = idx;
|
||||
|
||||
fetch(`/Project/GetProject?id=${idx}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.Success) {
|
||||
alert('프로젝트 정보를 불러오는데 실패했습니다: ' + data.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
const project = data.Data;
|
||||
document.getElementById('modalTitle').textContent = '프로젝트 편집';
|
||||
document.getElementById('projectIdx').value = project.idx;
|
||||
document.getElementById('projectName').value = project.프로젝트명 || '';
|
||||
document.getElementById('projectProcess').value = project.프로젝트공정 || '';
|
||||
document.getElementById('projectSdate').value = project.시작일 || '';
|
||||
document.getElementById('projectEdate').value = project.완료일 || '';
|
||||
document.getElementById('projectDdate').value = project.만료일 || '';
|
||||
document.getElementById('projectOdate').value = project.출고일 || '';
|
||||
document.getElementById('projectUserManager').value = project.프로젝트관리자 || '';
|
||||
document.getElementById('projectStatus').value = project.상태 || '진행';
|
||||
document.getElementById('projectMemo').value = project.memo || '';
|
||||
|
||||
document.getElementById('deleteProjectBtn').classList.remove('hidden');
|
||||
document.getElementById('projectModal').classList.remove('hidden');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('프로젝트 정보를 불러오는데 실패했습니다.');
|
||||
});
|
||||
|
||||
try {
|
||||
const jsonStr = await machine.Project_GetProject(idx);
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
if (!data.Success) {
|
||||
alert('프로젝트 정보를 불러오는데 실패했습니다: ' + data.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
const project = data.Data;
|
||||
document.getElementById('modalTitle').textContent = '프로젝트 편집';
|
||||
document.getElementById('projectIdx').value = project.idx;
|
||||
document.getElementById('projectName').value = project.프로젝트명 || '';
|
||||
document.getElementById('projectProcess').value = project.프로젝트공정 || '';
|
||||
document.getElementById('projectSdate').value = project.시작일 || '';
|
||||
document.getElementById('projectEdate').value = project.완료일 || '';
|
||||
document.getElementById('projectDdate').value = project.만료일 || '';
|
||||
document.getElementById('projectOdate').value = project.출고일 || '';
|
||||
document.getElementById('projectUserManager').value = project.프로젝트관리자 || '';
|
||||
document.getElementById('projectStatus').value = project.상태 || '진행';
|
||||
document.getElementById('projectMemo').value = project.memo || '';
|
||||
|
||||
document.getElementById('deleteProjectBtn').classList.remove('hidden');
|
||||
document.getElementById('projectModal').classList.remove('hidden');
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('프로젝트 정보를 불러오는데 실패했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
function closeProjectModal() {
|
||||
@@ -868,9 +776,9 @@
|
||||
document.getElementById('deleteModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function handleFormSubmit(e) {
|
||||
async function handleFormSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
const projectData = {
|
||||
idx: currentProjectIdx ? parseInt(document.getElementById('projectIdx').value) : 0,
|
||||
name: document.getElementById('projectName').value,
|
||||
@@ -883,19 +791,16 @@
|
||||
status: document.getElementById('projectStatus').value,
|
||||
memo: document.getElementById('projectMemo').value
|
||||
};
|
||||
|
||||
const url = currentProjectIdx ? '/Project/UpdateProject' : '/Project/CreateProject';
|
||||
const method = currentProjectIdx ? 'PUT' : 'POST';
|
||||
|
||||
fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(projectData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
|
||||
try {
|
||||
let jsonStr;
|
||||
if (currentProjectIdx) {
|
||||
jsonStr = await machine.Project_UpdateProject(JSON.stringify(projectData));
|
||||
} else {
|
||||
jsonStr = await machine.Project_CreateProject(JSON.stringify(projectData));
|
||||
}
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
if (!data.Success) {
|
||||
alert('오류: ' + data.Message);
|
||||
} else {
|
||||
@@ -903,11 +808,10 @@
|
||||
closeProjectModal();
|
||||
loadProjects();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('저장 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteCurrentProject() {
|
||||
@@ -918,14 +822,13 @@
|
||||
document.getElementById('deleteModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
async function confirmDelete() {
|
||||
if (!currentProjectIdx) return;
|
||||
|
||||
fetch(`/Project/DeleteProject?id=${currentProjectIdx}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
|
||||
try {
|
||||
const jsonStr = await machine.Project_DeleteProject(currentProjectIdx);
|
||||
const data = JSON.parse(jsonStr);
|
||||
|
||||
if (!data.Success) {
|
||||
alert('오류: ' + data.Message);
|
||||
} else {
|
||||
@@ -933,11 +836,10 @@
|
||||
closeProjectModal();
|
||||
loadProjects();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusClass(status) {
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
<!-- 공통 네비게이션 -->
|
||||
<nav id="main-navigation" class="glass-effect border-b border-white/10 relative z-40">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- 로고 및 브랜드 -->
|
||||
<div class="flex items-center space-x-8">
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z"></path>
|
||||
</svg>
|
||||
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||
</div>
|
||||
|
||||
<!-- 데스크톱 메뉴 -->
|
||||
<nav class="hidden md:flex space-x-1">
|
||||
<a href="/DashBoard/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="dashboard">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z"></path>
|
||||
</svg>
|
||||
대시보드
|
||||
</a>
|
||||
|
||||
<a href="/Common/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="common">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="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>
|
||||
공용코드
|
||||
</a>
|
||||
|
||||
<a href="/Jobreport/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="jobreport">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
|
||||
</svg>
|
||||
업무일지
|
||||
</a>
|
||||
|
||||
<a href="/Kuntae/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="kuntae">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
근태관리
|
||||
</a>
|
||||
|
||||
<a href="/Todo/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="todo">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4"></path>
|
||||
</svg>
|
||||
할일관리
|
||||
</a>
|
||||
|
||||
<a href="/Project/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="project">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
프로젝트
|
||||
</a>
|
||||
|
||||
<a href="/Purchase/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="purchase">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path>
|
||||
</svg>
|
||||
구매관리
|
||||
</a>
|
||||
|
||||
<a href="/Customer/"
|
||||
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
|
||||
data-page="customer">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
||||
</svg>
|
||||
고객관리
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- 우측 메뉴 -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-sm text-white/60">
|
||||
<span>사용자</span>
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 버튼 -->
|
||||
<button id="mobile-menu-button"
|
||||
class="md:hidden text-white/60 hover:text-white focus:outline-none focus:text-white">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 -->
|
||||
<div id="mobile-menu" class="md:hidden border-t border-white/10 py-2 hidden">
|
||||
<a href="/DashBoard/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="dashboard">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z"></path>
|
||||
</svg>
|
||||
대시보드
|
||||
</a>
|
||||
<a href="/Common/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="common">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="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>
|
||||
공용코드
|
||||
</a>
|
||||
<a href="/Jobreport/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="jobreport">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
|
||||
</svg>
|
||||
업무일지
|
||||
</a>
|
||||
<a href="/Kuntae/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="kuntae">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
근태관리
|
||||
</a>
|
||||
<a href="/Todo/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="todo">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4"></path>
|
||||
</svg>
|
||||
할일관리
|
||||
</a>
|
||||
<a href="/Project/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="project">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||
</svg>
|
||||
프로젝트
|
||||
</a>
|
||||
<a href="/Purchase/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="purchase">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path>
|
||||
</svg>
|
||||
구매관리
|
||||
</a>
|
||||
<a href="/Customer/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="customer">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
||||
</svg>
|
||||
고객관리
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,111 +0,0 @@
|
||||
// 공통 네비게이션 컴포넌트
|
||||
class CommonNavigation {
|
||||
constructor(currentPage = '') {
|
||||
this.currentPage = currentPage;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createNavigation();
|
||||
this.addEventListeners();
|
||||
}
|
||||
|
||||
createNavigation() {
|
||||
const nav = document.createElement('nav');
|
||||
nav.className = 'glass-effect border-b border-white/10';
|
||||
nav.innerHTML = this.getNavigationHTML();
|
||||
|
||||
// body의 첫 번째 자식으로 추가
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
}
|
||||
|
||||
getNavigationHTML() {
|
||||
return `
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- 로고/타이틀 -->
|
||||
<div class="flex items-center">
|
||||
<h2 class="text-xl font-bold text-white">GroupWare</h2>
|
||||
</div>
|
||||
|
||||
<!-- 메뉴 -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
${this.getMenuItemHTML('dashboard', '/Dashboard/', '대시보드', 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z')}
|
||||
${this.getMenuItemHTML('common', '/Common', '공용코드', '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')}
|
||||
${this.getMenuItemHTML('jobreport', '/Jobreport/', '업무일지', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2')}
|
||||
${this.getMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 버튼 -->
|
||||
<div class="md:hidden">
|
||||
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 -->
|
||||
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
|
||||
${this.getMobileMenuItemHTML('dashboard', '/Dashboard/', '대시보드', 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z')}
|
||||
${this.getMobileMenuItemHTML('common', '/Common', '공용코드', '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')}
|
||||
${this.getMobileMenuItemHTML('jobreport', '/Jobreport/', '업무일지', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2')}
|
||||
${this.getMobileMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getMenuItemHTML(pageKey, href, text, svgPath) {
|
||||
const isActive = this.currentPage === pageKey;
|
||||
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
|
||||
|
||||
return `
|
||||
<a href="${href}" class="${activeClass} transition-colors px-3 py-2 rounded-lg">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${svgPath}"></path>
|
||||
</svg>
|
||||
${text}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
getMobileMenuItemHTML(pageKey, href, text, svgPath) {
|
||||
const isActive = this.currentPage === pageKey;
|
||||
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
|
||||
|
||||
return `
|
||||
<a href="${href}" class="block ${activeClass} transition-colors px-3 py-2 rounded-lg mb-2">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${svgPath}"></path>
|
||||
</svg>
|
||||
${text}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
// 모바일 메뉴 토글
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuButton && mobileMenu) {
|
||||
mobileMenuButton.addEventListener('click', function() {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 함수로 내비게이션 초기화
|
||||
function initNavigation(currentPage = '') {
|
||||
// DOM이 로드된 후에 실행
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new CommonNavigation(currentPage);
|
||||
});
|
||||
} else {
|
||||
new CommonNavigation(currentPage);
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/**
|
||||
* 공통 네비게이션 컴포넌트
|
||||
* 서버에서 메뉴 정보를 받아와서 동적으로 네비게이션을 생성합니다.
|
||||
*/
|
||||
class CommonNavigation {
|
||||
constructor(currentPage = '') {
|
||||
this.currentPage = currentPage;
|
||||
this.menuItems = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.loadMenuItems();
|
||||
this.createNavigation();
|
||||
this.addEventListeners();
|
||||
} catch (error) {
|
||||
console.error('Navigation initialization failed:', error);
|
||||
// 오류 발생 시 기본 메뉴로 폴백
|
||||
this.createFallbackNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
async loadMenuItems() {
|
||||
try {
|
||||
const response = await fetch('/api/Common/GetNavigationMenu');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
if (data.Success && data.Data) {
|
||||
this.menuItems = data.Data;
|
||||
} else {
|
||||
throw new Error(data.Message || 'Failed to load menu items');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load navigation menu:', error);
|
||||
// 기본 메뉴 항목으로 폴백
|
||||
this.menuItems = this.getDefaultMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultMenuItems() {
|
||||
return [
|
||||
{
|
||||
key: 'dashboard',
|
||||
title: '대시보드',
|
||||
url: '/Dashboard/',
|
||||
icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z',
|
||||
isVisible: true,
|
||||
sortOrder: 1
|
||||
},
|
||||
{
|
||||
key: 'common',
|
||||
title: '공용코드',
|
||||
url: '/Common',
|
||||
icon: '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',
|
||||
isVisible: true,
|
||||
sortOrder: 2
|
||||
},
|
||||
{
|
||||
key: 'jobreport',
|
||||
title: '업무일지',
|
||||
url: '/Jobreport/',
|
||||
icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2',
|
||||
isVisible: true,
|
||||
sortOrder: 3
|
||||
},
|
||||
{
|
||||
key: 'kuntae',
|
||||
title: '근태관리',
|
||||
url: '/Kuntae/',
|
||||
icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
isVisible: true,
|
||||
sortOrder: 4
|
||||
},
|
||||
{
|
||||
key: 'todo',
|
||||
title: '할일관리',
|
||||
url: '/Todo/',
|
||||
icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4',
|
||||
isVisible: true,
|
||||
sortOrder: 5
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
createNavigation() {
|
||||
const nav = document.createElement('nav');
|
||||
nav.className = 'glass-effect border-b border-white/10';
|
||||
nav.innerHTML = this.getNavigationHTML();
|
||||
|
||||
// body의 첫 번째 자식으로 추가
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
}
|
||||
|
||||
createFallbackNavigation() {
|
||||
console.log('Creating fallback navigation...');
|
||||
this.createNavigation();
|
||||
}
|
||||
|
||||
getNavigationHTML() {
|
||||
const visibleItems = this.menuItems
|
||||
.filter(item => item.isVisible)
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
|
||||
return `
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- 로고/타이틀 -->
|
||||
<div class="flex items-center">
|
||||
<h2 class="text-xl font-bold text-white">GroupWare</h2>
|
||||
</div>
|
||||
|
||||
<!-- 메뉴 -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
${visibleItems.map(item => this.getMenuItemHTML(item)).join('')}
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 버튼 -->
|
||||
<div class="md:hidden">
|
||||
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 -->
|
||||
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
|
||||
${visibleItems.map(item => this.getMobileMenuItemHTML(item)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getMenuItemHTML(item) {
|
||||
const isActive = this.currentPage === item.key;
|
||||
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
|
||||
|
||||
return `
|
||||
<a href="${item.url}" class="${activeClass} transition-colors px-3 py-2 rounded-lg">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||
</svg>
|
||||
${item.title}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
getMobileMenuItemHTML(item) {
|
||||
const isActive = this.currentPage === item.key;
|
||||
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
|
||||
|
||||
return `
|
||||
<a href="${item.url}" class="block ${activeClass} transition-colors px-3 py-2 rounded-lg mb-2">
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||
</svg>
|
||||
${item.title}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
// 모바일 메뉴 토글
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuButton && mobileMenu) {
|
||||
mobileMenuButton.addEventListener('click', function() {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 함수로 내비게이션 초기화
|
||||
function initNavigation(currentPage = '') {
|
||||
// DOM이 로드된 후에 실행
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new CommonNavigation(currentPage);
|
||||
});
|
||||
} else {
|
||||
new CommonNavigation(currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
// ES6 모듈로도 사용 가능하도록 export (필요시)
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { CommonNavigation, initNavigation };
|
||||
}
|
||||
Reference in New Issue
Block a user