style - 디자인 가이드 적용
crawler_api.py - 클릭방식으로 변환 README.md - 디자인 가이드 추가 analyze.md - 텍스트 비교 방식으로 분석
This commit is contained in:
129
dashboard.html
129
dashboard.html
@@ -1,32 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Project Master Overseas 관리자</title>
|
||||
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
<link rel="stylesheet" as="style" crossorigin
|
||||
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
|
||||
<link rel="stylesheet" href="style/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="topbar">
|
||||
<div class="topbar-header">
|
||||
<h2>Project Master Overseas</h2>
|
||||
<a href="/">
|
||||
<h2>Project Master Test</h2>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="nav-list">
|
||||
<li class="nav-item active">대시보드</li>
|
||||
<li class="nav-item">문의사항 <span class="badge" style="background:#FFFFFF;color:var(--primary-color); border-radius:10px; font-weight: bold; padding: 2px 5px;">12</span></li>
|
||||
<li class="nav-item">로그관리</li>
|
||||
<li class="nav-item">파일관리</li>
|
||||
<li class="nav-item">인원관리</li>
|
||||
<li class="nav-item">공지사항</li>
|
||||
<li class="nav-item active" onclick="location.href='/dashboard'">대시보드</li>
|
||||
<li class="nav-item" onclick="alert('준비 중입니다.')">문의사항</li>
|
||||
<li class="nav-item" onclick="alert('준비 중입니다.')">로그관리</li>
|
||||
<li class="nav-item" onclick="alert('준비 중입니다.')">파일관리</li>
|
||||
<li class="nav-item" onclick="alert('준비 중입니다.')">인원관리</li>
|
||||
<li class="nav-item" onclick="alert('준비 중입니다.')">공지사항</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
<header>
|
||||
<div style="display:flex; align-items:center;">
|
||||
<h1>대시보드 현황</h1>
|
||||
<h1>프로젝트 현황</h1>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center;">
|
||||
<button id="syncBtn" class="sync-btn" onclick="syncData()">
|
||||
@@ -38,8 +43,11 @@
|
||||
</header>
|
||||
|
||||
<!-- 실시간 로그 콘솔 추가 -->
|
||||
<div id="logConsole" style="display:none; background:#000; color:#0f0; font-family:monospace; padding:15px; margin-bottom:20px; border-radius:4px; max-height:200px; overflow-y:auto; font-size:12px; line-height:1.5;">
|
||||
<div style="color:#fff; border-bottom:1px solid #333; margin-bottom:10px; padding-bottom:5px; font-weight:bold;">실시간 수집 로그 [PM Overseas]</div>
|
||||
<div id="logConsole"
|
||||
style="display:none; background:#000; color:#0f0; font-family:monospace; padding:15px; margin-bottom:20px; border-radius:4px; max-height:200px; overflow-y:auto; font-size:12px; line-height:1.5;">
|
||||
<div
|
||||
style="color:#fff; border-bottom:1px solid #333; margin-bottom:10px; padding-bottom:5px; font-weight:bold;">
|
||||
실시간 수집 로그 [PM Overseas]</div>
|
||||
<div id="logBody"></div>
|
||||
</div>
|
||||
|
||||
@@ -92,8 +100,8 @@
|
||||
];
|
||||
|
||||
const continentMap = {
|
||||
"라오스": "아시아", "미얀마": "아시아", "베트남": "아시아", "사우디아라비아": "아시아",
|
||||
"우즈베키스탄": "아시아", "이라크": "아시아", "캄보디아": "아시아",
|
||||
"라오스": "아시아", "미얀마": "아시아", "베트남": "아시아", "사우디아라비아": "아시아",
|
||||
"우즈베키스탄": "아시아", "이라크": "아시아", "캄보디아": "아시아",
|
||||
"키르기스스탄": "아시아", "파키스탄": "아시아", "필리핀": "아시아",
|
||||
"아르헨티나": "아메리카", "온두라스": "아메리카", "볼리비아": "아메리카", "콜롬비아": "아메리카",
|
||||
"파라과이": "아메리카", "페루": "아메리카", "엘살바도르": "아메리카",
|
||||
@@ -116,7 +124,7 @@
|
||||
const projectName = item[0];
|
||||
let continent = "";
|
||||
let country = "";
|
||||
|
||||
|
||||
if (projectName.endsWith("사무소")) {
|
||||
continent = "지사";
|
||||
country = projectName.split(" ")[0];
|
||||
@@ -127,10 +135,10 @@
|
||||
country = projectName.split(" ")[0];
|
||||
continent = continentMap[country] || "기타";
|
||||
}
|
||||
|
||||
|
||||
if (!groupedData[continent]) groupedData[continent] = {};
|
||||
if (!groupedData[continent][country]) groupedData[continent][country] = [];
|
||||
|
||||
|
||||
groupedData[continent][country].push({ item, index });
|
||||
});
|
||||
|
||||
@@ -141,7 +149,7 @@
|
||||
sortedContinents.forEach(continent => {
|
||||
const continentGroup = document.createElement('div');
|
||||
continentGroup.className = 'continent-group';
|
||||
|
||||
|
||||
let continentHtml = `
|
||||
<div class="continent-header" onclick="toggleGroup(this)">
|
||||
<span>${continent}</span>
|
||||
@@ -161,48 +169,40 @@
|
||||
</div>
|
||||
<div class="country-body">
|
||||
<div class="accordion-container">
|
||||
<div class="accordion-list-header">
|
||||
<div>프로젝트명</div>
|
||||
<div>담당부서</div>
|
||||
<div>담당자</div>
|
||||
<div style="text-align:center;">파일수</div>
|
||||
<div>최근로그</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const sortedProjects = groupedData[continent][country].sort((a, b) => a.item[0].localeCompare(b.item[0]));
|
||||
|
||||
sortedProjects.forEach(({item, index}) => {
|
||||
sortedProjects.forEach(({ item, index }) => {
|
||||
const projectName = item[0];
|
||||
const dept = item[1];
|
||||
const admin = item[2];
|
||||
const recentLogRaw = item[3];
|
||||
const fileCount = item[4];
|
||||
const personnelCount = Math.floor(Math.random()*15)+3; // 인원은 시트에 없으므로 임의 할당 유지
|
||||
|
||||
|
||||
const recentLog = recentLogRaw === "X" ? "기록 없음" : recentLogRaw;
|
||||
const logTime = recentLog !== "기록 없음" ? recentLog.split(',')[0] : "기록 없음";
|
||||
|
||||
// 상태 클래스 결정
|
||||
let statusClass = "";
|
||||
if (fileCount === 0) statusClass = "status-error";
|
||||
else if (recentLog === "기록 없음") statusClass = "status-warning";
|
||||
|
||||
continentHtml += `
|
||||
<div class="accordion-item">
|
||||
<div class="accordion-item ${statusClass}">
|
||||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
||||
<div>
|
||||
<span class="header-label">프로젝트 명</span>
|
||||
<span class="header-value" title="${projectName}">${projectName}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="header-label">담당부서</span>
|
||||
<span class="header-value">${dept}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="header-label">관리자</span>
|
||||
<span class="header-value">${admin}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="header-label">파일 수</span>
|
||||
<span class="header-value">${fileCount}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="header-label">인원</span>
|
||||
<span class="header-value">${personnelCount}명</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="header-label">최근 로그</span>
|
||||
<span class="header-value" style="color:var(--text-sub); font-size:11px;" title="${recentLog}">${recentLog}</span>
|
||||
</div>
|
||||
<div class="repo-title" title="${projectName}">${projectName}</div>
|
||||
<div class="repo-dept">${dept}</div>
|
||||
<div class="repo-admin">${admin}</div>
|
||||
<div class="repo-files ${fileCount === 0 ? 'warning-text' : ''}">${fileCount}</div>
|
||||
<div class="repo-log ${recentLog === '기록 없음' ? 'warning-text' : ''}" title="${recentLog}">${recentLog}</div>
|
||||
</div>
|
||||
<div class="accordion-body">
|
||||
<div class="detail-grid">
|
||||
@@ -245,44 +245,36 @@
|
||||
continentHtml += `
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
continentGroup.innerHTML = continentHtml;
|
||||
container.appendChild(continentGroup);
|
||||
});
|
||||
|
||||
|
||||
const allContinents = container.querySelectorAll('.continent-group');
|
||||
allContinents.forEach(continent => {
|
||||
continent.classList.add('active');
|
||||
continent.querySelector('.continent-header .toggle-icon').textContent = '▲';
|
||||
});
|
||||
|
||||
const allCountries = container.querySelectorAll('.country-group');
|
||||
allCountries.forEach(country => {
|
||||
country.classList.add('active');
|
||||
country.querySelector('.country-header .toggle-icon').textContent = '▲';
|
||||
});
|
||||
}
|
||||
|
||||
function toggleGroup(header) {
|
||||
const group = header.parentElement;
|
||||
const icon = header.querySelector('.toggle-icon');
|
||||
group.classList.toggle('active');
|
||||
if (group.classList.contains('active')) {
|
||||
icon.textContent = '▲';
|
||||
} else {
|
||||
icon.textContent = '▼';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAccordion(header) {
|
||||
const item = header.parentElement;
|
||||
const container = item.parentElement;
|
||||
|
||||
|
||||
const allItems = container.querySelectorAll('.accordion-item');
|
||||
allItems.forEach(el => {
|
||||
if(el !== item) el.classList.remove('active');
|
||||
if (el !== item) el.classList.remove('active');
|
||||
});
|
||||
|
||||
|
||||
item.classList.toggle('active');
|
||||
}
|
||||
|
||||
@@ -290,11 +282,11 @@
|
||||
const btn = document.getElementById('syncBtn');
|
||||
const logConsole = document.getElementById('logConsole');
|
||||
const logBody = document.getElementById('logBody');
|
||||
|
||||
|
||||
btn.classList.add('loading');
|
||||
btn.innerHTML = `<span class="spinner"></span> 동기화 중 (진행 상황 확인 중...)`;
|
||||
btn.disabled = true;
|
||||
|
||||
|
||||
logConsole.style.display = 'block';
|
||||
logBody.innerHTML = ''; // 이전 로그 삭제
|
||||
|
||||
@@ -307,7 +299,7 @@
|
||||
|
||||
try {
|
||||
const response = await fetch(`/sync`);
|
||||
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
@@ -317,21 +309,21 @@
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const payload = JSON.parse(line.substring(6));
|
||||
|
||||
|
||||
if (payload.type === 'log') {
|
||||
addLog(payload.message);
|
||||
} else if (payload.type === 'done') {
|
||||
const newData = payload.data;
|
||||
newData.forEach(scrapedItem => {
|
||||
const target = rawData.find(item =>
|
||||
item[0].replace(/\s/g,'').includes(scrapedItem.projectName.replace(/\s/g,'')) ||
|
||||
scrapedItem.projectName.replace(/\s/g,'').includes(item[0].replace(/\s/g,''))
|
||||
const target = rawData.find(item =>
|
||||
item[0].replace(/\s/g, '').includes(scrapedItem.projectName.replace(/\s/g, '')) ||
|
||||
scrapedItem.projectName.replace(/\s/g, '').includes(item[0].replace(/\s/g, ''))
|
||||
);
|
||||
|
||||
|
||||
if (target) {
|
||||
// 기존 데이터 유지 마커 확인
|
||||
if (scrapedItem.recentLog !== "기존데이터유지") {
|
||||
@@ -340,7 +332,7 @@
|
||||
target[4] = scrapedItem.fileCount;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('projectAccordion').innerHTML = '';
|
||||
init();
|
||||
addLog(">>> 모든 동기화 작업이 완료되었습니다!");
|
||||
@@ -364,4 +356,5 @@
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user