Files
employee_votes/templates/menu_form.html
2025-09-17 15:20:01 +08:00

550 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Menu Items 表單維護系統</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.container {
background: white;
border-radius: 15px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
margin-top: 2rem;
margin-bottom: 2rem;
padding: 2rem;
}
.navbar-brand {
font-weight: bold;
color: #fff !important;
}
.card {
border: none;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.btn-primary {
background: linear-gradient(45deg, #667eea, #764ba2);
border: none;
border-radius: 25px;
padding: 10px 25px;
font-weight: 600;
}
.btn-primary:hover {
background: linear-gradient(45deg, #5a6fd8, #6a4190);
transform: translateY(-2px);
}
.btn-outline-primary {
border-color: #667eea;
color: #667eea;
border-radius: 25px;
}
.btn-outline-primary:hover {
background: #667eea;
color: white;
}
.form-control, .form-select {
border-radius: 10px;
border: 2px solid #e9ecef;
padding: 12px 15px;
transition: all 0.3s ease;
}
.form-control:focus, .form-select:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.table th {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
border: none;
}
.status-badge {
font-size: 0.8rem;
padding: 0.5rem 1rem;
border-radius: 20px;
font-weight: 600;
}
.active-status {
background: linear-gradient(45deg, #48bb78, #38a169);
color: white;
}
.inactive-status {
background: linear-gradient(45deg, #f56565, #e53e3e);
color: white;
}
.loading {
display: none;
text-align: center;
padding: 3rem;
}
.toast {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
border-radius: 10px;
}
.section-title {
color: #667eea;
font-weight: 700;
margin-bottom: 1.5rem;
border-bottom: 3px solid #667eea;
padding-bottom: 0.5rem;
}
.action-buttons .btn {
margin: 0 5px;
border-radius: 20px;
}
</style>
</head>
<body>
<!-- 導航欄 -->
<nav class="navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<i class="fas fa-utensils me-2"></i>
Menu Items 表單維護系統
</a>
</div>
</nav>
<div class="container">
<!-- 表單操作區 -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h4 class="section-title">
<i class="fas fa-pen-to-square me-2"></i>
菜單資料表單
</h4>
<form id="menuForm" class="row g-3">
<input type="hidden" id="formId" name="id">
<div class="col-md-6">
<label class="form-label fw-bold">
<i class="fas fa-utensil-spoon me-2"></i>
主菜單名稱 *
</label>
<input type="text" class="form-control" id="mainCourse" name="main_course"
placeholder="請輸入主菜單名稱" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">
<i class="fas fa-carrot me-2"></i>
副菜單名稱
</label>
<input type="text" class="form-control" id="sideDish" name="side_dish"
placeholder="請輸入副菜單名稱(選填)">
</div>
<div class="col-md-6">
<label class="form-label fw-bold">
<i class="fas fa-ice-cream me-2"></i>
甜點/湯品
</label>
<input type="text" class="form-control" id="addon" name="addon"
placeholder="請輸入甜點或湯品名稱(選填)">
</div>
<div class="col-md-6">
<label class="form-label fw-bold">
<i class="fas fa-calendar-alt me-2"></i>
可訂餐日期 *
</label>
<input type="date" class="form-control" id="orderDate" name="Order_Date" required>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">
<i class="fas fa-toggle-on me-2"></i>
狀態
</label>
<select class="form-select" id="isActive" name="is_active">
<option value="true">啟用</option>
<option value="false">停用</option>
</select>
</div>
<div class="col-12 mt-4">
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button type="button" class="btn btn-outline-secondary me-md-2" onclick="resetForm()">
<i class="fas fa-undo me-2"></i>
重置表單
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane me-2"></i>
送出表單
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- 資料操作區 -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="section-title mb-0">
<i class="fas fa-list me-2"></i>
現有菜單資料
</h4>
<button class="btn btn-outline-primary" onclick="loadData()">
<i class="fas fa-sync-alt me-2"></i>
重新載入
</button>
</div>
<!-- 搜尋篩選 -->
<div class="row g-3 mb-4">
<div class="col-md-4">
<input type="text" class="form-control" id="searchMainCourse"
placeholder="搜尋主菜單...">
</div>
<div class="col-md-3">
<input type="text" class="form-control" id="searchSideDish"
placeholder="搜尋副菜單...">
</div>
<div class="col-md-3">
<select class="form-select" id="searchStatus">
<option value="">全部狀態</option>
<option value="true">啟用</option>
<option value="false">停用</option>
</select>
</div>
<div class="col-md-2">
<button class="btn btn-primary w-100" onclick="searchItems()">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<!-- 資料表格 -->
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>主菜單</th>
<th>副菜單</th>
<th>甜點/湯品</th>
<th>訂餐日期</th>
<th>狀態</th>
<th>操作</th>
</tr>
</thead>
<tbody id="dataTable">
<!-- 資料將透過JavaScript動態載入 -->
</tbody>
</table>
</div>
<div id="loading" class="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">載入中...</span>
</div>
<p class="mt-2">資料載入中...</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Toast通知 -->
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">
<i class="fas fa-bell me-2"></i>
系統訊息
</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body" id="toastMessage"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
const API_BASE_URL = 'http://127.0.0.1:5000/v1/menu_items';
let toast = null;
// 初始化頁面
document.addEventListener('DOMContentLoaded', function() {
toast = new bootstrap.Toast(document.querySelector('.toast'));
loadData();
// 表單提交事件
document.getElementById('menuForm').addEventListener('submit', function(e) {
e.preventDefault();
submitForm();
});
// 設定今天為預設日期
const today = new Date().toISOString().split('T')[0];
document.getElementById('orderDate').value = today;
});
// 顯示通知
function showToast(message, type = 'info') {
const toastElement = document.querySelector('.toast');
toastElement.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info');
switch(type) {
case 'success':
toastElement.classList.add('bg-success', 'text-white');
break;
case 'error':
toastElement.classList.add('bg-danger', 'text-white');
break;
case 'warning':
toastElement.classList.add('bg-warning', 'text-dark');
break;
default:
toastElement.classList.add('bg-info', 'text-white');
}
document.getElementById('toastMessage').textContent = message;
toast.show();
}
// 載入資料
async function loadData() {
showLoading(true);
try {
const response = await fetch(API_BASE_URL);
const result = await response.json();
if (result.status === 'success') {
renderTable(result.data);
showToast('資料載入成功', 'success');
} else {
showToast('載入資料失敗: ' + result.message, 'error');
}
} catch (error) {
showToast('網路錯誤: ' + error.message, 'error');
}
showLoading(false);
}
// 搜尋資料
async function searchItems() {
showLoading(true);
const mainCourse = document.getElementById('searchMainCourse').value;
const sideDish = document.getElementById('searchSideDish').value;
const status = document.getElementById('searchStatus').value;
let url = API_BASE_URL + '?';
if (mainCourse) url += `main_course=${encodeURIComponent(mainCourse)}&`;
if (sideDish) url += `side_dish=${encodeURIComponent(sideDish)}&`;
if (status) url += `is_active=${status}&`;
try {
const response = await fetch(url);
const result = await response.json();
if (result.status === 'success') {
renderTable(result.data);
showToast(`找到 ${result.data.length} 筆資料`, 'success');
} else {
showToast('搜尋失敗: ' + result.message, 'error');
}
} catch (error) {
showToast('搜尋錯誤: ' + error.message, 'error');
}
showLoading(false);
}
// 渲染表格
function renderTable(data) {
const tbody = document.getElementById('dataTable');
tbody.innerHTML = '';
if (data.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="text-center text-muted py-5">
<i class="fas fa-inbox fa-3x mb-3"></i>
<br>
暫無資料,請新增菜單項目
</td>
</tr>
`;
return;
}
data.forEach(item => {
const row = `
<tr>
<td class="fw-bold">${item.Id}</td>
<td>${item.main_course || '-'}</td>
<td>${item.side_dish || '-'}</td>
<td>${item.addon || '-'}</td>
<td>${item.Order_Date || '-'}</td>
<td>
<span class="status-badge ${item.is_active ? 'active-status' : 'inactive-status'}">
<i class="fas ${item.is_active ? 'fa-check-circle' : 'fa-times-circle'} me-1"></i>
${item.is_active ? '啟用' : '停用'}
</span>
</td>
<td>
<div class="action-buttons">
<button class="btn btn-sm btn-warning" onclick="editItem(${item.Id})"
title="編輯">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="deleteItem(${item.Id})"
title="刪除">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
`;
tbody.innerHTML += row;
});
}
// 顯示/隱藏載入動畫
function showLoading(show) {
document.getElementById('loading').style.display = show ? 'block' : 'none';
document.querySelector('.table-responsive').style.display = show ? 'none' : 'block';
}
// 重置表單
function resetForm() {
document.getElementById('menuForm').reset();
document.getElementById('formId').value = '';
const today = new Date().toISOString().split('T')[0];
document.getElementById('orderDate').value = today;
showToast('表單已重置', 'info');
}
// 提交表單
async function submitForm() {
const formData = new FormData(document.getElementById('menuForm'));
const data = Object.fromEntries(formData.entries());
const id = data.id;
// 轉換布林值
data.is_active = data.is_active === 'true';
try {
let response, result;
if (id) {
// 更新現有資料
delete data.id;
response = await fetch(`${API_BASE_URL}/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
result = await response.json();
if (response.status === 200) {
showToast('資料更新成功', 'success');
resetForm();
loadData();
} else {
showToast('更新失敗: ' + result.message, 'error');
}
} else {
// 新增資料
response = await fetch(API_BASE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
result = await response.json();
if (response.status === 201) {
showToast('資料新增成功', 'success');
resetForm();
loadData();
} else {
showToast('新增失敗: ' + result.message, 'error');
}
}
} catch (error) {
showToast('操作錯誤: ' + error.message, 'error');
}
}
// 編輯資料
async function editItem(id) {
try {
const response = await fetch(`${API_BASE_URL}/${id}`);
const result = await response.json();
if (result.status === 'success') {
const item = result.data;
// 填充表單
document.getElementById('formId').value = item.Id;
document.getElementById('mainCourse').value = item.main_course || '';
document.getElementById('sideDish').value = item.side_dish || '';
document.getElementById('addon').value = item.addon || '';
document.getElementById('orderDate').value = item.Order_Date || '';
document.getElementById('isActive').value = item.is_active ? 'true' : 'false';
// 滾動到表單
document.getElementById('menuForm').scrollIntoView({
behavior: 'smooth',
block: 'start'
});
showToast('已載入編輯資料', 'info');
} else {
showToast('載入資料失敗: ' + result.message, 'error');
}
} catch (error) {
showToast('載入錯誤: ' + error.message, 'error');
}
}
// 刪除資料
async function deleteItem(id) {
if (!confirm('確定要刪除這筆菜單資料嗎?此操作無法復原。')) {
return;
}
try {
const response = await fetch(`${API_BASE_URL}/${id}`, {
method: 'DELETE'
});
if (response.status === 204) {
showToast('資料刪除成功', 'success');
loadData();
} else {
const result = await response.json();
showToast('刪除失敗: ' + result.message, 'error');
}
} catch (error) {
showToast('刪除錯誤: ' + error.message, 'error');
}
}
</script>
</body>
</html>