後台調整
This commit is contained in:
parent
6e1b511d19
commit
450f4d99cc
@ -15,6 +15,16 @@
|
||||
.container.mt-4 {
|
||||
flex-grow: 1;
|
||||
}
|
||||
/* Ensure placeholders are visible for date inputs when not focused */
|
||||
input[type="date"]::before {
|
||||
content: attr(placeholder);
|
||||
color: #6c757d; /* Bootstrap's default placeholder color */
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
input[type="date"]:focus::before,
|
||||
input[type="date"]:valid::before {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -28,14 +38,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
<div class="row g-2 mb-3 align-items-center">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<input type="text" id="searchPayer" class="form-control" placeholder="搜尋住戶/繳費人">
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<input type="text" id="searchBillNumber" class="form-control" placeholder="搜尋繳費單號">
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-2 col-lg-2">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<select id="searchPaymentStatus" class="form-select">
|
||||
<option value="">所有狀態</option>
|
||||
<option value="待繳費">待繳費</option>
|
||||
@ -43,10 +53,17 @@
|
||||
<option value="已逾期">已逾期</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<input type="text" onfocus="(this.type='date')" onblur="(this.type === 'date' && !this.value) ? this.type='text' : null" id="searchDueDateStart" class="form-control" placeholder="截止日 (起)" title="繳費截止日 (起)">
|
||||
</div>
|
||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
|
||||
<input type="text" onfocus="(this.type='date')" onblur="(this.type === 'date' && !this.value) ? this.type='text' : null" id="searchDueDateEnd" class="form-control" placeholder="截止日 (迄)" title="繳費截止日 (迄)">
|
||||
</div>
|
||||
<div class="col-xl-1 col-lg-2 col-md-2 col-sm-6">
|
||||
<button class="btn btn-outline-primary w-100 rounded-pill" onclick="performSearch()">搜尋</button>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2"> <button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
|
||||
<div class="col-xl-1 col-lg-2 col-md-2 col-sm-6">
|
||||
<button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -60,6 +77,7 @@
|
||||
<th>應繳金額</th>
|
||||
<th>繳費項目</th>
|
||||
<th>繳費單號</th>
|
||||
<th>繳費截止日</th>
|
||||
<th>繳費狀態</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
@ -68,6 +86,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>目前列表總金額:<span id="currentListTotalAmount" class="fw-bold">$0</span></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>第 <span id="currentPageInfo">1</span> 頁,共 <span id="totalPagesInfo">1</span> 頁</div>
|
||||
<div>
|
||||
@ -91,7 +113,9 @@
|
||||
{ id: 4, billingDate: "2025/03/01", payer: "李四 (A102)", amountDue: 500, feeItem: "公設修繕分攤", billNumber: "FEE20250301003", paymentStatus: "已逾期", notes: "多次催繳未付", dueDate: "2025/03/15" },
|
||||
{ id: 5, billingDate: "2025/05/05", payer: "張三 (D301)", amountDue: 150, feeItem: "腳踏車位租金", billNumber: "FEE20250505001", paymentStatus: "待繳費", notes: "", dueDate: "2025/05/20" },
|
||||
{ id: 6, billingDate: "2025/05/10", payer: "趙六 (B205)", amountDue: 2000, feeItem: "預繳上半年管理費", billNumber: "FEE20250510001", paymentStatus: "已繳費", notes: "一次付清", dueDate: "2025/05/25" },
|
||||
{ id: 7, billingDate: "2025/04/15", payer: "孫七 (E101)", amountDue: 750, feeItem: "社區活動費", billNumber: "FEE20250415001", paymentStatus: "待繳費", notes: "母親節活動", dueDate: "2025/04/30" }
|
||||
{ id: 7, billingDate: "2025/04/15", payer: "孫七 (E101)", amountDue: 750, feeItem: "社區活動費", billNumber: "FEE20250415001", paymentStatus: "待繳費", notes: "母親節活動", dueDate: "2025/04/30" },
|
||||
{ id: 8, billingDate: "2025/06/01", payer: "吳九 (F202)", amountDue: 1200, feeItem: "管理費 2025年6月", billNumber: "FEE20250601001", paymentStatus: "待繳費", notes: "", dueDate: "" }, // Example with empty due date
|
||||
{ id: 9, billingDate: "2025/06/05", payer: "鄭十 (G303)", amountDue: 350, feeItem: "游泳池維護", billNumber: "FEE20250605001", paymentStatus: "待繳費", notes: "", dueDate: "2025/06/15" }
|
||||
];
|
||||
|
||||
function renderTable(dataToRender) {
|
||||
@ -100,19 +124,22 @@
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const pageData = dataToRender.slice(start, start + pageSize);
|
||||
|
||||
let currentListTotal = 0;
|
||||
dataToRender.forEach(fee => {
|
||||
currentListTotal += fee.amountDue;
|
||||
});
|
||||
const totalAmountDisplay = document.getElementById("currentListTotalAmount");
|
||||
if (totalAmountDisplay) {
|
||||
totalAmountDisplay.textContent = '$' + currentListTotal;
|
||||
}
|
||||
|
||||
pageData.forEach(fee => {
|
||||
const row = document.createElement("tr");
|
||||
let statusBadgeClass = 'bg-secondary'; // Default badge
|
||||
let statusBadgeClass = 'bg-secondary';
|
||||
switch (fee.paymentStatus) {
|
||||
case '待繳費':
|
||||
statusBadgeClass = 'bg-warning text-dark';
|
||||
break;
|
||||
case '已繳費':
|
||||
statusBadgeClass = 'bg-success';
|
||||
break;
|
||||
case '已逾期':
|
||||
statusBadgeClass = 'bg-danger';
|
||||
break;
|
||||
case '待繳費': statusBadgeClass = 'bg-warning text-dark'; break;
|
||||
case '已繳費': statusBadgeClass = 'bg-success'; break;
|
||||
case '已逾期': statusBadgeClass = 'bg-danger'; break;
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
@ -123,6 +150,7 @@
|
||||
<td>$${fee.amountDue}</td>
|
||||
<td>${fee.feeItem}</td>
|
||||
<td>${fee.billNumber}</td>
|
||||
<td>${fee.dueDate || '---'}</td>
|
||||
<td><span class="badge ${statusBadgeClass}">${fee.paymentStatus}</span></td>
|
||||
<td>
|
||||
<a href="fee_edit.html?id=${fee.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
|
||||
@ -160,6 +188,10 @@
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
|
||||
if (prevButton) prevButton.disabled = true;
|
||||
if (nextButton) nextButton.disabled = true;
|
||||
const totalAmountDisplay = document.getElementById("currentListTotalAmount");
|
||||
if (totalAmountDisplay) {
|
||||
totalAmountDisplay.textContent = '$0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,19 +216,57 @@
|
||||
const payerInput = document.getElementById("searchPayer").value.toLowerCase();
|
||||
const billNumberInput = document.getElementById("searchBillNumber").value.toLowerCase();
|
||||
const paymentStatusInput = document.getElementById("searchPaymentStatus").value;
|
||||
const dueDateStartInput = document.getElementById("searchDueDateStart").value; // "YYYY-MM-DD" or ""
|
||||
const dueDateEndInput = document.getElementById("searchDueDateEnd").value; // "YYYY-MM-DD" or ""
|
||||
|
||||
// Load from localStorage if available
|
||||
const storedFees = JSON.parse(localStorage.getItem('fees_data_temp'));
|
||||
if (storedFees) {
|
||||
fees_data = storedFees;
|
||||
}
|
||||
|
||||
currentFilteredData = fees_data.filter(fee => {
|
||||
const payerMatch = fee.payer.toLowerCase().includes(payerInput);
|
||||
const billNumberMatch = fee.billNumber.toLowerCase().includes(billNumberInput);
|
||||
const statusMatch = (paymentStatusInput === "" || fee.paymentStatus === paymentStatusInput);
|
||||
|
||||
currentFilteredData = fees_data.filter(fee =>
|
||||
fee.payer.toLowerCase().includes(payerInput) &&
|
||||
fee.billNumber.toLowerCase().includes(billNumberInput) &&
|
||||
(paymentStatusInput === "" || fee.paymentStatus === paymentStatusInput)
|
||||
);
|
||||
let dateRangeMatch = true;
|
||||
const feeDueDateStr = fee.dueDate; // format "YYYY/MM/DD" or empty
|
||||
|
||||
if (dueDateStartInput || dueDateEndInput) { // Apply date filter only if at least one date is entered
|
||||
if (!feeDueDateStr || feeDueDateStr.trim() === "") { // If fee has no valid due date string
|
||||
dateRangeMatch = false;
|
||||
} else {
|
||||
try {
|
||||
// Convert fee's due date "YYYY/MM/DD" to a Date object
|
||||
const itemDate = new Date(feeDueDateStr.replace(/\//g, '-'));
|
||||
if (isNaN(itemDate.getTime())) { // Check for invalid date
|
||||
dateRangeMatch = false;
|
||||
} else {
|
||||
itemDate.setHours(0, 0, 0, 0); // Normalize to compare dates only
|
||||
|
||||
if (dueDateStartInput) {
|
||||
const startDate = new Date(dueDateStartInput); // Input is "YYYY-MM-DD"
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
if (itemDate < startDate) {
|
||||
dateRangeMatch = false;
|
||||
}
|
||||
}
|
||||
if (dateRangeMatch && dueDateEndInput) { // Only check end if start was okay
|
||||
const endDate = new Date(dueDateEndInput); // Input is "YYYY-MM-DD"
|
||||
endDate.setHours(0, 0, 0, 0);
|
||||
if (itemDate > endDate) {
|
||||
dateRangeMatch = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing due date:", feeDueDateStr, e);
|
||||
dateRangeMatch = false; // Error during parsing, treat as non-match
|
||||
}
|
||||
}
|
||||
}
|
||||
return payerMatch && billNumberMatch && statusMatch && dateRangeMatch;
|
||||
});
|
||||
currentPage = 1;
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
@ -215,27 +285,29 @@
|
||||
}
|
||||
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
|
||||
fees_data = fees_data.filter(fee => !idsToDelete.includes(fee.id));
|
||||
localStorage.setItem('fees_data_temp', JSON.stringify(fees_data)); // Update localStorage
|
||||
localStorage.setItem('fees_data_temp', JSON.stringify(fees_data));
|
||||
|
||||
performSearch(); // Re-filter and re-render
|
||||
performSearch();
|
||||
}
|
||||
|
||||
function deleteFee(id) {
|
||||
if (!confirm("確定要刪除此筆繳費通知嗎?")) return;
|
||||
fees_data = fees_data.filter(fee => fee.id !== id);
|
||||
localStorage.setItem('fees_data_temp', JSON.stringify(fees_data)); // Update localStorage
|
||||
localStorage.setItem('fees_data_temp', JSON.stringify(fees_data));
|
||||
|
||||
performSearch(); // Re-filter and re-render
|
||||
performSearch();
|
||||
}
|
||||
|
||||
function exportFees() {
|
||||
const payerInput = document.getElementById("searchPayer").value.toLowerCase();
|
||||
const billNumberInput = document.getElementById("searchBillNumber").value.toLowerCase();
|
||||
const paymentStatusInput = document.getElementById("searchPaymentStatus").value;
|
||||
// Note: Due date range from inputs could also be used to pre-filter dataToExport
|
||||
// but current logic exports based on `currentFilteredData` if any filter is active,
|
||||
// which now includes date range.
|
||||
|
||||
let dataToExport = fees_data;
|
||||
// If any search criteria is active, export filtered data
|
||||
if (payerInput || billNumberInput || paymentStatusInput) {
|
||||
if (payerInput || billNumberInput || paymentStatusInput || document.getElementById("searchDueDateStart").value || document.getElementById("searchDueDateEnd").value) {
|
||||
dataToExport = currentFilteredData;
|
||||
}
|
||||
|
||||
@ -244,7 +316,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
let csvContent = "data:text/csv;charset=utf-8,\uFEFF"; // \uFEFF for BOM
|
||||
let csvContent = "data:text/csv;charset=utf-8,\uFEFF";
|
||||
csvContent += "ID,開單日期,住戶/繳費人,應繳金額,繳費項目,繳費單號,繳費狀態,繳費截止日,備註,提醒次數\n";
|
||||
dataToExport.forEach(fee => {
|
||||
const cleanPayer = `"${fee.payer.replace(/"/g, '""')}"`;
|
||||
@ -267,11 +339,16 @@
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
alert(`準備匯出 ${dataToExport.length} 筆繳費資料(${payerInput || billNumberInput || paymentStatusInput ? "依搜尋條件" : "全部"})`);
|
||||
const filterActive = payerInput || billNumberInput || paymentStatusInput || document.getElementById("searchDueDateStart").value || document.getElementById("searchDueDateEnd").value;
|
||||
alert(`準備匯出 ${dataToExport.length} 筆繳費資料(${filterActive ? "依搜尋條件" : "全部"})`);
|
||||
}
|
||||
|
||||
// Initial load: apply no filters (show all) and render
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize date input types correctly if they have values (e.g. from browser cache)
|
||||
['searchDueDateStart', 'searchDueDateEnd'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el.value) el.type = 'date';
|
||||
});
|
||||
performSearch();
|
||||
});
|
||||
</script>
|
||||
|
@ -11,27 +11,43 @@
|
||||
|
||||
<!-- 左側圖片區(約佔 35%) -->
|
||||
<div class="w-[35%] hidden md:flex items-center justify-center bg-gray-50">
|
||||
<img src="https://i.postimg.cc/rFSSxXRX/41513123132132.png"
|
||||
alt="美工圖"
|
||||
class="max-h-[80%] max-w-[90%] object-contain" />
|
||||
</div>
|
||||
<img src="https://i.postimg.cc/rFSSxXRX/41513123132132.png"
|
||||
alt="美工圖"
|
||||
class="max-h-[80%] max-w-[90%] object-contain" />
|
||||
</div>
|
||||
|
||||
<!-- 右側登入表單區(約佔 65%) -->
|
||||
<div class="w-full md:w-[65%] p-12 flex flex-col justify-center">
|
||||
<h2 class="text-4xl font-bold mb-8 text-gray-800">社區通 後台登入</h2>
|
||||
<form class="space-y-6">
|
||||
<form class="space-y-6" onsubmit="return login(event)">
|
||||
<div>
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700">帳號</label>
|
||||
<input type="text" class="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="請輸入帳號" />
|
||||
<input type="text" id="username" class="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="請輸入帳號" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700">密碼</label>
|
||||
<input type="password" class="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="請輸入密碼" />
|
||||
<input type="password" id="password" class="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="請輸入密碼" />
|
||||
</div>
|
||||
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 transition">登入</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function login(event) {
|
||||
event.preventDefault(); // 防止表單送出刷新頁面
|
||||
|
||||
const username = document.getElementById("username").value;
|
||||
const password = document.getElementById("password").value;
|
||||
|
||||
if (username === "admin" && password === "admin") {
|
||||
window.location.href = "main.html";
|
||||
} else {
|
||||
alert("帳號或密碼錯誤!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -68,6 +68,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>目前列表總代收金額:<span id="currentListTotalAmountToCollect" class="fw-bold">$0</span></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>第 <span id="currentPageInfo">1</span> 頁,共 <span id="totalPagesInfo">1</span> 頁</div>
|
||||
<div>
|
||||
@ -100,6 +104,16 @@
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const pageData = dataToRender.slice(start, start + pageSize);
|
||||
|
||||
// Calculate and display total amount to collect for the current filtered list
|
||||
let currentListTotalCollect = 0;
|
||||
dataToRender.forEach(pkg => {
|
||||
currentListTotalCollect += pkg.amountToCollect;
|
||||
});
|
||||
const totalAmountCollectDisplay = document.getElementById("currentListTotalAmountToCollect");
|
||||
if (totalAmountCollectDisplay) {
|
||||
totalAmountCollectDisplay.textContent = '$' + currentListTotalCollect;
|
||||
}
|
||||
|
||||
pageData.forEach(pkg => {
|
||||
const row = document.createElement("tr");
|
||||
let statusBadgeClass = 'bg-secondary'; // Default badge
|
||||
@ -161,6 +175,11 @@
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
|
||||
if (prevButton) prevButton.disabled = true;
|
||||
if (nextButton) nextButton.disabled = true;
|
||||
// Also reset total amount to collect if no items
|
||||
const totalAmountCollectDisplay = document.getElementById("currentListTotalAmountToCollect");
|
||||
if (totalAmountCollectDisplay) {
|
||||
totalAmountCollectDisplay.textContent = '$0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,6 +205,13 @@
|
||||
const packageNumberInput = document.getElementById("searchPackageNumber").value.toLowerCase();
|
||||
const statusInput = document.getElementById("searchStatus").value;
|
||||
|
||||
// In a real app, you might fetch from localStorage or server here if data can be modified elsewhere.
|
||||
// For this example, using the in-memory packages_data.
|
||||
// const storedPackages = JSON.parse(localStorage.getItem('packages_data_temp'));
|
||||
// if (storedPackages) {
|
||||
// packages_data = storedPackages;
|
||||
// }
|
||||
|
||||
currentFilteredData = packages_data.filter(pkg =>
|
||||
pkg.recipient.toLowerCase().includes(recipientInput) &&
|
||||
pkg.packageNumber.toLowerCase().includes(packageNumberInput) &&
|
||||
@ -209,6 +235,7 @@
|
||||
}
|
||||
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
|
||||
packages_data = packages_data.filter(pkg => !idsToDelete.includes(pkg.id));
|
||||
// localStorage.setItem('packages_data_temp', JSON.stringify(packages_data)); // If using localStorage
|
||||
|
||||
performSearch(); // Re-filter and re-render
|
||||
}
|
||||
@ -216,6 +243,7 @@
|
||||
function deletePackage(id) {
|
||||
if (!confirm("確定要刪除此包裹紀錄嗎?")) return;
|
||||
packages_data = packages_data.filter(pkg => pkg.id !== id);
|
||||
// localStorage.setItem('packages_data_temp', JSON.stringify(packages_data)); // If using localStorage
|
||||
|
||||
performSearch(); // Re-filter and re-render
|
||||
}
|
||||
@ -226,7 +254,6 @@
|
||||
const statusInput = document.getElementById("searchStatus").value;
|
||||
|
||||
let dataToExport = packages_data;
|
||||
// If any search criteria is active, export filtered data
|
||||
if (recipientInput || packageNumberInput || statusInput) {
|
||||
dataToExport = currentFilteredData;
|
||||
}
|
||||
@ -261,7 +288,9 @@
|
||||
}
|
||||
|
||||
// Initial load: apply no filters (show all) and render
|
||||
performSearch();
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
performSearch();
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
|
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">儲存</button>
|
||||
<a href="repair-list.html" class="btn btn-secondary ms-2">返回列表</a>
|
||||
<a href="repair_list.html" class="btn btn-secondary ms-2">返回列表</a>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
222
Backstage/repair_list.html
Normal file
222
Backstage/repair_list.html
Normal file
@ -0,0 +1,222 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>水電報修列表</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
/* 只針對狀態欄位中的 badge 進行字體放大 */
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.container.mt-4 {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<h2>水電報修列表</h2>
|
||||
</div>
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-sm-6 col-md-3 col-lg-2">
|
||||
<input type="text" id="searchName" class="form-control" placeholder="搜尋姓名">
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2">
|
||||
<input type="text" id="searchRoom" class="form-control" placeholder="搜尋房號">
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-2">
|
||||
<select id="searchStatus" class="form-select">
|
||||
<option value="">所有狀態</option>
|
||||
<option value="待處理">待處理</option>
|
||||
<option value="處理中">處理中</option>
|
||||
<option value="已完成">已完成</option>
|
||||
<option value="無法處理">無法處理</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<button class="btn btn-outline-primary w-100 rounded-pill" onclick="performSearch()">搜尋</button>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2"> <button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAllCheckbox" onclick="toggleAll(this)" /></th>
|
||||
<th>報修類型</th>
|
||||
<th>住戶姓名</th>
|
||||
<th>房號</th>
|
||||
<th>報修說明</th>
|
||||
<th>送出時間</th>
|
||||
<th>維修費用</th>
|
||||
<th>狀態</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="repairTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>列表總維修費用:<span id="totalMaintenanceFee" class="fw-bold">$0</span></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div>第 <span id="currentPageInfo">1</span> 頁,共 <span id="totalPagesInfo">1</span> 頁</div>
|
||||
<div>
|
||||
<button id="prevPageBtn" class="btn btn-sm btn-outline-secondary me-1" onclick="prevPage()">上一頁</button>
|
||||
<button id="nextPageBtn" class="btn btn-sm btn-outline-secondary" onclick="nextPage()">下一頁</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const pageSize = 5;
|
||||
let currentPage = 1;
|
||||
let currentFilteredData = [];
|
||||
|
||||
// Sample repair data
|
||||
let repairs_data = [
|
||||
{ id: 1, type: "水管漏水", name: "王小明", room: "C203", description: "廚房水管滴水不止", submissionTime: "2025-05-20 10:15", maintenanceFee: 0, status: "待處理" },
|
||||
{ id: 2, type: "電燈故障", name: "林美麗", room: "A101", description: "客廳燈不亮,已換過燈泡無效", submissionTime: "2025-05-21 14:30", maintenanceFee: 500, status: "已完成" },
|
||||
{ id: 3, type: "馬桶堵塞", name: "陳大文", room: "B305", description: "馬桶無法沖水", submissionTime: "2025-05-21 09:00", maintenanceFee: 800, status: "處理中" },
|
||||
{ id: 4, type: "冷氣異音", name: "李淑芬", room: "D402", description: "冷氣運轉時有喀拉聲", submissionTime: "2025-05-22 11:00", maintenanceFee: 0, status: "待處理" },
|
||||
{ id: 5, type: "網路不通", name: "張建宏", room: "E108", description: "房間網路孔無法上網", submissionTime: "2025-05-22 16:45", maintenanceFee: 300, status: "已完成" },
|
||||
{ id: 6, type: "門鎖損壞", name: "吳佳玲", room: "C208", description: "大門喇叭鎖故障卡死", submissionTime: "2025-05-23 08:20", maintenanceFee: 1200, status: "已完成" },
|
||||
{ id: 7, type: "窗戶漏風", name: "黃志強", room: "A501", description: "窗戶膠條老化,風雨會滲入", submissionTime: "2025-05-24 13:00", maintenanceFee: 0, status: "無法處理" }
|
||||
];
|
||||
|
||||
function renderTable(dataToRender) {
|
||||
const tbody = document.getElementById("repairTableBody");
|
||||
tbody.innerHTML = ""; // Clear existing rows
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const pageData = dataToRender.slice(start, start + pageSize);
|
||||
|
||||
let totalFee = 0;
|
||||
dataToRender.forEach(item => {
|
||||
totalFee += (item.maintenanceFee || 0);
|
||||
});
|
||||
document.getElementById("totalMaintenanceFee").textContent = '$' + totalFee;
|
||||
|
||||
pageData.forEach(item => {
|
||||
const row = document.createElement("tr");
|
||||
let statusBadgeClass = 'bg-secondary';
|
||||
switch (item.status) {
|
||||
case '待處理': statusBadgeClass = 'bg-warning text-dark'; break;
|
||||
case '處理中': statusBadgeClass = 'bg-info text-dark'; break;
|
||||
case '已完成': statusBadgeClass = 'bg-success'; break;
|
||||
case '無法處理': statusBadgeClass = 'bg-danger'; break;
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td><input type="checkbox" class="row-checkbox" value="${item.id}" /></td>
|
||||
<td>${item.type}</td>
|
||||
<td>${item.name}</td>
|
||||
<td>${item.room}</td>
|
||||
<td>${item.description}</td>
|
||||
<td>${item.submissionTime}</td>
|
||||
<td>$${item.maintenanceFee || 0}</td>
|
||||
<td><span class="badge ${statusBadgeClass}">${item.status}</span></td>
|
||||
<td>
|
||||
<a href="repair_edit.html?id=${item.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="deleteSingleItem(${item.id})">刪除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
updatePaginationControls(dataToRender.length);
|
||||
// Ensure "select all" checkbox is unchecked after re-render if rows are empty or all unselected
|
||||
const selectAllCheckbox = document.getElementById("selectAllCheckbox");
|
||||
if(selectAllCheckbox) selectAllCheckbox.checked = false;
|
||||
}
|
||||
|
||||
function updatePaginationControls(totalItems) {
|
||||
const totalPages = Math.ceil(totalItems / pageSize) || 1;
|
||||
document.getElementById("currentPageInfo").textContent = currentPage;
|
||||
document.getElementById("totalPagesInfo").textContent = totalPages;
|
||||
|
||||
document.getElementById("prevPageBtn").disabled = currentPage === 1;
|
||||
document.getElementById("nextPageBtn").disabled = currentPage === totalPages || totalItems === 0;
|
||||
|
||||
if (currentPage > totalPages && totalPages > 0) {
|
||||
currentPage = totalPages;
|
||||
renderTable(currentFilteredData); // This might cause a loop if renderTable calls updatePaginationControls directly
|
||||
// However, performSearch sets currentPage = 1, so this is more of a safeguard.
|
||||
} else if (totalItems === 0) {
|
||||
document.getElementById("currentPageInfo").textContent = 1;
|
||||
document.getElementById("totalPagesInfo").textContent = 1;
|
||||
// Total fee is updated in renderTable, so not strictly needed here unless renderTable is bypassed.
|
||||
// document.getElementById("totalMaintenanceFee").textContent = '$0';
|
||||
}
|
||||
}
|
||||
|
||||
function prevPage() {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
renderTable(currentFilteredData);
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
const totalPages = Math.ceil(currentFilteredData.length / pageSize);
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
renderTable(currentFilteredData);
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
}
|
||||
|
||||
function performSearch() {
|
||||
const nameInput = document.getElementById("searchName").value.toLowerCase();
|
||||
const roomInput = document.getElementById("searchRoom").value.toLowerCase();
|
||||
const statusInput = document.getElementById("searchStatus").value;
|
||||
|
||||
currentFilteredData = repairs_data.filter(item => {
|
||||
const nameMatch = item.name.toLowerCase().includes(nameInput);
|
||||
const roomMatch = item.room.toLowerCase().includes(roomInput);
|
||||
const statusMatch = (statusInput === "" || item.status === statusInput);
|
||||
return nameMatch && roomMatch && statusMatch;
|
||||
});
|
||||
currentPage = 1; // Reset to first page after search or filter change
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
|
||||
function toggleAll(source) {
|
||||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
||||
checkboxes.forEach(cb => (cb.checked = source.checked));
|
||||
}
|
||||
|
||||
function deleteSingleItem(itemId) {
|
||||
if (confirm('確定要刪除這筆報修項目嗎?')) {
|
||||
repairs_data = repairs_data.filter(item => item.id !== itemId);
|
||||
performSearch(); // Re-filter and re-render the table
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
const checkboxes = document.querySelectorAll('.row-checkbox:checked');
|
||||
if (checkboxes.length === 0) {
|
||||
alert('請先勾選要刪除的報修項目');
|
||||
return;
|
||||
}
|
||||
if (confirm(`確定要刪除選取的 ${checkboxes.length} 筆報修項目嗎?`)) {
|
||||
const idsToDelete = Array.from(checkboxes).map(cb => parseInt(cb.value));
|
||||
repairs_data = repairs_data.filter(item => !idsToDelete.includes(item.id));
|
||||
performSearch(); // Re-filter and re-render the table
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
performSearch(); // Initial load and render
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -203,7 +203,7 @@
|
||||
<span class="submenu-arrow">▼</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li onclick="requestNavigation('repair-list.html', this)">
|
||||
<li onclick="requestNavigation('repair_list.html', this)">
|
||||
<div class="li-content">
|
||||
<span>報修申請</span>
|
||||
<span class="badge" data-count="99">99</span>
|
||||
|
Loading…
x
Reference in New Issue
Block a user