Files
ver1_ext/templates/index.html
2025-07-25 15:47:50 +08:00

390 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PANJIT分機表查詢</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-900 text-gray-100 font-sans text-xs">
<div class="container mx-auto px-2 py-4">
<div class="text-center mb-6">
<h1 class="text-4xl font-extrabold text-white tracking-tight">PANJIT分機表查詢</h1>
<p class="mt-2 text-gray-400 text-base">快速查詢與管理分機資料</p>
</div>
<!-- Search and Add Button Section -->
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
<!-- Search Form -->
<div class="w-full md:w-1/2 bg-gray-800 p-4 rounded-lg shadow-md border border-gray-700">
<form action="/" method="get" class="flex items-center">
<input type="text" name="search" placeholder="搜尋廠別、部門、姓名、分機..." value="{{ search_query }}" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-l-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-xs">
<button type="submit" class="bg-blue-600 text-white px-5 py-2 rounded-r-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200 ease-in-out text-sm">搜尋</button>
</form>
</div>
<!-- Buttons Section -->
<div class="flex flex-col md:flex-row gap-4 w-full md:w-auto">
<!-- Add New Extension Button -->
<button id="open-add-modal" class="w-full whitespace-nowrap bg-green-600 text-white px-6 py-2 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 shadow-lg transition duration-200 ease-in-out text-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
新增分機
</button>
<!-- Batch Upload Button -->
<button id="open-batch-upload-modal" class="w-full whitespace-nowrap bg-purple-600 text-white px-6 py-2 rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 shadow-lg transition duration-200 ease-in-out text-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
批次增修分機
</button>
</div>
</div>
<!-- Batch Upload Modal -->
<div id="batch-upload-modal" class="fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 hidden">
<div class="bg-gray-800 p-6 rounded-lg shadow-2xl w-full max-w-3xl border border-gray-700 transform transition-all duration-300 scale-95 opacity-0" id="batch-upload-modal-content">
<div class="flex justify-between items-center pb-3 border-b border-gray-700 mb-4">
<h3 class="text-2xl font-bold text-white">批次增修分機</h3>
<button id="close-batch-upload-modal" class="text-gray-400 hover:text-gray-200 text-3xl leading-none">&times;</button>
</div>
<div class="mt-3">
<div class="mb-4">
<label for="batch-csv-file" class="block text-gray-300 text-sm font-bold mb-2">選擇 CSV 檔案:</label>
<input type="file" id="batch-csv-file" accept=".csv" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs">
<p class="mt-2 text-gray-400 text-xs">請下載 <a href="/download_template" class="text-blue-400 hover:underline" download>CSV 範本</a> 以確保檔案格式正確。</p>
</div>
<div id="csv-preview" class="bg-gray-700 p-3 rounded-md overflow-auto max-h-60 mb-4 text-gray-300 text-xs">
<p class="text-center text-gray-400">請選擇一個 CSV 檔案以預覽內容</p>
</div>
<div class="flex justify-end space-x-3">
<button id="confirm-batch-upload" class="bg-green-600 text-white px-5 py-2 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 transition duration-200 ease-in-out text-sm" disabled>確認上傳</button>
<button id="cancel-batch-upload" class="bg-gray-600 text-white px-5 py-2 rounded-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 transition duration-200 ease-in-out text-sm">取消</button>
</div>
</div>
</div>
</div>
<!-- Add Extension Modal -->
<div id="add-modal" class="fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 hidden">
<div class="bg-gray-800 p-6 rounded-lg shadow-2xl w-full max-w-sm border border-gray-700 transform transition-all duration-300 scale-95 opacity-0" id="add-modal-content">
<div class="flex justify-between items-center pb-3 border-b border-gray-700 mb-4">
<h3 class="text-2xl font-bold text-white">新增分機</h3>
<button id="close-add-modal" class="text-gray-400 hover:text-gray-200 text-3xl leading-none">&times;</button>
</div>
<div class="mt-3">
<form id="add-form" class="grid grid-cols-1 gap-3">
<input type="text" name="plant" placeholder="廠別" required class="px-3 py-1 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs">
<input type="text" name="department" placeholder="部門" required class="px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs">
<input type="text" name="name" placeholder="姓名" required class="px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs">
<input type="text" name="position" placeholder="職位" class="px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs">
<input type="text" name="extension" placeholder="分機號碼" required class="px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs">
<button type="submit" class="bg-green-600 text-white px-5 py-2 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 transition duration-200 ease-in-out text-sm mt-3">確認新增</button>
</form>
</div>
</div>
</div>
<!-- Data Table -->
<div class="bg-gray-800 shadow-xl rounded-lg overflow-hidden border border-gray-700">
<table class="min-w-full leading-normal">
<thead class="bg-gray-700 text-gray-300 uppercase text-xs">
<tr>
<th class="px-4 py-2 border-b-2 border-gray-600 text-left tracking-wider">廠別</th>
<th class="px-4 py-2 border-b-2 border-gray-600 text-left tracking-wider">部門</th>
<th class="px-4 py-2 border-b-2 border-gray-600 text-left tracking-wider">姓名</th>
<th class="px-4 py-2 border-b-2 border-gray-600 text-left tracking-wider">職位</th>
<th class="px-4 py-2 border-b-2 border-gray-600 text-left tracking-wider">分機</th>
<th class="px-4 py-2 border-b-2 border-gray-600 text-left tracking-wider">操作</th>
</tr>
</thead>
<tbody class="text-gray-200 text-xs">
{% for row in data %}
<tr class="border-b border-gray-700 hover:bg-gray-700 transition duration-150 ease-in-out">
<td class="px-4 py-2">{{ row.plant }}</td>
<td class="px-4 py-2">{{ row.department }}</td>
<td class="px-4 py-2">{{ row.name }}</td>
<td class="px-4 py-2">{{ row.position }}</td>
<td class="px-4 py-2">{{ row.extension }}</td>
<td class="px-4 py-2 flex space-x-1">
<button class="bg-yellow-600 text-white px-2 py-1 rounded-md hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-yellow-500 transition duration-200 ease-in-out text-xs" onclick="editRow(this, {{ row.id }})">編輯</button>
<button class="bg-red-600 text-white px-2 py-1 rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 transition duration-200 ease-in-out text-xs" onclick="deleteRow({{ row.id }})">刪除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script>
// Modal functionality
const addModal = document.getElementById('add-modal');
const addModalContent = document.getElementById('add-modal-content');
const openAddModalBtn = document.getElementById('open-add-modal');
const closeAddModalBtn = document.getElementById('close-add-modal');
function showModal() {
addModal.classList.remove('hidden');
setTimeout(() => {
addModalContent.classList.remove('opacity-0', 'scale-95');
addModalContent.classList.add('opacity-100', 'scale-100');
}, 50);
}
function hideModal() {
addModalContent.classList.remove('opacity-100', 'scale-100');
addModalContent.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
addModal.classList.add('hidden');
}, 300); // Match this to the transition duration
}
openAddModalBtn.addEventListener('click', showModal);
closeAddModalBtn.addEventListener('click', hideModal);
window.addEventListener('click', (event) => {
if (event.target === addModal) {
hideModal();
}
});
document.getElementById('add-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData.entries());
fetch('/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
location.reload();
}
});
});
function editRow(button, id) {
const row = button.closest('tr');
const cells = row.querySelectorAll('td');
if (button.textContent === '編輯') {
cells.forEach((cell, index) => {
if (index < cells.length - 1) {
const value = cell.textContent;
cell.innerHTML = `<input type="text" class="w-full px-2 py-1 bg-gray-700 border border-gray-600 rounded-md text-white text-xs" value="${value}">`;
}
});
button.textContent = '儲存';
button.classList.remove('bg-yellow-600', 'hover:bg-yellow-700');
button.classList.add('bg-green-600', 'hover:bg-green-700');
} else {
const data = {};
const inputs = row.querySelectorAll('input');
data.plant = inputs[0].value;
data.department = inputs[1].value;
data.name = inputs[2].value;
data.position = inputs[3].value;
data.extension = inputs[4].value;
fetch(`/update/${id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
location.reload();
}
});
}
}
function deleteRow(id) {
if (confirm('確定要刪除這筆資料嗎?')) {
fetch(`/delete/${id}`, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
location.reload();
}
});
}
}
// Batch Upload Modal functionality
const batchUploadModal = document.getElementById('batch-upload-modal');
const batchUploadModalContent = document.getElementById('batch-upload-modal-content');
const openBatchUploadModalBtn = document.getElementById('open-batch-upload-modal');
const closeBatchUploadModalBtn = document.getElementById('close-batch-upload-modal');
const cancelBatchUploadBtn = document.getElementById('cancel-batch-upload');
const batchCsvFile = document.getElementById('batch-csv-file');
const csvPreview = document.getElementById('csv-preview');
const confirmBatchUploadBtn = document.getElementById('confirm-batch-upload');
function showBatchUploadModal() {
batchUploadModal.classList.remove('hidden');
setTimeout(() => {
batchUploadModalContent.classList.remove('opacity-0', 'scale-95');
batchUploadModalContent.classList.add('opacity-100', 'scale-100');
}, 50);
}
function hideBatchUploadModal() {
batchUploadModalContent.classList.remove('opacity-100', 'scale-100');
batchUploadModalContent.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
batchUploadModal.classList.add('hidden');
// Reset file input and preview
batchCsvFile.value = '';
csvPreview.innerHTML = '<p class="text-center text-gray-400">請選擇一個 CSV 檔案以預覽內容</p>';
confirmBatchUploadBtn.disabled = true;
}, 300);
}
openBatchUploadModalBtn.addEventListener('click', showBatchUploadModal);
closeBatchUploadModalBtn.addEventListener('click', hideBatchUploadModal);
cancelBatchUploadBtn.addEventListener('click', hideBatchUploadModal);
window.addEventListener('click', (event) => {
if (event.target === batchUploadModal) {
hideBatchUploadModal();
}
});
batchCsvFile.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const text = e.target.result;
const lines = text.split(/\r\n|\n/);
const headers = ["plant", "department", "csv_id", "position", "extension"]; // Internal keys
const chineseHeaders = ["廠別", "部門", "姓名", "職位", "分機"];
if (lines.length < 2) {
csvPreview.innerHTML = '<p class="text-center text-gray-400">CSV 檔案內容為空或格式不正確。</p>';
confirmBatchUploadBtn.disabled = true;
return;
}
// Parse CSV data for sending to backend
const csvData = [];
for (let i = 1; i < lines.length; i++) {
const row = lines[i].split(',');
if (row.length >= 5) { // Ensure row has enough columns
csvData.push({
plant: row[0],
department: row[1],
csv_id: row[2],
position: row[3],
extension: row[4]
});
}
}
if (csvData.length === 0) {
csvPreview.innerHTML = '<p class="text-center text-gray-400">CSV 檔案中沒有有效的資料行。</p>';
confirmBatchUploadBtn.disabled = true;
return;
}
// Send data to backend for validation
fetch('/validate_csv_data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(csvData)
})
.then(response => response.json())
.then(validatedRows => {
let html = '<table class="min-w-full bg-gray-700 text-gray-300 text-xs"><thead><tr>';
chineseHeaders.forEach(h => {
html += `<th class="px-2 py-1 border-b border-gray-600 text-left">${h}</th>`;
});
html += '<th class="px-2 py-1 border-b border-gray-600 text-left">狀態</th>';
html += '</tr></thead><tbody>';
validatedRows.forEach(rowData => {
const original = rowData.original_data;
const diff = rowData.diff;
const status = rowData.status;
html += '<tr>';
html += `<td class="px-2 py-1 border-b border-gray-600">${original.plant}</td>`;
html += `<td class="px-2 py-1 border-b border-gray-600 ${diff.department ? 'text-red-400' : ''}">${original.department}</td>`;
html += `<td class="px-2 py-1 border-b border-gray-600">${original.csv_id}</td>`;
html += `<td class="px-2 py-1 border-b border-gray-600 ${diff.position ? 'text-red-400' : ''}">${original.position}</td>`;
html += `<td class="px-2 py-1 border-b border-gray-600 ${diff.extension ? 'text-red-400' : ''}">${original.extension}</td>`;
let statusText = '';
if (status === 'new') {
statusText = '<span class="text-green-400">新增</span>';
} else if (status === 'update') {
statusText = '<span class="text-yellow-400">更新</span>';
} else {
statusText = '<span class="text-gray-400">無變更</span>';
}
html += `<td class="px-2 py-1 border-b border-gray-600">${statusText}</td>`;
html += '</tr>';
});
html += '</tbody></table>';
csvPreview.innerHTML = html;
confirmBatchUploadBtn.disabled = false;
})
.catch(error => {
console.error('Error validating CSV data:', error);
csvPreview.innerHTML = '<p class="text-center text-red-400">驗證 CSV 檔案時發生錯誤。</p>';
confirmBatchUploadBtn.disabled = true;
});
};
reader.readAsText(file, 'Big5'); // Assuming Big5 encoding for CSV
} else {
csvPreview.innerHTML = '<p class="text-center text-gray-400">請選擇一個 CSV 檔案以預覽內容</p>';
confirmBatchUploadBtn.disabled = true;
}
});
confirmBatchUploadBtn.addEventListener('click', function() {
const file = batchCsvFile.files[0];
if (file) {
const formData = new FormData();
formData.append('csv_file', file);
fetch('/batch_upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('批次上傳成功!');
hideBatchUploadModal();
location.reload();
} else {
alert('批次上傳失敗: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('批次上傳過程中發生錯誤。');
});
}
});
</script>
</body>
</html>