0523_前後台調整
This commit is contained in:
parent
2909ed1f65
commit
a560065aec
123
Backstage/activity_add.html
Normal file
123
Backstage/activity_add.html
Normal file
@ -0,0 +1,123 @@
|
||||
<!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">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">新增活動資料</h2>
|
||||
<form id="addActivityForm" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">活動名稱</label>
|
||||
<input type="text" class="form-control" id="title" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="date" class="form-label">開始時間</label>
|
||||
<input type="datetime-local" class="form-control" id="date" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="endDate" class="form-label">結束時間</label>
|
||||
<input type="datetime-local" class="form-control" id="endDate" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="location" class="form-label">地點</label>
|
||||
<input type="text" class="form-control" id="location" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">活動描述</label>
|
||||
<textarea class="form-control" id="description" rows="4" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="poster" class="form-label">上傳活動海報</label>
|
||||
<input type="file" class="form-control" id="poster" accept="image/*">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="needRegister" class="form-label">是否需統計報名人數?</label>
|
||||
<select id="needRegister" class="form-select">
|
||||
<option value="否" selected>否</option>
|
||||
<option value="是">是</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="repeatOption" class="form-label">是否重複舉辦</label>
|
||||
<select id="repeatOption" class="form-select" onchange="toggleRepeatFields()">
|
||||
<option value="none" selected>否</option>
|
||||
<option value="weekly">每週一次</option>
|
||||
<option value="biweekly">每兩週一次</option>
|
||||
<option value="monthly">每月一次</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 d-none" id="repeatCountGroup">
|
||||
<label for="repeatCount" class="form-label">重複次數(不含首次)</label>
|
||||
<input type="number" class="form-control" id="repeatCount" min="1" value="1">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="approvalStatus" class="form-label">審核狀態</label>
|
||||
<select id="approvalStatus" class="form-select">
|
||||
<option value="審核中" selected>審核中</option>
|
||||
<option value="通過">通過</option>
|
||||
<option value="不通過">不通過</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">新增活動</button>
|
||||
<a href="activity_list.html" class="btn btn-secondary ms-2">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleRepeatFields() {
|
||||
const option = document.getElementById('repeatOption').value;
|
||||
const group = document.getElementById('repeatCountGroup');
|
||||
group.classList.toggle('d-none', option === 'none');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
toggleRepeatFields();
|
||||
|
||||
document.getElementById("addActivityForm").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const title = document.getElementById('title').value;
|
||||
const startDate = document.getElementById('date').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
const location = document.getElementById('location').value;
|
||||
const description = document.getElementById('description').value;
|
||||
const posterFile = document.getElementById('poster').files[0];
|
||||
const needRegister = document.getElementById('needRegister').value;
|
||||
const repeatOption = document.getElementById('repeatOption').value;
|
||||
const repeatCount = document.getElementById('repeatCount').value;
|
||||
const approvalStatus = document.getElementById('approvalStatus').value; // Get approval status
|
||||
|
||||
if (endDate && startDate && new Date(endDate) <= new Date(startDate)) {
|
||||
alert("結束時間必須晚於開始時間!");
|
||||
return;
|
||||
}
|
||||
|
||||
let alertMessage = `新活動已建立(模擬):\n名稱: ${title}\n開始: ${startDate}\n結束: ${endDate}\n地點: ${location}\n描述: ${description}\n是否需報名: ${needRegister}\n審核狀態: ${approvalStatus}`; // Added to alert
|
||||
|
||||
if (repeatOption !== 'none') {
|
||||
const optionMap = { weekly: '每週', biweekly: '每兩週', monthly: '每月' };
|
||||
alertMessage += `\n重複設定:${optionMap[repeatOption]},共重複 ${repeatCount} 次`;
|
||||
}
|
||||
if (posterFile) {
|
||||
alertMessage += `\n海報檔案: ${posterFile.name}`;
|
||||
}
|
||||
alert(alertMessage);
|
||||
|
||||
window.location.href = "activity_list.html";
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
184
Backstage/activity_edit.html
Normal file
184
Backstage/activity_edit.html
Normal file
@ -0,0 +1,184 @@
|
||||
<!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>
|
||||
.current-poster-label {
|
||||
font-size: 0.9em;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">編輯活動資料</h2>
|
||||
<form id="editActivityForm" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">活動名稱</label>
|
||||
<input type="text" class="form-control" id="title" required>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="date" class="form-label">開始時間</label>
|
||||
<input type="datetime-local" class="form-control" id="date" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="endDate" class="form-label">結束時間</label>
|
||||
<input type="datetime-local" class="form-control" id="endDate" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="location" class="form-label">地點</label>
|
||||
<input type="text" class="form-control" id="location" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">活動描述</label>
|
||||
<textarea class="form-control" id="description" rows="4" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="poster" class="form-label">更新活動海報</label>
|
||||
<input type="file" class="form-control" id="poster" accept="image/*">
|
||||
<div id="currentPosterDisplay" class="form-text"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="needRegister" class="form-label">是否需統計報名人數?</label>
|
||||
<select id="needRegister" class="form-select">
|
||||
<option value="否">否</option>
|
||||
<option value="是">是</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="repeatOption" class="form-label">是否重複舉辦</label>
|
||||
<select id="repeatOption" class="form-select" onchange="toggleRepeatFields()">
|
||||
<option value="none">否</option>
|
||||
<option value="weekly">每週一次</option>
|
||||
<option value="biweekly">每兩週一次</option>
|
||||
<option value="monthly">每月一次</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 d-none" id="repeatCountGroup">
|
||||
<label for="repeatCount" class="form-label">重複次數(不含首次)</label>
|
||||
<input type="number" class="form-control" id="repeatCount" min="1" value="1">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="approvalStatus" class="form-label">審核狀態</label>
|
||||
<select id="approvalStatus" class="form-select">
|
||||
<option value="審核中">審核中</option>
|
||||
<option value="通過">通過</option>
|
||||
<option value="不通過">不通過</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">儲存變更</button>
|
||||
<a href="activity_list.html" class="btn btn-secondary ms-2">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const activities_data = [ // Sample data now includes approvalStatus
|
||||
{
|
||||
id: 1, title: "🎉 社區春季市集", startDate: "2025-04-27T10:00", endDate: "2025-04-27T16:00",
|
||||
location: "中庭花園", description: "市集將有手作小物、美食攤販及親子遊戲活動,歡迎全體住戶參與!",
|
||||
imageName: "spring_market.jpg", needRegister: "否", repeatOption: "none", repeatCount: 1, approvalStatus: "通過"
|
||||
},
|
||||
{
|
||||
id: 2, title: "🧘♀️ 早晨瑜珈課程", startDate: "2025-05-03T07:00", endDate: "2025-05-03T08:00",
|
||||
location: "社區多功能教室", description: "由專業老師帶領,適合各年齡層,請自備瑜珈墊。",
|
||||
imageName: "yoga_class.png", needRegister: "是", repeatOption: "weekly", repeatCount: 4, approvalStatus: "審核中"
|
||||
},
|
||||
{
|
||||
id: 3, title: "📚 二手書交換日", startDate: "2025-05-05T13:00", endDate: "2025-05-05T17:00",
|
||||
location: "社區圖書區", description: "帶來你的二手書,一起交流閱讀樂趣!",
|
||||
imageName: null, needRegister: "是", repeatOption: "none", repeatCount: 1, approvalStatus: "不通過"
|
||||
}
|
||||
];
|
||||
|
||||
function getParam(key) {
|
||||
const url = new URL(window.location.href);
|
||||
return url.searchParams.get(key);
|
||||
}
|
||||
|
||||
function toggleRepeatFields() {
|
||||
const option = document.getElementById('repeatOption').value;
|
||||
const group = document.getElementById('repeatCountGroup');
|
||||
group.classList.toggle('d-none', option === 'none');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const activityId = parseInt(getParam("id"));
|
||||
const activity = activities_data.find(act => act.id === activityId);
|
||||
|
||||
if (activity) {
|
||||
document.getElementById("title").value = activity.title;
|
||||
document.getElementById("date").value = activity.startDate || '';
|
||||
document.getElementById("endDate").value = activity.endDate || '';
|
||||
document.getElementById("location").value = activity.location;
|
||||
document.getElementById("description").value = activity.description;
|
||||
document.getElementById("needRegister").value = activity.needRegister;
|
||||
document.getElementById("repeatOption").value = activity.repeatOption || 'none';
|
||||
document.getElementById("repeatCount").value = activity.repeatCount || 1;
|
||||
document.getElementById("approvalStatus").value = activity.approvalStatus || '審核中'; // Populate status
|
||||
|
||||
if (activity.imageName) {
|
||||
document.getElementById("currentPosterDisplay").textContent = `目前海報:${activity.imageName}`;
|
||||
} else {
|
||||
document.getElementById("currentPosterDisplay").textContent = `目前尚無海報。`;
|
||||
}
|
||||
toggleRepeatFields();
|
||||
} else {
|
||||
alert("找不到指定的活動資料!");
|
||||
window.location.href = "activity_list.html";
|
||||
}
|
||||
|
||||
document.getElementById("editActivityForm").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const title = document.getElementById('title').value;
|
||||
const startDate = document.getElementById('date').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
const approvalStatus = document.getElementById('approvalStatus').value; // Get status
|
||||
const newPosterFile = document.getElementById('poster').files[0];
|
||||
|
||||
if (endDate && startDate && new Date(endDate) <= new Date(startDate)) {
|
||||
alert("結束時間必須晚於開始時間!");
|
||||
return;
|
||||
}
|
||||
|
||||
let alertMessage = `活動資料已更新(模擬):\nID: ${activityId}\n名稱: ${title}\n開始: ${startDate}\n結束: ${endDate}\n審核狀態: ${approvalStatus}`; // Added to alert
|
||||
if (newPosterFile) {
|
||||
alertMessage += `\n新海報: ${newPosterFile.name}`;
|
||||
}
|
||||
alert(alertMessage);
|
||||
|
||||
const activityIndex = activities_data.findIndex(act => act.id === activityId);
|
||||
if (activityIndex > -1) {
|
||||
activities_data[activityIndex].title = title;
|
||||
activities_data[activityIndex].startDate = startDate;
|
||||
activities_data[activityIndex].endDate = endDate;
|
||||
activities_data[activityIndex].location = document.getElementById('location').value;
|
||||
activities_data[activityIndex].description = document.getElementById('description').value;
|
||||
activities_data[activityIndex].needRegister = document.getElementById('needRegister').value;
|
||||
activities_data[activityIndex].repeatOption = document.getElementById('repeatOption').value;
|
||||
if (activities_data[activityIndex].repeatOption !== 'none') {
|
||||
activities_data[activityIndex].repeatCount = document.getElementById('repeatCount').value;
|
||||
}
|
||||
activities_data[activityIndex].approvalStatus = approvalStatus; // Save status
|
||||
if (newPosterFile) {
|
||||
activities_data[activityIndex].imageName = newPosterFile.name;
|
||||
}
|
||||
}
|
||||
|
||||
window.location.href = "activity_list.html";
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
295
Backstage/activity_list.html
Normal file
295
Backstage/activity_list.html
Normal file
@ -0,0 +1,295 @@
|
||||
<!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>
|
||||
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">
|
||||
<h2 class="mb-4">活動列表</h2>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-4 col-md-2 col-lg-1">
|
||||
<a href="activity_add.html" class="btn btn-outline-success w-100 rounded-pill">新增活動</a>
|
||||
</div>
|
||||
<div class="col-4 col-md-2 col-lg-1">
|
||||
<button class="btn btn-outline-secondary w-100 rounded-pill" onclick="exportActivities()">匯出活動</button>
|
||||
</div>
|
||||
</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-4 col-lg-3">
|
||||
<input type="text" id="searchLocation" class="form-control" placeholder="搜尋地點">
|
||||
</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-1">
|
||||
<button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
|
||||
<th>編號</th>
|
||||
<th>活動名稱</th>
|
||||
<th>活動時間</th>
|
||||
<th>地點</th>
|
||||
<th>是否需報名</th>
|
||||
<th>審核狀態</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="activityTable">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<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; // Number of items per page
|
||||
let currentPage = 1;
|
||||
let currentFilteredData = [];
|
||||
|
||||
// Sample activity data. In a real application, this would come from a server.
|
||||
// Note: The 'time' field can accommodate various formats.
|
||||
let activities_data = [
|
||||
{ id: 1, title: "🎉 社區春季市集", time: "2025/04/27(日)10:00 - 16:00", location: "中庭花園", desc: "市集將有手作小物、美食攤販及親子遊戲活動,歡迎全體住戶參與!", image: "https://picsum.photos/id/1011/600/300", needRegister: "否", status: "審核通過" },
|
||||
{ id: 2, title: "🧘♀️ 早晨瑜珈課程", time: "每週六 07:00 - 08:00", location: "社區多功能教室", desc: "由專業老師帶領,適合各年齡層,請自備瑜珈墊。", image: "https://picsum.photos/id/1015/600/300", needRegister: "是", status: "審核中" },
|
||||
{ id: 3, title: "📚 二手書交換日", time: "2025/05/05(六)13:00 - 17:00", location: "社區圖書區", desc: "帶來你的二手書,一起交流閱讀樂趣!", image: "https://picsum.photos/id/1033/600/300", needRegister: "是", status: "未審核" },
|
||||
{ id: 4, title: "電影之夜:星際漫遊", time: "2025/06/15(六)19:00 - 21:00", location: "社區視聽室", desc: "播放經典科幻電影,提供爆米花。", image: "https://picsum.photos/id/1043/600/300", needRegister: "是", status: "審核通過" },
|
||||
{ id: 5, title: "夏季園藝工作坊", time: "2025/07/10(三)14:00 - 16:00", location: "頂樓花園", desc: "學習基本園藝技巧,美化社區環境。", image: "https://picsum.photos/id/1053/600/300", needRegister: "否", status: "未審核" },
|
||||
{ id: 6, title: "中秋晚會", time: "2025/09/15(一)18:00 - 21:00", location: "戶外廣場", desc: "賞月、品嚐月餅、燈謎活動。", image: "https://picsum.photos/id/1063/600/300", needRegister: "是", status: "審核中" },
|
||||
{ id: 7, title: "兒童故事時間", time: "每月第一個週六 10:00 - 11:00", location: "社區圖書區", desc: "專為兒童設計的趣味故事時間。", image: "https://picsum.photos/id/1073/600/300", needRegister: "否", status: "審核通過" }
|
||||
];
|
||||
|
||||
function renderTable(dataToRender) {
|
||||
const tbody = document.getElementById("activityTable");
|
||||
tbody.innerHTML = "";
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const pageData = dataToRender.slice(start, start + pageSize);
|
||||
|
||||
pageData.forEach(act => {
|
||||
const row = document.createElement("tr");
|
||||
let statusBadgeClass = 'bg-secondary'; // Default badge
|
||||
if (act.status === '審核通過') {
|
||||
statusBadgeClass = 'bg-success';
|
||||
} else if (act.status === '審核中') {
|
||||
statusBadgeClass = 'bg-info';
|
||||
} else if (act.status === '未審核') {
|
||||
statusBadgeClass = 'bg-warning text-dark'; // text-dark for better contrast on yellow
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td><input type="checkbox" class="row-checkbox" data-id="${act.id}"></td>
|
||||
<td>${act.id}</td>
|
||||
<td>${act.title}</td>
|
||||
<td>${act.time}</td>
|
||||
<td>${act.location}</td>
|
||||
<td><span class="badge bg-${act.needRegister === '是' ? 'success' : 'secondary'}">${act.needRegister}</span></td>
|
||||
<td><span class="badge ${statusBadgeClass}">${act.status}</span></td>
|
||||
<td>
|
||||
<a href="activity_edit.html?id=${act.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="deleteActivity(${act.id})">刪除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
updatePaginationControls(dataToRender.length);
|
||||
const selectAllCheckbox = document.getElementById("selectAll");
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationControls(totalItems) {
|
||||
const totalPages = Math.ceil(totalItems / pageSize) || 1;
|
||||
const currentPageDisplay = document.getElementById("currentPageInfo");
|
||||
const totalPagesDisplay = document.getElementById("totalPagesInfo");
|
||||
const prevButton = document.getElementById("prevPageBtn");
|
||||
const nextButton = document.getElementById("nextPageBtn");
|
||||
|
||||
if (currentPageDisplay) currentPageDisplay.textContent = currentPage;
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = totalPages;
|
||||
|
||||
if (prevButton) prevButton.disabled = currentPage === 1;
|
||||
if (nextButton) nextButton.disabled = currentPage === totalPages || totalItems === 0;
|
||||
|
||||
if (currentPage > totalPages && totalPages > 0) {
|
||||
currentPage = totalPages;
|
||||
renderTable(currentFilteredData);
|
||||
} else if (totalItems === 0) {
|
||||
if (currentPageDisplay) currentPageDisplay.textContent = 1;
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
|
||||
if (prevButton) prevButton.disabled = true;
|
||||
if (nextButton) nextButton.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
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 locationInput = document.getElementById("searchLocation").value.toLowerCase();
|
||||
|
||||
currentFilteredData = activities_data.filter(act =>
|
||||
act.title.toLowerCase().includes(nameInput) &&
|
||||
act.location.toLowerCase().includes(locationInput)
|
||||
);
|
||||
currentPage = 1;
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
|
||||
function toggleAll(source) {
|
||||
const checkboxes = document.querySelectorAll(".row-checkbox");
|
||||
checkboxes.forEach(cb => cb.checked = source.checked);
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
if (!confirm("確定要刪除勾選的活動嗎?")) return;
|
||||
const selected = document.querySelectorAll(".row-checkbox:checked");
|
||||
if (selected.length === 0) {
|
||||
alert("請先勾選要刪除的活動");
|
||||
return;
|
||||
}
|
||||
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
|
||||
activities_data = activities_data.filter(act => !idsToDelete.includes(act.id));
|
||||
|
||||
// Re-filter and re-render after deletion
|
||||
const nameInput = document.getElementById("searchName").value.toLowerCase();
|
||||
const locationInput = document.getElementById("searchLocation").value.toLowerCase();
|
||||
currentFilteredData = activities_data.filter(act =>
|
||||
act.title.toLowerCase().includes(nameInput) &&
|
||||
act.location.toLowerCase().includes(locationInput)
|
||||
);
|
||||
|
||||
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
|
||||
if (currentPage > totalPages) {
|
||||
currentPage = totalPages;
|
||||
}
|
||||
if (currentFilteredData.length === 0 && currentPage > 1) { // If all items on current page are deleted
|
||||
currentPage = Math.max(1, totalPages); // Go to previous page or 1
|
||||
} else if (currentFilteredData.length === 0) {
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
|
||||
function deleteActivity(id) {
|
||||
if (!confirm("確定要刪除此活動嗎?")) return;
|
||||
activities_data = activities_data.filter(act => act.id !== id);
|
||||
|
||||
// Re-filter and re-render after deletion
|
||||
const nameInput = document.getElementById("searchName").value.toLowerCase();
|
||||
const locationInput = document.getElementById("searchLocation").value.toLowerCase();
|
||||
currentFilteredData = activities_data.filter(act =>
|
||||
act.title.toLowerCase().includes(nameInput) &&
|
||||
act.location.toLowerCase().includes(locationInput)
|
||||
);
|
||||
|
||||
// Adjust current page if necessary (e.g. if last item on a page is deleted)
|
||||
const totalItemsOnCurrentPage = currentFilteredData.slice((currentPage - 1) * pageSize, currentPage * pageSize).length;
|
||||
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
|
||||
|
||||
if (totalItemsOnCurrentPage === 0 && currentPage > 1) {
|
||||
currentPage--;
|
||||
} else if (currentPage > totalPages) {
|
||||
currentPage = totalPages;
|
||||
}
|
||||
if (currentFilteredData.length === 0) { // If no data left at all
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
|
||||
function exportActivities() {
|
||||
const nameInput = document.getElementById("searchName").value.toLowerCase();
|
||||
const locationInput = document.getElementById("searchLocation").value.toLowerCase();
|
||||
let dataToExport = activities_data;
|
||||
if (nameInput || locationInput) { // If any search criteria is active
|
||||
dataToExport = currentFilteredData; // Export filtered data
|
||||
}
|
||||
|
||||
if (dataToExport.length === 0) {
|
||||
alert("沒有資料可匯出。");
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic CSV export for simplicity
|
||||
let csvContent = "data:text/csv;charset=utf-8,\uFEFF"; // \uFEFF for BOM to ensure UTF-8 in Excel
|
||||
csvContent += "編號,活動名稱,活動時間,地點,是否需報名,審核狀態,描述,圖片連結\n"; // CSV Header
|
||||
dataToExport.forEach(act => {
|
||||
// Ensure all fields are properly quoted and escaped for CSV
|
||||
const cleanTitle = `"${act.title.replace(/"/g, '""')}"`;
|
||||
const cleanTime = `"${act.time.replace(/"/g, '""')}"`;
|
||||
const cleanLocation = `"${act.location.replace(/"/g, '""')}"`;
|
||||
const cleanStatus = `"${act.status.replace(/"/g, '""')}"`;
|
||||
const cleanDesc = `"${act.desc.replace(/"/g, '""')}"`;
|
||||
const cleanImage = `"${act.image ? act.image.replace(/"/g, '""') : ''}"`;
|
||||
|
||||
const row = [act.id, cleanTitle, cleanTime, cleanLocation, act.needRegister, cleanStatus, cleanDesc, cleanImage];
|
||||
csvContent += row.join(",") + "\n";
|
||||
});
|
||||
|
||||
const encodedUri = encodeURI(csvContent);
|
||||
const link = document.createElement("a");
|
||||
link.setAttribute("href", encodedUri);
|
||||
link.setAttribute("download", "activities_export.csv");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
alert(`準備匯出 ${dataToExport.length} 筆活動資料(${nameInput || locationInput ? "依搜尋條件" : "全部"})`);
|
||||
}
|
||||
|
||||
// Initial load: apply no filters (show all) and render
|
||||
performSearch();
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
39
Backstage/mailing_firm_add.html
Normal file
39
Backstage/mailing_firm_add.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>新增郵寄廠商</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">新增郵寄廠商</h2>
|
||||
<form id="createVendorForm">
|
||||
<div class="mb-3">
|
||||
<label for="vendorName" class="form-label">廠商名稱</label>
|
||||
<input type="text" class="form-control" id="vendorName" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">新增廠商</button>
|
||||
<a href="mailing_firm_list.html" class="btn btn-secondary ms-2">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById("createVendorForm").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const newVendor = {
|
||||
name: document.getElementById("vendorName").value
|
||||
};
|
||||
|
||||
// In a real application, you would generate a unique ID here or get it from the backend.
|
||||
// For demonstration, we'll assume the list page's script handles ID generation or can work without it initially.
|
||||
console.log("新增的郵寄廠商資料:", newVendor);
|
||||
alert("郵寄廠商資料已新增(此為前端演示,實際需串接後端 API)");
|
||||
// Typically, you'd pass the new data to the list page or have the list page re-fetch.
|
||||
// For now, we'll just redirect.
|
||||
window.location.href = "mailing_firm_list.html";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
74
Backstage/mailing_firm_edit.html
Normal file
74
Backstage/mailing_firm_edit.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>編輯郵寄廠商資料</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">編輯郵寄廠商資料</h2>
|
||||
<form id="editVendorForm">
|
||||
<div class="mb-3">
|
||||
<label for="vendorName" class="form-label">廠商名稱</label>
|
||||
<input type="text" class="form-control" id="vendorName" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">儲存變更</button>
|
||||
<a href="mailing_firm_list.html" class="btn btn-secondary ms-2">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Function to get URL parameters
|
||||
function getUrlParameter(name) {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||
const results = regex.exec(location.search);
|
||||
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
// In a real application, you would fetch the vendor data based on the ID from the URL.
|
||||
// For this example, we'll simulate fetching data.
|
||||
// You would need a way to access the 'vendors' array from mailing_firm_list.html or use localStorage/backend.
|
||||
// For now, let's use a placeholder name based on the ID or a default.
|
||||
const vendorId = getUrlParameter('id');
|
||||
let currentVendorName = "範例郵寄廠商"; // Default name
|
||||
|
||||
// This is a simplified placeholder.
|
||||
// In a real scenario, you'd retrieve this from localStorage or an API using vendorId.
|
||||
// For example, if you stored vendors in localStorage from the list page:
|
||||
// let vendors = JSON.parse(localStorage.getItem('mailingVendors')) || [];
|
||||
// const vendorToEdit = vendors.find(v => v.id === parseInt(vendorId));
|
||||
// if (vendorToEdit) {
|
||||
// currentVendorName = vendorToEdit.name;
|
||||
// } else if (vendorId) {
|
||||
// currentVendorName = `廠商 #${vendorId}`; // Fallback if not found but ID exists
|
||||
// }
|
||||
|
||||
|
||||
// Populate the form field
|
||||
document.getElementById("vendorName").value = currentVendorName;
|
||||
if (vendorId) {
|
||||
// If you have a way to get the actual name, display it, e.g.
|
||||
// document.getElementById("vendorName").value = actualVendorNameFetched;
|
||||
console.log("Editing vendor with ID:", vendorId); // Log the ID for context
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("editVendorForm").addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const updatedVendor = {
|
||||
id: vendorId ? parseInt(vendorId) : null, // Keep the ID if it exists
|
||||
name: document.getElementById("vendorName").value
|
||||
};
|
||||
|
||||
console.log("更新後的郵寄廠商資料:", updatedVendor);
|
||||
alert("郵寄廠商資料已更新(此為前端演示,實際需串接後端 API 並更新對應資料)");
|
||||
// In a real application, you would send this data to a backend to update.
|
||||
// Then, redirect or update the list page.
|
||||
window.location.href = "mailing_firm_list.html";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
214
Backstage/mailing_firm_list.html
Normal file
214
Backstage/mailing_firm_list.html
Normal file
@ -0,0 +1,214 @@
|
||||
<!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>
|
||||
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">
|
||||
<h2 class="mb-4">郵寄廠商</h2>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-auto">
|
||||
<a href="mailing_firm_add.html" class="btn btn-outline-success rounded-pill">新增廠商</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-sm-8 col-md-5 col-lg-4">
|
||||
<input type="text" id="searchName" class="form-control" placeholder="搜尋廠商名稱">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-outline-primary w-100 rounded-pill" onclick="performSearch()">搜尋</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-outline-danger w-100 rounded-pill" onclick="deleteSelected()">刪除勾選</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
|
||||
<th>廠商名稱</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vendorTable"></tbody>
|
||||
</table>
|
||||
|
||||
<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; // Adjusted page size for simpler data
|
||||
let currentPage = 1;
|
||||
let currentFilteredData = [];
|
||||
|
||||
// Sample data for mailing vendors
|
||||
let vendors = [
|
||||
{ id: 1, name: "快捷郵運" },
|
||||
{ id: 2, name: "順風物流" },
|
||||
{ id: 3, name: "平安包裹" },
|
||||
{ id: 4, name: "台灣郵政合作夥伴" },
|
||||
{ id: 5, name: "環球快遞" },
|
||||
{ id: 6, name: "城市通郵寄" },
|
||||
{ id: 7, name: "鄉里配送" },
|
||||
{ id: 8, name: "文件急送專家" },
|
||||
{ id: 9, name: "省心郵寄服務" },
|
||||
{ id: 10, name: "國際郵件通" },
|
||||
{ id: 11, name: "本地包裹王" }
|
||||
];
|
||||
|
||||
function renderTable(dataToRender) {
|
||||
const tbody = document.getElementById("vendorTable");
|
||||
tbody.innerHTML = "";
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const pageData = dataToRender.slice(start, start + pageSize);
|
||||
|
||||
pageData.forEach(v => {
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td><input type="checkbox" class="row-checkbox" data-id="${v.id}"></td>
|
||||
<td>${v.name}</td>
|
||||
<td>
|
||||
<a href="mailing_firm_edit.html?id=${v.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="deleteVendor(${v.id})">刪除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
updatePaginationControls(dataToRender.length);
|
||||
}
|
||||
|
||||
function updatePaginationControls(totalItems) {
|
||||
const totalPages = Math.ceil(totalItems / pageSize) || 1;
|
||||
const currentPageDisplay = document.getElementById("currentPageInfo");
|
||||
const totalPagesDisplay = document.getElementById("totalPagesInfo");
|
||||
const prevButton = document.getElementById("prevPageBtn");
|
||||
const nextButton = document.getElementById("nextPageBtn");
|
||||
|
||||
if (currentPageDisplay) currentPageDisplay.textContent = currentPage;
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = totalPages;
|
||||
|
||||
if (prevButton) prevButton.disabled = currentPage === 1;
|
||||
if (nextButton) nextButton.disabled = currentPage === totalPages || totalItems === 0;
|
||||
|
||||
if (currentPage > totalPages && totalPages > 0) {
|
||||
currentPage = totalPages;
|
||||
renderTable(currentFilteredData);
|
||||
} else if (totalItems === 0) {
|
||||
if (currentPageDisplay) currentPageDisplay.textContent = 1;
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
|
||||
if (prevButton) prevButton.disabled = true;
|
||||
if (nextButton) nextButton.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
currentFilteredData = vendors.filter(v =>
|
||||
v.name.toLowerCase().includes(nameInput)
|
||||
);
|
||||
currentPage = 1;
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
|
||||
function toggleAll(source) {
|
||||
const checkboxes = document.querySelectorAll(".row-checkbox");
|
||||
checkboxes.forEach(cb => cb.checked = source.checked);
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
if (!confirm("確定要刪除勾選的資料嗎?")) return;
|
||||
const selected = document.querySelectorAll(".row-checkbox:checked");
|
||||
if (selected.length === 0) {
|
||||
alert("請先勾選要刪除的資料");
|
||||
return;
|
||||
}
|
||||
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
|
||||
vendors = vendors.filter(v => !idsToDelete.includes(v.id));
|
||||
|
||||
const nameInput = document.getElementById("searchName").value.toLowerCase();
|
||||
currentFilteredData = vendors.filter(v =>
|
||||
v.name.toLowerCase().includes(nameInput)
|
||||
);
|
||||
|
||||
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
|
||||
if (currentPage > totalPages) {
|
||||
currentPage = totalPages;
|
||||
}
|
||||
if (currentFilteredData.length === 0) {
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
renderTable(currentFilteredData);
|
||||
document.getElementById("selectAll").checked = false;
|
||||
}
|
||||
|
||||
function deleteVendor(id) {
|
||||
if (!confirm("確定要刪除此筆資料嗎?")) return;
|
||||
vendors = vendors.filter(v => v.id !== id);
|
||||
|
||||
const nameInput = document.getElementById("searchName").value.toLowerCase();
|
||||
currentFilteredData = vendors.filter(v =>
|
||||
v.name.toLowerCase().includes(nameInput)
|
||||
);
|
||||
|
||||
const itemsOnCurrentPageAfterDelete = currentFilteredData.slice((currentPage - 1) * pageSize, currentPage * pageSize).length;
|
||||
const totalPages = Math.ceil(currentFilteredData.length / pageSize) || 1;
|
||||
|
||||
if (itemsOnCurrentPageAfterDelete === 0 && currentPage > 1) {
|
||||
currentPage--;
|
||||
} else if (currentPage > totalPages) {
|
||||
currentPage = totalPages;
|
||||
}
|
||||
if (currentFilteredData.length === 0) {
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
|
||||
// Initial load
|
||||
performSearch();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
272
Backstage/parcel_add.html
Normal file
272
Backstage/parcel_add.html
Normal file
@ -0,0 +1,272 @@
|
||||
<!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>
|
||||
/* Styles for the custom suggestions box - Retained as it's specific to this page's functionality */
|
||||
.suggestions-container {
|
||||
position: relative; /* Needed for absolute positioning of the box */
|
||||
}
|
||||
.suggestions-box {
|
||||
position: absolute;
|
||||
border: 1px solid #ced4da;
|
||||
border-top: none;
|
||||
max-height: 200px; /* Limit height and make it scrollable */
|
||||
overflow-y: auto;
|
||||
background-color: white;
|
||||
width: 100%; /* Match the width of the input field */
|
||||
z-index: 1000; /* Ensure it's above other elements */
|
||||
border-radius: 0 0 0.25rem 0.25rem;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.suggestion-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.suggestion-item:hover, .suggestion-item.active {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.suggestions-box:empty { /* Hide box if empty */
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">新增信件包裹</h2>
|
||||
|
||||
<form id="addPackageForm">
|
||||
<div class="mb-3">
|
||||
<label for="dateReceived" class="form-label">收件日期</label>
|
||||
<input type="date" class="form-control" id="dateReceived" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 suggestions-container">
|
||||
<label for="recipient" class="form-label">收件人 (可輸入搜尋)</label>
|
||||
<input type="text" class="form-control" id="recipient" placeholder="輸入收件人姓名開始搜尋" required autocomplete="off">
|
||||
<div id="suggestionsBox" class="suggestions-box">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="amountToCollect" class="form-label">代收金額</label>
|
||||
<input type="number" class="form-control" id="amountToCollect" placeholder="輸入代收金額 (若無則填0)" value="0" min="0" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="deliveryService" class="form-label">配送廠商</label>
|
||||
<select class="form-select" id="deliveryService" required>
|
||||
<option value="" disabled selected>請選擇配送廠商</option>
|
||||
<option value="黑貓宅急便">黑貓宅急便</option>
|
||||
<option value="郵局">郵局</option>
|
||||
<option value="7-11 交貨便">7-11 交貨便</option>
|
||||
<option value="全家店到店">全家店到店</option>
|
||||
<option value="OK便利店">OK便利店</option>
|
||||
<option value="萊爾富">萊爾富</option>
|
||||
<option value="嘉里大榮">嘉里大榮</option>
|
||||
<option value="新竹物流">新竹物流</option>
|
||||
<option value="宅配通">宅配通</option>
|
||||
<option value="Uber Connect">Uber Connect</option>
|
||||
<option value="Lalamove">Lalamove</option>
|
||||
<option value="其他">其他</option>
|
||||
</select>
|
||||
<input type="text" class="form-control mt-2" id="otherDeliveryService" placeholder="請輸入其他配送廠商名稱" style="display: none;">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="packageNumber" class="form-label">包裹編號</label>
|
||||
<input type="text" class="form-control" id="packageNumber" placeholder="輸入包裹追蹤編號" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">狀態</label>
|
||||
<select class="form-select" id="status" required>
|
||||
<option value="待領取" selected>待領取</option>
|
||||
<option value="已領取">已領取</option>
|
||||
<option value="已退回">已退回</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="notes" class="form-label">備註</label>
|
||||
<textarea class="form-control" id="notes" rows="3" placeholder="輸入其他備註事項 (選填)"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">儲存包裹</button>
|
||||
<a href="parcel_list.html" class="btn btn-secondary ms-2">返回列表</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// For hundreds of residents, this list ideally comes from a server
|
||||
const allResidents = [
|
||||
"林小安 (A101)", "陳大明 (B203)", "王小美 (C502)", "李四 (A102)", "張三 (D301)",
|
||||
"趙六 (B205)", "孫七 (E101)", "吳八金 (F405)", "鄭九玲 (G1201)", "周十全 (H707)",
|
||||
"劉一一 (A103)", "黃二二 (B204)", "蔡三三 (C503)", "許四四 (A104)", "洪五五 (D302)",
|
||||
"曾六六 (B206)", "蕭七七 (E102)", "葉八八 (F406)", "楊九九 (G1202)", "呂零零 (H708)",
|
||||
"高才 (A201)", "簡明 (B301)", "施美 (C601)", "溫柔 (A202)", "方正 (D401)",
|
||||
"管理委員會", "社區服務中心", "訪客登記處", "保全室", "清潔公司"
|
||||
];
|
||||
|
||||
const recipientInput = document.getElementById('recipient');
|
||||
const suggestionsBox = document.getElementById('suggestionsBox');
|
||||
let activeSuggestionIndex = -1;
|
||||
|
||||
recipientInput.addEventListener('input', function() {
|
||||
const inputText = this.value.toLowerCase();
|
||||
suggestionsBox.innerHTML = '';
|
||||
activeSuggestionIndex = -1;
|
||||
|
||||
if (inputText.length === 0) {
|
||||
suggestionsBox.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const filteredResidents = allResidents.filter(resident =>
|
||||
resident.toLowerCase().includes(inputText)
|
||||
);
|
||||
if (filteredResidents.length > 0) {
|
||||
filteredResidents.forEach((resident, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.classList.add('suggestion-item');
|
||||
item.textContent = resident;
|
||||
item.addEventListener('click', function() {
|
||||
recipientInput.value = resident;
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
});
|
||||
item.setAttribute('data-index', index);
|
||||
suggestionsBox.appendChild(item);
|
||||
});
|
||||
suggestionsBox.style.display = 'block';
|
||||
} else {
|
||||
suggestionsBox.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
recipientInput.addEventListener('keydown', function(e) {
|
||||
const items = suggestionsBox.querySelectorAll('.suggestion-item');
|
||||
if (items.length === 0 || suggestionsBox.style.display === 'none') return;
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
activeSuggestionIndex = (activeSuggestionIndex + 1) % items.length;
|
||||
updateActiveSuggestion(items);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
activeSuggestionIndex = (activeSuggestionIndex - 1 + items.length) % items.length;
|
||||
updateActiveSuggestion(items);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (activeSuggestionIndex > -1 && items[activeSuggestionIndex]) {
|
||||
items[activeSuggestionIndex].click();
|
||||
} else {
|
||||
document.getElementById('addPackageForm').dispatchEvent(new Event('submit', {cancelable: true}));
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
activeSuggestionIndex = -1;
|
||||
}
|
||||
});
|
||||
|
||||
function updateActiveSuggestion(items) {
|
||||
items.forEach(item => item.classList.remove('active'));
|
||||
if (items[activeSuggestionIndex]) {
|
||||
items[activeSuggestionIndex].classList.add('active');
|
||||
items[activeSuggestionIndex].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(event) {
|
||||
if (!recipientInput.contains(event.target) && !suggestionsBox.contains(event.target)) {
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
activeSuggestionIndex = -1;
|
||||
}
|
||||
});
|
||||
|
||||
// --- Delivery Service Dropdown Logic ---
|
||||
const deliveryServiceSelect = document.getElementById('deliveryService');
|
||||
const otherDeliveryServiceInput = document.getElementById('otherDeliveryService');
|
||||
|
||||
deliveryServiceSelect.addEventListener('change', function() {
|
||||
if (this.value === '其他') {
|
||||
otherDeliveryServiceInput.style.display = 'block';
|
||||
otherDeliveryServiceInput.required = true; // Make it required if "Other" is selected
|
||||
otherDeliveryServiceInput.focus();
|
||||
} else {
|
||||
otherDeliveryServiceInput.style.display = 'none';
|
||||
otherDeliveryServiceInput.required = false; // Not required if a predefined option is selected
|
||||
otherDeliveryServiceInput.value = ''; // Clear its value
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const dateReceivedInput = document.getElementById('dateReceived');
|
||||
if(dateReceivedInput) {
|
||||
dateReceivedInput.value = today;
|
||||
}
|
||||
// Ensure "Other" field is hidden and not required on initial load
|
||||
otherDeliveryServiceInput.style.display = 'none';
|
||||
otherDeliveryServiceInput.required = false;
|
||||
});
|
||||
|
||||
document.getElementById('addPackageForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let deliveryServiceValue = deliveryServiceSelect.value;
|
||||
if (deliveryServiceValue === '其他') {
|
||||
deliveryServiceValue = otherDeliveryServiceInput.value.trim();
|
||||
if (!deliveryServiceValue) {
|
||||
alert("請輸入「其他配送廠商」的名稱。");
|
||||
otherDeliveryServiceInput.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const newPackage = {
|
||||
id: Date.now(),
|
||||
dateReceived: document.getElementById('dateReceived').value,
|
||||
recipient: recipientInput.value,
|
||||
amountToCollect: parseFloat(document.getElementById('amountToCollect').value),
|
||||
deliveryService: deliveryServiceValue,
|
||||
packageNumber: document.getElementById('packageNumber').value,
|
||||
status: document.getElementById('status').value,
|
||||
notes: document.getElementById('notes').value,
|
||||
notificationCount: 0 // Assuming 0 for new packages
|
||||
};
|
||||
|
||||
console.log("新包裹資料:", newPackage);
|
||||
|
||||
let existingPackages = JSON.parse(localStorage.getItem('packages_data_temp')) || [];
|
||||
existingPackages.push(newPackage);
|
||||
localStorage.setItem('packages_data_temp', JSON.stringify(existingPackages));
|
||||
|
||||
alert("包裹資料已「模擬」儲存!\n收件人:" + newPackage.recipient + "\n包裹編號:" + newPackage.packageNumber);
|
||||
this.reset(); // Resets all form fields to their initial values
|
||||
|
||||
// Explicitly reset custom fields and state after form.reset()
|
||||
const dateReceivedInput = document.getElementById('dateReceived');
|
||||
if(dateReceivedInput) {
|
||||
dateReceivedInput.value = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
document.getElementById('status').value = "待領取"; // Default status
|
||||
document.getElementById('amountToCollect').value = "0"; // Default amount
|
||||
deliveryServiceSelect.value = ""; // Reset select to default placeholder
|
||||
otherDeliveryServiceInput.style.display = 'none';
|
||||
otherDeliveryServiceInput.required = false;
|
||||
otherDeliveryServiceInput.value = '';
|
||||
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
activeSuggestionIndex = -1;
|
||||
recipientInput.focus(); // Set focus back to recipient for quick new entry
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
332
Backstage/parcel_edit.html
Normal file
332
Backstage/parcel_edit.html
Normal file
@ -0,0 +1,332 @@
|
||||
<!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>
|
||||
/* Styles for the custom suggestions box - Retained as it's specific to this page's functionality */
|
||||
.suggestions-container {
|
||||
position: relative; /* Needed for absolute positioning of the box */
|
||||
}
|
||||
.suggestions-box {
|
||||
position: absolute;
|
||||
border: 1px solid #ced4da;
|
||||
border-top: none;
|
||||
max-height: 200px; /* Limit height and make it scrollable */
|
||||
overflow-y: auto;
|
||||
background-color: white;
|
||||
width: 100%; /* Match the width of the input field */
|
||||
z-index: 1000; /* Ensure it's above other elements */
|
||||
border-radius: 0 0 0.25rem 0.25rem;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.suggestion-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.suggestion-item:hover, .suggestion-item.active {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.suggestions-box:empty { /* Hide box if empty */
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">編輯信件包裹 (測試模式)</h2>
|
||||
|
||||
<form id="editPackageForm">
|
||||
<input type="hidden" id="packageId" name="packageId">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="dateReceived" class="form-label">收件日期</label>
|
||||
<input type="date" class="form-control" id="dateReceived" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 suggestions-container">
|
||||
<label for="recipient" class="form-label">收件人 (可輸入搜尋)</label>
|
||||
<input type="text" class="form-control" id="recipient" placeholder="輸入收件人姓名開始搜尋" required autocomplete="off">
|
||||
<div id="suggestionsBox" class="suggestions-box">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="amountToCollect" class="form-label">代收金額</label>
|
||||
<input type="number" class="form-control" id="amountToCollect" placeholder="輸入代收金額 (若無則填0)" value="0" min="0" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="deliveryService" class="form-label">配送廠商</label>
|
||||
<select class="form-select" id="deliveryService" required>
|
||||
<option value="" disabled>請選擇配送廠商</option>
|
||||
<option value="黑貓宅急便">黑貓宅急便</option>
|
||||
<option value="郵局">郵局</option>
|
||||
<option value="7-11 交貨便">7-11 交貨便</option>
|
||||
<option value="全家店到店">全家店到店</option>
|
||||
<option value="OK便利店">OK便利店</option>
|
||||
<option value="萊爾富">萊爾富</option>
|
||||
<option value="嘉里大榮">嘉里大榮</option>
|
||||
<option value="新竹物流">新竹物流</option>
|
||||
<option value="宅配通">宅配通</option>
|
||||
<option value="Uber Connect">Uber Connect</option>
|
||||
<option value="Lalamove">Lalamove</option>
|
||||
<option value="其他">其他</option>
|
||||
</select>
|
||||
<input type="text" class="form-control mt-2" id="otherDeliveryService" placeholder="請輸入其他配送廠商名稱" style="display: none;">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="packageNumber" class="form-label">包裹編號</label>
|
||||
<input type="text" class="form-control" id="packageNumber" placeholder="輸入包裹追蹤編號" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">狀態</label>
|
||||
<select class="form-select" id="status" required>
|
||||
<option value="待領取">待領取</option>
|
||||
<option value="已領取">已領取</option>
|
||||
<option value="已退回">已退回</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-9">
|
||||
<label for="notes" class="form-label">備註</label>
|
||||
<textarea class="form-control" id="notes" rows="3" placeholder="輸入其他備註事項 (選填)"></textarea>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="notificationCountDisplay" class="form-label">通知次數</label>
|
||||
<input type="text" class="form-control" id="notificationCountDisplay" readonly disabled>
|
||||
<button type="button" class="btn btn-outline-info btn-sm mt-2 w-100" id="resendNotificationBtn">再寄一次到貨通知</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">更新包裹</button>
|
||||
<a href="parcel_list.html" class="btn btn-secondary ms-2">返回列表</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentEditingPackage = {};
|
||||
const allResidents = [
|
||||
"林小安 (A101)", "陳大明 (B203)", "王小美 (C502)", "李四 (A102)", "張三 (D301)",
|
||||
"趙六 (B205)", "孫七 (E101)", "吳八金 (F405)", "鄭九玲 (G1201)", "周十全 (H707)",
|
||||
"劉一一 (A103)", "黃二二 (B204)", "蔡三三 (C503)", "許四四 (A104)", "洪五五 (D302)",
|
||||
"曾六六 (B206)", "蕭七七 (E102)", "葉八八 (F406)", "楊九九 (G1202)", "呂零零 (H708)",
|
||||
"高才 (A201)", "簡明 (B301)", "施美 (C601)", "溫柔 (A202)", "方正 (D401)",
|
||||
"管理委員會", "社區服務中心", "訪客登記處", "保全室", "清潔公司", "測試用戶 (Z999)"
|
||||
];
|
||||
|
||||
const recipientInput = document.getElementById('recipient');
|
||||
const suggestionsBox = document.getElementById('suggestionsBox');
|
||||
const notificationCountDisplay = document.getElementById('notificationCountDisplay');
|
||||
const resendNotificationBtn = document.getElementById('resendNotificationBtn');
|
||||
const deliveryServiceSelectEdit = document.getElementById('deliveryService');
|
||||
const otherDeliveryServiceInputEdit = document.getElementById('otherDeliveryService');
|
||||
let activeSuggestionIndex = -1;
|
||||
|
||||
recipientInput.addEventListener('input', function() {
|
||||
const inputText = this.value.toLowerCase();
|
||||
suggestionsBox.innerHTML = '';
|
||||
activeSuggestionIndex = -1;
|
||||
if (inputText.length === 0) {
|
||||
suggestionsBox.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const filteredResidents = allResidents.filter(resident =>
|
||||
resident.toLowerCase().includes(inputText)
|
||||
);
|
||||
if (filteredResidents.length > 0) {
|
||||
filteredResidents.forEach((resident, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.classList.add('suggestion-item');
|
||||
item.textContent = resident;
|
||||
item.addEventListener('click', function() {
|
||||
recipientInput.value = resident;
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
});
|
||||
item.setAttribute('data-index', index);
|
||||
suggestionsBox.appendChild(item);
|
||||
});
|
||||
suggestionsBox.style.display = 'block';
|
||||
} else {
|
||||
suggestionsBox.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
recipientInput.addEventListener('keydown', function(e) {
|
||||
const items = suggestionsBox.querySelectorAll('.suggestion-item');
|
||||
if (items.length === 0 || suggestionsBox.style.display === 'none') return;
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
activeSuggestionIndex = (activeSuggestionIndex + 1) % items.length;
|
||||
updateActiveSuggestion(items);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
activeSuggestionIndex = (activeSuggestionIndex - 1 + items.length) % items.length;
|
||||
updateActiveSuggestion(items);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (activeSuggestionIndex > -1 && items[activeSuggestionIndex]) {
|
||||
items[activeSuggestionIndex].click();
|
||||
} else {
|
||||
document.getElementById('editPackageForm').dispatchEvent(new Event('submit', {cancelable: true}));
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
activeSuggestionIndex = -1;
|
||||
}
|
||||
});
|
||||
|
||||
function updateActiveSuggestion(items) {
|
||||
items.forEach(item => item.classList.remove('active'));
|
||||
if (items[activeSuggestionIndex]) {
|
||||
items[activeSuggestionIndex].classList.add('active');
|
||||
items[activeSuggestionIndex].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(event) {
|
||||
if (!recipientInput.contains(event.target) && !suggestionsBox.contains(event.target)) {
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
activeSuggestionIndex = -1;
|
||||
}
|
||||
});
|
||||
|
||||
deliveryServiceSelectEdit.addEventListener('change', function() {
|
||||
if (this.value === '其他') {
|
||||
otherDeliveryServiceInputEdit.style.display = 'block';
|
||||
otherDeliveryServiceInputEdit.required = true;
|
||||
otherDeliveryServiceInputEdit.focus();
|
||||
} else {
|
||||
otherDeliveryServiceInputEdit.style.display = 'none';
|
||||
otherDeliveryServiceInputEdit.required = false;
|
||||
otherDeliveryServiceInputEdit.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
currentEditingPackage = {
|
||||
id: 99901,
|
||||
dateReceived: "2025-05-20",
|
||||
recipient: "測試用戶 (Z999)",
|
||||
amountToCollect: 150,
|
||||
deliveryService: "測試快遞公司", // This will be handled by the logic below
|
||||
packageNumber: "TESTPKG00123XYZ",
|
||||
status: "已通知住戶",
|
||||
notes: "這是一筆用於編輯頁面測試的包裹備註。",
|
||||
notificationCount: 1
|
||||
};
|
||||
|
||||
// Attempt to retrieve from localStorage first
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const packageIdFromUrl = parseInt(urlParams.get('id'));
|
||||
if (packageIdFromUrl) {
|
||||
let existingPackages = JSON.parse(localStorage.getItem('packages_data_temp')) || [];
|
||||
const foundPackage = existingPackages.find(p => p.id === packageIdFromUrl);
|
||||
if (foundPackage) {
|
||||
currentEditingPackage = { ...foundPackage }; // Make a copy to avoid modifying the stored object directly
|
||||
} else {
|
||||
alert("找不到指定的包裹資料!將使用範例資料。 ID: " + packageIdFromUrl);
|
||||
// Fallback to sample if not found, or handle error differently
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.getElementById('packageId').value = currentEditingPackage.id;
|
||||
document.getElementById('dateReceived').value = currentEditingPackage.dateReceived;
|
||||
recipientInput.value = currentEditingPackage.recipient;
|
||||
document.getElementById('amountToCollect').value = currentEditingPackage.amountToCollect;
|
||||
document.getElementById('packageNumber').value = currentEditingPackage.packageNumber;
|
||||
document.getElementById('status').value = currentEditingPackage.status;
|
||||
document.getElementById('notes').value = currentEditingPackage.notes;
|
||||
notificationCountDisplay.value = currentEditingPackage.notificationCount || 0;
|
||||
|
||||
// Populate delivery service dropdown
|
||||
const deliveryServiceValueFromData = currentEditingPackage.deliveryService;
|
||||
let isOtherService = true;
|
||||
if (deliveryServiceValueFromData) {
|
||||
for (let i = 0; i < deliveryServiceSelectEdit.options.length; i++) {
|
||||
if (deliveryServiceSelectEdit.options[i].value === deliveryServiceValueFromData) {
|
||||
deliveryServiceSelectEdit.value = deliveryServiceValueFromData;
|
||||
isOtherService = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isOtherService) {
|
||||
deliveryServiceSelectEdit.value = '其他';
|
||||
otherDeliveryServiceInputEdit.value = deliveryServiceValueFromData;
|
||||
otherDeliveryServiceInputEdit.style.display = 'block';
|
||||
otherDeliveryServiceInputEdit.required = true;
|
||||
} else {
|
||||
otherDeliveryServiceInputEdit.style.display = 'none';
|
||||
otherDeliveryServiceInputEdit.required = false;
|
||||
}
|
||||
} else {
|
||||
deliveryServiceSelectEdit.value = ""; // Set to default "請選擇配送廠商"
|
||||
otherDeliveryServiceInputEdit.style.display = 'none';
|
||||
otherDeliveryServiceInputEdit.required = false;
|
||||
}
|
||||
});
|
||||
|
||||
resendNotificationBtn.addEventListener('click', function() {
|
||||
currentEditingPackage.notificationCount = (parseInt(currentEditingPackage.notificationCount) || 0) + 1;
|
||||
notificationCountDisplay.value = currentEditingPackage.notificationCount;
|
||||
alert(`已模擬重新發送第 ${currentEditingPackage.notificationCount} 次到貨通知給 ${currentEditingPackage.recipient}!`);
|
||||
});
|
||||
|
||||
document.getElementById('editPackageForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let updatedDeliveryServiceValue = deliveryServiceSelectEdit.value;
|
||||
if (updatedDeliveryServiceValue === '其他') {
|
||||
updatedDeliveryServiceValue = otherDeliveryServiceInputEdit.value.trim();
|
||||
if (!updatedDeliveryServiceValue) {
|
||||
alert("請輸入「其他配送廠商」的名稱。");
|
||||
otherDeliveryServiceInputEdit.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentEditingPackage.dateReceived = document.getElementById('dateReceived').value;
|
||||
currentEditingPackage.recipient = recipientInput.value;
|
||||
currentEditingPackage.amountToCollect = parseFloat(document.getElementById('amountToCollect').value);
|
||||
currentEditingPackage.deliveryService = updatedDeliveryServiceValue;
|
||||
currentEditingPackage.packageNumber = document.getElementById('packageNumber').value;
|
||||
currentEditingPackage.status = document.getElementById('status').value;
|
||||
currentEditingPackage.notes = document.getElementById('notes').value;
|
||||
// notificationCount is already part of currentEditingPackage
|
||||
|
||||
console.log("更新後包裹資料 (測試模式):", currentEditingPackage);
|
||||
|
||||
let existingPackages = JSON.parse(localStorage.getItem('packages_data_temp')) || [];
|
||||
const packageIndex = existingPackages.findIndex(p => p.id === currentEditingPackage.id);
|
||||
|
||||
if (packageIndex > -1) {
|
||||
existingPackages[packageIndex] = { ...currentEditingPackage };
|
||||
localStorage.setItem('packages_data_temp', JSON.stringify(existingPackages));
|
||||
alert("包裹資料已「模擬更新」至 LocalStorage!\n收件人:" + currentEditingPackage.recipient + "\n通知次數:" + currentEditingPackage.notificationCount);
|
||||
} else {
|
||||
// This case should ideally not happen if we are editing an existing one,
|
||||
// but as a fallback, it adds it.
|
||||
existingPackages.push({ ...currentEditingPackage });
|
||||
localStorage.setItem('packages_data_temp', JSON.stringify(existingPackages));
|
||||
alert("包裹資料 (ID: " + currentEditingPackage.id + ") 在列表中未找到,已「模擬新增」至 LocalStorage!\n通知次數:" + currentEditingPackage.notificationCount);
|
||||
}
|
||||
suggestionsBox.innerHTML = '';
|
||||
suggestionsBox.style.display = 'none';
|
||||
activeSuggestionIndex = -1;
|
||||
// Optionally redirect or give other feedback
|
||||
// window.location.href = 'parcel_list.html';
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
268
Backstage/parcel_list.html
Normal file
268
Backstage/parcel_list.html
Normal file
@ -0,0 +1,268 @@
|
||||
<!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>
|
||||
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">
|
||||
<h2 class="mb-4">信件包裹管理</h2>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-auto"> <a href="parcel_add.html" class="btn btn-outline-success rounded-pill px-3">新增包裹</a> </div>
|
||||
<div class="col-auto"> <button class="btn btn-outline-secondary rounded-pill px-3" onclick="exportPackages()">匯出列表</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
<input type="text" id="searchRecipient" class="form-control" placeholder="搜尋收件人">
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3 col-lg-3">
|
||||
<input type="text" id="searchPackageNumber" class="form-control" placeholder="搜尋包裹編號">
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-2 col-lg-2">
|
||||
<select id="searchStatus" class="form-select">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
|
||||
<th>編號</th>
|
||||
<th>收件日期</th>
|
||||
<th>收件人</th>
|
||||
<th>代收金額</th>
|
||||
<th>配送廠商</th>
|
||||
<th>包裹編號</th>
|
||||
<th>狀態</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="packageTable">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<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; // Number of items per page
|
||||
let currentPage = 1;
|
||||
let currentFilteredData = [];
|
||||
|
||||
// Sample package data. In a real application, this would come from a server.
|
||||
let packages_data = [
|
||||
{ id: 1, dateReceived: "2025/04/16", recipient: "林小安", amountToCollect: 250, deliveryService: "宅配 - 7-11 交貨便", packageNumber: "P20240416001", status: "待領取", notes: "易碎品" },
|
||||
{ id: 2, dateReceived: "2025/04/14", recipient: "陳大明", amountToCollect: 120, deliveryService: "黑貓宅急便", packageNumber: "P20240414002", status: "待領取", notes: "" },
|
||||
{ id: 3, dateReceived: "2025/04/10", recipient: "王小美", amountToCollect: 0, deliveryService: "郵局掛號", packageNumber: "P20240410001", status: "已領取", notes: "文件" },
|
||||
{ id: 4, dateReceived: "2025/04/09", recipient: "李四", amountToCollect: 500, deliveryService: "嘉里大榮", packageNumber: "P20240409003", status: "待領取", notes: "" },
|
||||
{ id: 5, dateReceived: "2025/04/08", recipient: "張三", amountToCollect: 0, deliveryService: "新竹物流", packageNumber: "P20240408001", status: "已退回", notes: "查無此人" },
|
||||
{ id: 6, dateReceived: "2025/04/17", recipient: "趙六", amountToCollect: 80, deliveryService: "店到店", packageNumber: "P20240417005", status: "待領取", notes: "" },
|
||||
{ id: 7, dateReceived: "2025/04/18", recipient: "孫七", amountToCollect: 1000, deliveryService: "Uber Connect", packageNumber: "P20240418001", status: "待領取", notes: "代收金額高" }
|
||||
];
|
||||
|
||||
function renderTable(dataToRender) {
|
||||
const tbody = document.getElementById("packageTable");
|
||||
tbody.innerHTML = "";
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const pageData = dataToRender.slice(start, start + pageSize);
|
||||
|
||||
pageData.forEach(pkg => {
|
||||
const row = document.createElement("tr");
|
||||
let statusBadgeClass = 'bg-secondary'; // Default badge
|
||||
let statusText = pkg.status;
|
||||
switch (pkg.status) {
|
||||
case '待領取':
|
||||
statusBadgeClass = 'bg-warning text-dark';
|
||||
break;
|
||||
case '已領取':
|
||||
statusBadgeClass = 'bg-success';
|
||||
break;
|
||||
case '已退回':
|
||||
statusBadgeClass = 'bg-danger';
|
||||
break;
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td><input type="checkbox" class="row-checkbox" data-id="${pkg.id}"></td>
|
||||
<td>${pkg.id}</td>
|
||||
<td>${pkg.dateReceived}</td>
|
||||
<td>${pkg.recipient}</td>
|
||||
<td>$${pkg.amountToCollect}</td>
|
||||
<td>${pkg.deliveryService}</td>
|
||||
<td>${pkg.packageNumber}</td>
|
||||
<td><span class="badge ${statusBadgeClass}">${statusText}</span></td>
|
||||
<td>
|
||||
<a href="parcel_edit.html?id=${pkg.id}" class="btn btn-outline-secondary btn-sm">編輯</a>
|
||||
<button class="btn btn-outline-danger btn-sm" onclick="deletePackage(${pkg.id})">刪除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
updatePaginationControls(dataToRender.length);
|
||||
const selectAllCheckbox = document.getElementById("selectAll");
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationControls(totalItems) {
|
||||
const totalPages = Math.ceil(totalItems / pageSize) || 1;
|
||||
const currentPageDisplay = document.getElementById("currentPageInfo");
|
||||
const totalPagesDisplay = document.getElementById("totalPagesInfo");
|
||||
const prevButton = document.getElementById("prevPageBtn");
|
||||
const nextButton = document.getElementById("nextPageBtn");
|
||||
|
||||
if (currentPageDisplay) currentPageDisplay.textContent = currentPage;
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = totalPages;
|
||||
|
||||
if (prevButton) prevButton.disabled = currentPage === 1;
|
||||
if (nextButton) nextButton.disabled = currentPage === totalPages || totalItems === 0;
|
||||
|
||||
if (currentPage > totalPages && totalPages > 0) {
|
||||
currentPage = totalPages;
|
||||
renderTable(currentFilteredData);
|
||||
} else if (totalItems === 0) {
|
||||
if (currentPageDisplay) currentPageDisplay.textContent = 1;
|
||||
if (totalPagesDisplay) totalPagesDisplay.textContent = 1;
|
||||
if (prevButton) prevButton.disabled = true;
|
||||
if (nextButton) nextButton.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
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 recipientInput = document.getElementById("searchRecipient").value.toLowerCase();
|
||||
const packageNumberInput = document.getElementById("searchPackageNumber").value.toLowerCase();
|
||||
const statusInput = document.getElementById("searchStatus").value;
|
||||
|
||||
currentFilteredData = packages_data.filter(pkg =>
|
||||
pkg.recipient.toLowerCase().includes(recipientInput) &&
|
||||
pkg.packageNumber.toLowerCase().includes(packageNumberInput) &&
|
||||
(statusInput === "" || pkg.status === statusInput)
|
||||
);
|
||||
currentPage = 1;
|
||||
renderTable(currentFilteredData);
|
||||
}
|
||||
|
||||
function toggleAll(source) {
|
||||
const checkboxes = document.querySelectorAll(".row-checkbox");
|
||||
checkboxes.forEach(cb => cb.checked = source.checked);
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
if (!confirm("確定要刪除勾選的包裹紀錄嗎?")) return;
|
||||
const selected = document.querySelectorAll(".row-checkbox:checked");
|
||||
if (selected.length === 0) {
|
||||
alert("請先勾選要刪除的包裹紀錄");
|
||||
return;
|
||||
}
|
||||
const idsToDelete = Array.from(selected).map(cb => parseInt(cb.dataset.id));
|
||||
packages_data = packages_data.filter(pkg => !idsToDelete.includes(pkg.id));
|
||||
|
||||
performSearch(); // Re-filter and re-render
|
||||
}
|
||||
|
||||
function deletePackage(id) {
|
||||
if (!confirm("確定要刪除此包裹紀錄嗎?")) return;
|
||||
packages_data = packages_data.filter(pkg => pkg.id !== id);
|
||||
|
||||
performSearch(); // Re-filter and re-render
|
||||
}
|
||||
|
||||
function exportPackages() {
|
||||
const recipientInput = document.getElementById("searchRecipient").value.toLowerCase();
|
||||
const packageNumberInput = document.getElementById("searchPackageNumber").value.toLowerCase();
|
||||
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;
|
||||
}
|
||||
|
||||
if (dataToExport.length === 0) {
|
||||
alert("沒有資料可匯出。");
|
||||
return;
|
||||
}
|
||||
|
||||
let csvContent = "data:text/csv;charset=utf-8,\uFEFF"; // \uFEFF for BOM
|
||||
csvContent += "編號,收件日期,收件人,代收金額,配送廠商,包裹編號,狀態,備註\n";
|
||||
dataToExport.forEach(pkg => {
|
||||
const cleanRecipient = `"${pkg.recipient.replace(/"/g, '""')}"`;
|
||||
const cleanService = `"${pkg.deliveryService.replace(/"/g, '""')}"`;
|
||||
const cleanPkgNum = `"${pkg.packageNumber.replace(/"/g, '""')}"`;
|
||||
const cleanStatus = `"${pkg.status.replace(/"/g, '""')}"`;
|
||||
const cleanNotes = `"${pkg.notes ? pkg.notes.replace(/"/g, '""') : ''}"`;
|
||||
|
||||
const row = [pkg.id, pkg.dateReceived, cleanRecipient, pkg.amountToCollect, cleanService, cleanPkgNum, cleanStatus, cleanNotes];
|
||||
csvContent += row.join(",") + "\n";
|
||||
});
|
||||
|
||||
const encodedUri = encodeURI(csvContent);
|
||||
const link = document.createElement("a");
|
||||
link.setAttribute("href", encodedUri);
|
||||
link.setAttribute("download", "packages_export.csv");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
alert(`準備匯出 ${dataToExport.length} 筆包裹資料(${recipientInput || packageNumberInput || statusInput ? "依搜尋條件" : "全部"})`);
|
||||
}
|
||||
|
||||
// Initial load: apply no filters (show all) and render
|
||||
performSearch();
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -205,7 +205,6 @@
|
||||
<li onclick="requestNavigation('repair_firm_list.html', this)">報修廠商</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li onclick="requestNavigation('feedback_list.html', this)">
|
||||
<div class="li-content">
|
||||
<span>意見回饋</span>
|
||||
@ -213,7 +212,24 @@
|
||||
<span class="submenu-arrow"> </span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li onclick="requestNavigation('activity_list.html', this)">
|
||||
<div class="li-content">
|
||||
<span>社區活動</span>
|
||||
<span class="badge" data-count="99">99</span>
|
||||
<span class="submenu-arrow"> </span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="has-submenu">
|
||||
<div class="submenu-toggle" onclick="toggleSubmenu(this)">
|
||||
<span>郵件管理</span>
|
||||
<span class="parent-badge"></span>
|
||||
<span class="submenu-arrow">▼</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li onclick="requestNavigation('parcel_list.html', this)">郵件管理</li>
|
||||
<li onclick="requestNavigation('mailing_firm_list.html', this)">郵寄廠商</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li onclick="requestNavigation('some_page6.html', this)">報表匯出</li>
|
||||
<li onclick="requestNavigation('some_page7.html', this)">設定</li>
|
||||
</ul>
|
||||
|
@ -27,9 +27,13 @@
|
||||
<input type="text" class="form-control" id="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="date" class="form-label">日期與時間</label>
|
||||
<label for="date" class="form-label">開始時間</label>
|
||||
<input type="datetime-local" class="form-control" id="date" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="endDate" class="form-label">結束時間</label>
|
||||
<input type="datetime-local" class="form-control" id="endDate" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="location" class="form-label">地點</label>
|
||||
<input type="text" class="form-control" id="location" required>
|
||||
@ -42,19 +46,35 @@
|
||||
<label for="poster" class="form-label">上傳活動海報</label>
|
||||
<input type="file" class="form-control" id="poster" accept="image/*">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="mb-3">
|
||||
<label for="needRegister" class="form-label">是否需統計報名人數?</label>
|
||||
<select id="needRegister" class="form-select">
|
||||
<option value="否" selected>否</option>
|
||||
<option value="是">是</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="repeatOption" class="form-label">是否重複舉辦</label>
|
||||
<select id="repeatOption" class="form-select" onchange="toggleRepeatFields()">
|
||||
<option value="none" selected>否</option>
|
||||
<option value="weekly">每週一次</option>
|
||||
<option value="biweekly">每兩週一次</option>
|
||||
<option value="monthly">每月一次</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 d-none" id="repeatCountGroup">
|
||||
<label for="repeatCount" class="form-label">重複次數(不含首次)</label>
|
||||
<input type="number" class="form-control" id="repeatCount" min="1" value="1">
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-success">送出申請</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<br/><br/><br/><br/><br/>
|
||||
|
||||
<nav class="navbar fixed-bottom navbar-light bg-light border-top">
|
||||
<div class="container justify-content-around">
|
||||
@ -66,17 +86,41 @@
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
function toggleRepeatFields() {
|
||||
const option = document.getElementById('repeatOption').value;
|
||||
const group = document.getElementById('repeatCountGroup');
|
||||
group.classList.toggle('d-none', option === 'none');
|
||||
}
|
||||
|
||||
function submitActivity(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const title = document.getElementById('title').value;
|
||||
const date = document.getElementById('date').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
const location = document.getElementById('location').value;
|
||||
const description = document.getElementById('description').value;
|
||||
const poster = document.getElementById('poster').files[0];
|
||||
const needRegister = document.getElementById('needRegister').value;
|
||||
const repeatOption = document.getElementById('repeatOption').value;
|
||||
const repeatCount = document.getElementById('repeatCount').value;
|
||||
|
||||
if (new Date(endDate) <= new Date(date)) {
|
||||
alert("結束時間必須晚於開始時間!");
|
||||
return;
|
||||
}
|
||||
|
||||
let message = `活動申請已送出\n名稱:${title}\n開始時間:${date}\n結束時間:${endDate}\n地點:${location}\n描述:${description}\n需報名統計:${needRegister}`;
|
||||
|
||||
if (repeatOption !== 'none') {
|
||||
const optionMap = {
|
||||
weekly: '每週',
|
||||
biweekly: '每兩週',
|
||||
monthly: '每月'
|
||||
};
|
||||
message += `\n重複設定:${optionMap[repeatOption]},共重複 ${repeatCount} 次`;
|
||||
}
|
||||
|
||||
let message = `活動申請已送出\n名稱:${title}\n時間:${date}\n地點:${location}\n描述:${description}\n需報名統計:${needRegister}`;
|
||||
if (poster) {
|
||||
message += `\n檔案名稱:${poster.name}`;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user