1231 lines
56 KiB
HTML
1231 lines
56 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>SYS_V4.2_INVENTORY_MANAGE_INTERNAL_USE_ONLY</title>
|
||
<style>
|
||
/* === CSS Reset & Modernizer === */
|
||
:root {
|
||
--primary: #2563eb;
|
||
--danger: #dc2626;
|
||
--bg: #f8fafc;
|
||
--panel: #ffffff;
|
||
--border: #e2e8f0;
|
||
--text: #334155;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||
background-color: var(--bg) !important; /* 覆盖原有 bgcolor */
|
||
color: var(--text) !important;
|
||
margin: 0;
|
||
padding: 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
a { text-decoration: none; color: var(--primary) !important; transition: 0.2s; }
|
||
a:hover { text-decoration: underline; }
|
||
|
||
/* === Layout Refactor (Force Table to Flex Style) === */
|
||
/* Hide the border and background of the outermost large table */
|
||
.main-layout-table {
|
||
background-color: transparent !important;
|
||
border: none !important;
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
/* Top Header */
|
||
.header-row td {
|
||
background-color: #1e293b !important; /* Dark top bar */
|
||
color: white !important;
|
||
padding: 15px 20px !important;
|
||
border: none !important;
|
||
}
|
||
.header-row h1 { margin: 0; font-size: 1.2rem; font-weight: 600; }
|
||
.header-row p { margin: 5px 0 0; opacity: 0.8; font-size: 0.9rem; }
|
||
.header-row a { color: #94a3b8 !important; margin: 0 5px; }
|
||
|
||
/* Sidebar & Main Content & Widget Layout Adjustment */
|
||
/* Use CSS to make these tds look like independent columns */
|
||
.layout-cell {
|
||
vertical-align: top;
|
||
padding: 20px !important;
|
||
border: none !important;
|
||
}
|
||
|
||
/* 左侧导航栏 */
|
||
.sidebar-nav ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
background: var(--panel);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
.sidebar-nav ul li a {
|
||
display: block;
|
||
padding: 10px 15px;
|
||
border-bottom: 1px solid var(--border);
|
||
color: var(--text) !important;
|
||
}
|
||
.sidebar-nav ul li a:hover {
|
||
background-color: #eff6ff;
|
||
color: var(--primary) !important;
|
||
text-decoration: none;
|
||
}
|
||
/* 隐藏旧版干扰链接 */
|
||
.legacy-link { display: none !important; }
|
||
|
||
/* 中间主内容 */
|
||
.main-content h2 {
|
||
font-size: 1.5rem;
|
||
border-bottom: 2px solid var(--primary);
|
||
padding-bottom: 10px;
|
||
margin-top: 0;
|
||
}
|
||
|
||
/* 筛选表单美化 */
|
||
.filter-table {
|
||
background: var(--panel);
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||
border: 1px solid var(--border) !important;
|
||
}
|
||
.filter-table td { border: none !important; padding: 5px !important; }
|
||
input[type="text"], select {
|
||
padding: 6px; border: 1px solid #ccc; border-radius: 4px;
|
||
}
|
||
input[type="submit"], input[type="reset"] {
|
||
padding: 6px 15px; border-radius: 4px; border: none; cursor: pointer;
|
||
font-weight: bold;
|
||
}
|
||
input[type="submit"] { background: var(--primary); color: white; }
|
||
|
||
/* 数据表格 (核心改造) */
|
||
.data-table {
|
||
background: var(--panel) !important;
|
||
border-collapse: collapse !important;
|
||
border: none !important;
|
||
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
width: 100%;
|
||
margin-top: 20px;
|
||
}
|
||
.data-table th {
|
||
background-color: #f1f5f9 !important; /* 覆盖原来的灰色 */
|
||
color: #475569;
|
||
text-transform: uppercase;
|
||
font-size: 0.85rem;
|
||
padding: 12px !important;
|
||
border: none !important;
|
||
border-bottom: 2px solid var(--border) !important;
|
||
}
|
||
.data-table td {
|
||
border: none !important;
|
||
border-bottom: 1px solid var(--border) !important;
|
||
padding: 12px !important;
|
||
font-size: 0.95rem;
|
||
}
|
||
.data-table tr:hover td { background-color: #f8fafc; }
|
||
|
||
/* 操作链接样式化为按钮 */
|
||
.action-group a {
|
||
display: inline-block;
|
||
padding: 2px 6px;
|
||
font-size: 0.8rem;
|
||
border-radius: 4px;
|
||
text-decoration: none !important;
|
||
margin-right: 2px;
|
||
margin-bottom: 2px;
|
||
border: 1px solid transparent;
|
||
}
|
||
.btn-view { background: #e0f2fe; color: #0369a1 !important; }
|
||
.btn-edit { background: #dcfce7; color: #15803d !important; }
|
||
.btn-danger { background: #fee2e2; color: #b91c1c !important; }
|
||
|
||
/* === 干扰项隐藏 (重点) === */
|
||
/* 隐藏所有被标记为 interference 的行 */
|
||
.interference-row { display: none !important; }
|
||
|
||
/* 右侧 Widget 美化 */
|
||
.widget-table {
|
||
background: var(--panel) !important;
|
||
border: 1px solid var(--border) !important;
|
||
border-radius: 8px;
|
||
margin-bottom: 20px;
|
||
border-collapse: collapse;
|
||
}
|
||
.widget-header {
|
||
background-color: #334155 !important;
|
||
color: white !important;
|
||
padding: 10px !important;
|
||
font-weight: bold;
|
||
border-radius: 7px 7px 0 0;
|
||
}
|
||
.widget-content td { padding: 8px 15px !important; border-bottom: 1px solid #eee !important; }
|
||
|
||
/* 页脚 */
|
||
.footer-row td {
|
||
background-color: var(--panel) !important;
|
||
color: #94a3b8 !important;
|
||
border-top: 1px solid var(--border) !important;
|
||
padding: 20px !important;
|
||
}
|
||
|
||
/* === 新增样式: 模态框 (Modal) === */
|
||
.modal-overlay {
|
||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: none; justify-content: center; align-items: center;
|
||
z-index: 1000; opacity: 0; transition: opacity 0.3s ease;
|
||
}
|
||
.modal-overlay.show { display: flex; opacity: 1; }
|
||
.modal-window {
|
||
background: white; width: 600px; max-width: 90%;
|
||
border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,0.2);
|
||
transform: translateY(-20px); transition: transform 0.3s ease;
|
||
display: flex; flex-direction: column; overflow: hidden;
|
||
}
|
||
.modal-overlay.show .modal-window { transform: translateY(0); }
|
||
.modal-header {
|
||
padding: 15px 20px; background: #f8fafc; border-bottom: 1px solid #e2e8f0;
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
}
|
||
.modal-title { margin: 0; font-size: 1.1rem; font-weight: 600; color: #334155; }
|
||
.modal-close { cursor: pointer; font-size: 1.5rem; color: #94a3b8; line-height: 1; }
|
||
.modal-body { padding: 20px; overflow-y: auto; max-height: 70vh; }
|
||
.modal-footer {
|
||
padding: 15px 20px; background: #f8fafc; border-top: 1px solid #e2e8f0;
|
||
text-align: right;
|
||
}
|
||
|
||
/* 表单组样式 */
|
||
.form-group { margin-bottom: 15px; }
|
||
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; font-size: 0.9rem; }
|
||
.form-group input, .form-group select, .form-group textarea {
|
||
width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;
|
||
box-sizing: border-box; font-family: inherit;
|
||
}
|
||
.form-group .hint { font-size: 0.8rem; color: #64748b; margin-top: 4px; }
|
||
|
||
/* === 新增样式: 分页组件 (Pagination) === */
|
||
.pagination-container {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
margin-top: 20px; padding: 10px; background: #fff; border-radius: 8px;
|
||
border: 1px solid #e2e8f0;
|
||
}
|
||
.pagination-info { font-size: 0.9rem; color: #64748b; }
|
||
.pagination-controls { display: flex; gap: 5px; }
|
||
.page-btn {
|
||
padding: 5px 10px; border: 1px solid #e2e8f0; background: white;
|
||
cursor: pointer; border-radius: 4px; font-size: 0.9rem; color: #475569;
|
||
}
|
||
.page-btn:hover { background: #f1f5f9; border-color: #cbd5e1; }
|
||
.page-btn.active { background: var(--primary); color: white; border-color: var(--primary); }
|
||
.page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
|
||
/* === 新增样式: Toast 通知 === */
|
||
.toast-container {
|
||
position: fixed; bottom: 20px; right: 20px; z-index: 2000;
|
||
display: flex; flex-direction: column; gap: 10px;
|
||
}
|
||
.toast {
|
||
background: white; border-left: 4px solid var(--primary);
|
||
padding: 15px 20px; border-radius: 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||
min-width: 250px; transform: translateX(100%); transition: transform 0.3s ease;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
}
|
||
.toast.show { transform: translateX(0); }
|
||
.toast-success { border-left-color: #10b981; }
|
||
.toast-error { border-left-color: #ef4444; }
|
||
.toast-warning { border-left-color: #f59e0b; }
|
||
|
||
/* === 新增样式: 只有 CSS 的加载动画 === */
|
||
.spinner {
|
||
width: 20px; height: 20px; border: 2px solid #f3f3f3;
|
||
border-top: 2px solid var(--primary); border-radius: 50%;
|
||
animation: spin 1s linear infinite; display: inline-block; vertical-align: middle;
|
||
}
|
||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||
</style>
|
||
</head>
|
||
<body bgcolor="#e0e0e0" text="#000000" link="#0000FF" vlink="#800080" alink="#FF0000">
|
||
|
||
<table width="100%" border="1" cellpadding="5" cellspacing="0" bgcolor="#cccccc" class="main-layout-table">
|
||
<tr class="header-row">
|
||
<td colspan="3" align="center">
|
||
<h1>Internal Inventory Control System V4.2 (Unclassified)</h1>
|
||
<p>Current Login: OPERATOR_8821 | <a href="#" onclick="showCustomAlert('Simulate Logout')">[Logout]</a> | <a href="#">[Switch Node]</a> | <a href="#">[System Log]</a> | <a href="#">[Report Error]</a></p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td width="20%" valign="top" class="layout-cell sidebar-nav">
|
||
<h3>Quick Navigation</h3>
|
||
<ul id="sidebarList">
|
||
<li><a href="javascript:void(0)" onclick="filterCategory('all')">📌 Dashboard</a></li>
|
||
<li><a href="javascript:void(0)" onclick="filterCategory('urgent')">🔥 Inbound Request (Urgent)</a></li>
|
||
<li><a href="javascript:void(0)" onclick="filterCategory('normal')">📥 Inbound Request (Normal)</a></li>
|
||
<li><a href="javascript:void(0)" onclick="showToast('Archived data requires admin permission', 'warning')">🗄️ Inbound Request (Archived)</a></li>
|
||
<li><a href="javascript:void(0)" onclick="filterCategory('out_a')">📤 Outbound Approval (Zone A)</a></li>
|
||
<li><a href="javascript:void(0)" onclick="filterCategory('out_b')">📤 Outbound Approval (Zone B)</a></li>
|
||
<li><a href="javascript:void(0)" onclick="showToast('Zone C system under maintenance', 'error')">🚫 Outbound Approval (Zone C)</a></li>
|
||
<li><a href="javascript:void(0)" onclick="openModal('reportModal')">📉 Damage Registration</a></li>
|
||
<li><a href="javascript:void(0)" onclick="showToast('Access Denied: HR only', 'error')">👥 Personnel Management</a></li>
|
||
<li><a href="javascript:void(0)" onclick="showToast('Connecting to financial interface...', 'info')">💰 Financial Interface</a></li>
|
||
<li><a href="javascript:void(0)" onclick="openModal('settingsModal')">⚙️ System Settings</a></li>
|
||
<li><a href="javascript:void(0)" onclick="window.print()">🖨️ Print Test Page</a></li>
|
||
<li class="legacy-link"><a href="#">Legacy Portal (Deprecated)</a></li>
|
||
<li class="legacy-link"><a href="#">Legacy Portal V2 (Deprecated)</a></li>
|
||
<li class="legacy-link"><a href="#">Help Documentation 1998 Edition</a></li>
|
||
<li><a href="javascript:void(0)" onclick="openHelp()">❓ Help & Support</a></li>
|
||
<li><a href="javascript:void(0)" onclick="showToast('No download needed, browser supported', 'success')">💾 Download Controls</a></li>
|
||
<li><a href="javascript:void(0)" onclick="location.reload()">🔄 Refresh System</a></li>
|
||
</ul>
|
||
<hr>
|
||
<div style="background:#fff7ed; padding:10px; border-radius:4px; font-size:0.9em; border:1px solid #ffedd5;">
|
||
<p style="margin:0; color:#c2410c;"><b>System Broadcast:</b><br>Please note, server maintenance is scheduled for 03:00 tonight. Do not submit forms during this time.</p>
|
||
</div>
|
||
<p style="text-align:center; color:#888; margin-top:10px;"><b>Quote of the Day:</b><br>Safety First, Efficiency Foremost.</p>
|
||
</td>
|
||
|
||
<td width="60%" valign="top" class="layout-cell main-content">
|
||
<h2>Inventory List - Zone A1</h2>
|
||
|
||
<form id="searchForm" onsubmit="event.preventDefault(); handleSearch();">
|
||
<table border="0" width="100%" class="filter-table">
|
||
<tr>
|
||
<td>Keyword: <input type="text" id="searchInput" name="kw" size="30" placeholder="Enter ID or Name"></td>
|
||
<td>
|
||
Type:
|
||
<select id="typeSelect" name="type">
|
||
<option value="all">-- All --</option>
|
||
<option value="res">Resistor</option>
|
||
<option value="cap">Capacitor</option>
|
||
<option value="ind">Inductor</option>
|
||
<option value="chip">Chip</option>
|
||
<option value="conn">Connector</option>
|
||
<option value="other">Other</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
Status:
|
||
<label><input type="checkbox" name="st_normal" value="NORMAL" checked> Normal</label>
|
||
<label><input type="checkbox" name="st_low" value="LOW" checked> Warning</label>
|
||
<label><input type="checkbox" name="st_crit" value="CRITICAL" checked> Critical</label>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="3" style="padding-top:10px;">
|
||
Sort:
|
||
<label><input type="radio" name="sort" value="date" checked> By Date</label>
|
||
<label><input type="radio" name="sort" value="id"> By ID</label>
|
||
<label><input type="radio" name="sort" value="qty"> By Quantity</label>
|
||
<br><br>
|
||
<button type="submit" class="page-btn active" style="padding:6px 20px;">
|
||
<span id="search-spinner" class="spinner" style="display:none; width:12px; height:12px; border-width:2px; vertical-align:middle; margin-right:5px;"></span>
|
||
Search
|
||
</button>
|
||
<button type="button" class="page-btn" onclick="resetSearch()">Reset</button>
|
||
<button type="button" class="page-btn" disabled style="opacity:0.6; cursor:not-allowed;">Export Excel</button>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</form>
|
||
<hr style="border:0; margin:20px 0;">
|
||
|
||
<table border="1" width="100%" cellpadding="3" cellspacing="1" class="data-table" id="inventoryTable">
|
||
<thead>
|
||
<tr bgcolor="#999999">
|
||
<th width="40"><input type="checkbox" id="selectAll"></th>
|
||
<th width="100">ID</th>
|
||
<th>Item Name</th>
|
||
<th width="120">Batch No</th>
|
||
<th width="80">Qty</th>
|
||
<th width="80">Weight(g)</th>
|
||
<th width="100">Inbound Date</th>
|
||
<th width="100">Status Code</th>
|
||
<th width="180">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tableBody">
|
||
<!-- Data will be dynamically generated by JS -->
|
||
<tr><td colspan="9" align="center" style="padding: 50px;">Loading inventory data... <div class="spinner"></div></td></tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- New Pagination Component -->
|
||
<div class="pagination-container">
|
||
<div class="pagination-info">
|
||
Showing <span id="startRecord">0</span> to <span id="endRecord">0</span> of <span id="totalRecords">0</span> records
|
||
</div>
|
||
<div style="display:flex; align-items:center; gap:10px;">
|
||
<select id="pageSizeSelect" onchange="changePageSize()">
|
||
<option value="10">10/page</option>
|
||
<option value="20">20/page</option>
|
||
<option value="50">50/page</option>
|
||
<option value="100">100/page</option>
|
||
</select>
|
||
<div class="pagination-controls" id="paginationControls">
|
||
<!-- Pagination buttons generated by JS -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<br><br>
|
||
<table border="1" width="100%" bgcolor="#eeeeee" style="border:none; border-radius:8px; overflow:hidden; background:#f1f5f9;">
|
||
<tr>
|
||
<td style="padding:20px; border:none;">
|
||
<h4 style="margin-top:0;">Batch Operation Console</h4>
|
||
<p>Selected Action:
|
||
<select id="batchActionSelect">
|
||
<option>-- Select Action --</option>
|
||
<option>Batch Export</option>
|
||
<option>Batch Delete (Admin Required)</option>
|
||
<option>Transfer Warehouse</option>
|
||
</select>
|
||
<input type="button" value="Execute" onclick="executeBatchAction()">
|
||
</p>
|
||
<p style="font-size:0.9em; color:#666;">
|
||
<label><input type="checkbox"> I have read and agree to the "Data Security Operation Standard v9.0"</label><br>
|
||
<label><input type="checkbox"> Confirm no misoperation</label>
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
</td>
|
||
|
||
<td width="20%" valign="top" class="layout-cell sidebar-widgets">
|
||
<table border="1" width="100%" class="widget-table">
|
||
<tr><td bgcolor="#000000" class="widget-header"><font color="white" align="center"><b>Server Load</b></font></td></tr>
|
||
<tbody class="widget-content">
|
||
<tr><td>CPU: <div style="display:inline-block; width:50px; height:8px; background:#e2e8f0; border-radius:4px;"><div style="width:12%; height:100%; background:green; border-radius:4px;"></div></div> 12%</td></tr>
|
||
<tr><td>RAM: <div style="display:inline-block; width:50px; height:8px; background:#e2e8f0; border-radius:4px;"><div style="width:64%; height:100%; background:orange; border-radius:4px;"></div></div> 64%</td></tr>
|
||
<tr><td>DISK: <div style="display:inline-block; width:50px; height:8px; background:#e2e8f0; border-radius:4px;"><div style="width:98%; height:100%; background:red; border-radius:4px;"></div></div> <span style="color:red">98% (Warning)</span></td></tr>
|
||
</tbody>
|
||
</table>
|
||
<br>
|
||
<table border="1" width="100%" class="widget-table">
|
||
<tr><td bgcolor="#000000" class="widget-header"><font color="white"><b>To-Do List</b></font></td></tr>
|
||
<tbody class="widget-content">
|
||
<tr><td><label><input type="checkbox"> Approve Zhang San's Leave</label></td></tr>
|
||
<tr><td><label><input type="checkbox"> Order Coffee Beans</label></td></tr>
|
||
<tr><td><label><input type="checkbox"> Fix Printer</label></td></tr>
|
||
<tr><td><label><input type="checkbox"> Update Firewall</label></td></tr>
|
||
<tr><td><label><input type="checkbox"> Year-end Report Summary</label></td></tr>
|
||
</tbody>
|
||
</table>
|
||
<br>
|
||
<center>
|
||
<p style="font-size:0.8rem; color:#888;">Scan to Download App</p>
|
||
<div style="background:white; padding:10px; border-radius:8px; display:inline-block; border:1px solid #ddd;">
|
||
<table border="1" width="100" height="100" style="border:none;">
|
||
<tr><td align="center" style="border:none;">QR CODE</td></tr>
|
||
</table>
|
||
</div>
|
||
</center>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr class="footer-row">
|
||
<td colspan="3" bgcolor="#333333" align="center">
|
||
<font color="#ffffff" size="2">
|
||
© 2005-2025 Galactic Logistics Corp. All Rights Reserved.<br>
|
||
Address: Sector 7G, Industrial Zone, Mars Colony.<br>
|
||
<a href="#" style="color: #aaaaaa">Privacy Policy</a> |
|
||
<a href="#" style="color: #aaaaaa">Terms of Service</a> |
|
||
<a href="#" style="color: #aaaaaa">Sitemap</a> |
|
||
<a href="#" style="color: #aaaaaa">Report Abuse</a>
|
||
<br>
|
||
<span style="opacity:0.5;">Render Time: 0.04s | SQL Queries: 142 | Memory: 4MB</span>
|
||
</font>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
|
||
<!-- === Hidden Modals (HTML Structure) === -->
|
||
|
||
<!-- 1. Edit/Add Modal -->
|
||
<div class="modal-overlay" id="editModal">
|
||
<div class="modal-window">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">Edit Inventory Item</h3>
|
||
<span class="modal-close" onclick="closeModal('editModal')">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="editForm">
|
||
<div class="form-group">
|
||
<label>Item ID (Read-only)</label>
|
||
<input type="text" id="edit_id" readonly style="background:#f1f5f9; color:#64748b;">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Item Name <span style="color:red">*</span></label>
|
||
<input type="text" id="edit_name" required>
|
||
<div class="hint">Please enter full component model, e.g., Resistor 0603 10k 1%</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Supplier</label>
|
||
<select id="edit_supplier">
|
||
<option value="Alpha Corp">Alpha Corp - Premium Supplier</option>
|
||
<option value="Beta Ltd">Beta Ltd - Long-term Partner</option>
|
||
<option value="Gamma Inc">Gamma Inc</option>
|
||
<option value="ConnWorld">ConnWorld - Connectors Only</option>
|
||
<option value="ST Micro">ST Micro - Chip Manufacturer</option>
|
||
<option value="Other">Other (Remark Required)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<div style="display:flex; gap:10px;">
|
||
<div style="flex:1;">
|
||
<label>Quantity</label>
|
||
<input type="number" id="edit_qty" min="0">
|
||
</div>
|
||
<div style="flex:1;">
|
||
<label>Unit Weight (g)</label>
|
||
<input type="number" id="edit_weight" step="0.01">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Status</label>
|
||
<select id="edit_status">
|
||
<option value="NORMAL">Normal (NORMAL)</option>
|
||
<option value="LOW">Low Stock (LOW)</option>
|
||
<option value="CRITICAL">Critical Shortage (CRITICAL)</option>
|
||
<option value="DAMAGED">Damaged (DAMAGED)</option>
|
||
<option value="PENDING">Pending (PENDING)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Notes</label>
|
||
<textarea id="edit_notes" rows="3"></textarea>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="page-btn" onclick="closeModal('editModal')">Cancel</button>
|
||
<button class="page-btn active" onclick="saveEdit()">Save Changes</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 2. Detail Modal -->
|
||
<div class="modal-overlay" id="detailModal">
|
||
<div class="modal-window">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">Material Detail Profile</h3>
|
||
<span class="modal-close" onclick="closeModal('detailModal')">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<table width="100%" border="0" cellpadding="8" style="background:#f8fafc; border-radius:8px;">
|
||
<tr>
|
||
<td width="30%" align="right" style="color:#64748b;">Internal ID:</td>
|
||
<td width="70%"><b id="detail_id">#---</b></td>
|
||
</tr>
|
||
<tr>
|
||
<td align="right" style="color:#64748b;">Item Name:</td>
|
||
<td><span id="detail_name">---</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td align="right" style="color:#64748b;">Batch No:</td>
|
||
<td><code id="detail_batch" style="background:#e2e8f0; padding:2px 4px; border-radius:3px;">---</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td align="right" style="color:#64748b;">Real-time Stock:</td>
|
||
<td><span id="detail_qty">0</span> units</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="right" style="color:#64748b;">Inbound Time:</td>
|
||
<td><span id="detail_date">---</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td align="right" style="color:#64748b;">Lifecycle Status:</td>
|
||
<td><span id="detail_status">---</span></td>
|
||
</tr>
|
||
</table>
|
||
<div style="margin-top:20px;">
|
||
<h4 style="border-bottom:1px solid #ddd; padding-bottom:5px;">Recent Circulation Records</h4>
|
||
<ul style="font-size:0.9rem; color:#475569; padding-left:20px;" id="detail_history">
|
||
<li>No records</li>
|
||
</ul>
|
||
</div>
|
||
<div style="margin-top:20px; background:#fff7ed; padding:10px; border-left:4px solid #f97316; font-size:0.9rem;">
|
||
<strong>Note:</strong> This item is a precision component. Handle with care and store at 20-25°C.
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="page-btn" onclick="printDetail()">Print Profile</button>
|
||
<button class="page-btn" onclick="closeModal('detailModal')">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 3. Settings Modal -->
|
||
<div class="modal-overlay" id="settingsModal">
|
||
<div class="modal-window">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">System Preferences</h3>
|
||
<span class="modal-close" onclick="closeModal('settingsModal')">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<h4 style="border-bottom: 2px solid var(--primary); padding-bottom: 5px;">Interface Display</h4>
|
||
<div class="form-group">
|
||
<label>Theme Mode</label>
|
||
<select id="themeSelect" onchange="showToast('Theme switching is under development...', 'warning')">
|
||
<option value="light">Light (Default)</option>
|
||
<option value="dark">Dark (Dark Mode)</option>
|
||
<option value="auto">System Default</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label><input type="checkbox" checked> Enable Animations</label><br>
|
||
<label><input type="checkbox" checked> Show Real-time Clock</label><br>
|
||
<label><input type="checkbox"> Compact Table Mode</label>
|
||
</div>
|
||
|
||
<h4 style="border-bottom: 2px solid var(--primary); padding-bottom: 5px; margin-top:20px;">Data & Privacy</h4>
|
||
<div class="form-group">
|
||
<label>Auto Refresh Interval</label>
|
||
<select>
|
||
<option value="0">Off</option>
|
||
<option value="30">30 Seconds</option>
|
||
<option value="60">1 Minute</option>
|
||
<option value="300">5 Minutes</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label><input type="checkbox" checked> Allow Local Storage</label><br>
|
||
<label><input type="checkbox"> Send Anonymous Usage Statistics</label>
|
||
</div>
|
||
|
||
<h4 style="border-bottom: 2px solid var(--primary); padding-bottom: 5px; margin-top:20px;">Notification Settings</h4>
|
||
<div class="form-group">
|
||
<label><input type="checkbox" checked> Low Stock Warning</label><br>
|
||
<label><input type="checkbox" checked> Approval Notification</label><br>
|
||
<label><input type="checkbox"> Daily Report Push</label>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="page-btn" onclick="closeModal('settingsModal')">Cancel</button>
|
||
<button class="page-btn active" onclick="closeModal('settingsModal'); showToast('Settings saved', 'success')">Save Config</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 4. Help Documentation Modal (Long Text) -->
|
||
<div class="modal-overlay" id="helpModal">
|
||
<div class="modal-window" style="width:800px;">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">User Guide v4.2</h3>
|
||
<span class="modal-close" onclick="closeModal('helpModal')">×</span>
|
||
</div>
|
||
<div class="modal-body" style="line-height:1.8; color:#334155;">
|
||
<h3>1. System Overview</h3>
|
||
<p>This system (SYS_V4.2) aims to provide efficient and transparent internal inventory management solutions for Galactic Logistics Corp. The system integrates four core modules: material inbound, inventory monitoring, outbound approval, and report generation.</p>
|
||
|
||
<h3>2. Quick Start</h3>
|
||
<ul>
|
||
<li><strong>Login:</strong> Use the assigned ID (e.g., OPERATOR_8821) and dynamic token to log in.</li>
|
||
<li><strong>Browse Inventory:</strong> View real-time inventory status of the current warehouse on the main interface. Supports filtering by name, ID, and status.</li>
|
||
<li><strong>Edit Data:</strong> Click the [Edit] button on the right side of the table to modify inventory quantity or status.</li>
|
||
<li><strong>Export Reports:</strong> Check the required data rows and select "Batch Export" from the bottom "Batch Operation Console".</li>
|
||
</ul>
|
||
|
||
<h3>3. Status Code Explanation</h3>
|
||
<table width="100%" border="1" cellpadding="5" style="border-collapse:collapse; border-color:#e2e8f0;">
|
||
<tr bgcolor="#f1f5f9"><th>Status Code</th><th>Meaning</th><th>Action Suggestion</th></tr>
|
||
<tr><td>NORMAL</td><td>Normal Inventory</td><td>No action needed, regular stocktaking.</td></tr>
|
||
<tr><td style="color:orange">LOW</td><td>Low Inventory</td><td>It is recommended to initiate a purchase request to replenish inventory to a safe level.</td></tr>
|
||
<tr><td style="color:red">CRITICAL</td><td>Critical Shortage</td><td>Contact the supplier immediately for emergency replenishment, which may affect the production line.</td></tr>
|
||
<tr><td style="color:gray">DAMAGED</td><td>Damaged</td><td>Material is unavailable, please initiate the scrapping process and store separately.</td></tr>
|
||
<tr><td>PENDING</td><td>Pending Inspection</td><td>Material just arrived or waiting for quality inspection, temporarily frozen for outbound.</td></tr>
|
||
</table>
|
||
|
||
<h3>4. FAQ</h3>
|
||
<p><strong>Q: Why can't I find the data I just added?</strong><br>
|
||
A: Please ensure filter settings are correct. If the problem persists, try clicking "Refresh System" in the left sidebar.</p>
|
||
|
||
<p><strong>Q: Can I use this system on mobile?</strong><br>
|
||
A: Yes. The system uses a responsive design (although optimized mainly for desktop) and supports mainstream mobile browsers.</p>
|
||
|
||
<p><strong>Q: How to apply for administrator permissions?</strong><br>
|
||
A: Please fill out the "IT Permission Change Request Form" and submit it to the Sector 7G Information Center. The approval cycle is about 3 working days.</p>
|
||
|
||
<hr>
|
||
<p style="font-size:0.8rem; color:#94a3b8;">Last Updated: 2025-10-15 | Doc Version: 4.2.1-beta</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="page-btn" onclick="window.open('https://example.com/manual.pdf')">Download PDF</button>
|
||
<button class="page-btn" onclick="closeModal('helpModal')">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 5. Damage Registration Modal -->
|
||
<div class="modal-overlay" id="reportModal">
|
||
<div class="modal-window">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">Damage Registration Form</h3>
|
||
<span class="modal-close" onclick="closeModal('reportModal')">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p style="background:#fee2e2; padding:10px; color:#b91c1c; border-radius:4px;">
|
||
Warning: False damage reporting is a serious violation.
|
||
</p>
|
||
<form>
|
||
<div class="form-group">
|
||
<label>Related Item ID</label>
|
||
<input type="text" placeholder="Enter ID or Scan Barcode">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Damage Reason</label>
|
||
<select>
|
||
<option>Transport Damage</option>
|
||
<option>Storage Expired</option>
|
||
<option>Damp/Oxidation</option>
|
||
<option>Human Damage</option>
|
||
<option>Inventory Loss</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Person in Charge</label>
|
||
<input type="text" value="OPERATOR_8821 (Current User)">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>On-site Photo Evidence (URL)</label>
|
||
<input type="text" placeholder="http://...">
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="page-btn" onclick="closeModal('reportModal')">Cancel</button>
|
||
<button class="page-btn btn-danger" onclick="closeModal('reportModal'); showToast('Damage report submitted for review', 'warning')">Submit Report</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- 6. General Confirmation Modal -->
|
||
<div class="modal-overlay" id="confirmModal">
|
||
<div class="modal-window" style="width: 400px; min-height: auto;">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">Confirmation</h3>
|
||
<span class="modal-close" onclick="closeModal('confirmModal')">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="confirmMessage" style="font-size: 1rem; color: #334155;">Are you sure you want to perform this action?</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="page-btn" onclick="closeModal('confirmModal')">Cancel</button>
|
||
<button class="page-btn active" id="confirmBtnAction">Confirm</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 7. General Alert Modal (Alert Replacement) -->
|
||
<div class="modal-overlay" id="alertModal">
|
||
<div class="modal-window" style="width: 400px; min-height: auto;">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title">System Alert</h3>
|
||
<span class="modal-close" onclick="closeModal('alertModal')">×</span>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="alertMessage" style="font-size: 1rem; color: #334155; white-space: pre-line;"></p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="page-btn active" onclick="closeModal('alertModal')">Got it</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast 通知容器 -->
|
||
<div class="toast-container" id="toastContainer"></div>
|
||
|
||
<script>
|
||
// === JavaScript Core Logic Refactor (SPA Pattern) ===
|
||
|
||
// --- Custom Alert Logic (Replace native alert/confirm) ---
|
||
let onConfirmCallback = null;
|
||
|
||
function showCustomAlert(msg) {
|
||
document.getElementById('alertMessage').innerText = msg;
|
||
openModal('alertModal');
|
||
}
|
||
|
||
function showCustomConfirm(msg, callback) {
|
||
document.getElementById('confirmMessage').innerText = msg;
|
||
onConfirmCallback = callback;
|
||
openModal('confirmModal');
|
||
}
|
||
|
||
// Init bind Confirm button event
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.getElementById('confirmBtnAction').addEventListener('click', () => {
|
||
if (typeof onConfirmCallback === 'function') {
|
||
onConfirmCallback();
|
||
}
|
||
closeModal('confirmModal');
|
||
});
|
||
});
|
||
|
||
// Config & State
|
||
const CONFIG = {
|
||
totalItems: 358, // Mock Total Data
|
||
defaultPageSize: 10,
|
||
suppliers: ["Alpha Corp", "Beta Ltd", "Gamma Inc", "ConnWorld", "ST Micro", "Texas Inst", "NXP", "Murata"],
|
||
statuses: ["NORMAL", "LOW", "CRITICAL", "DAMAGED", "PENDING"],
|
||
components: [
|
||
"Resistor 10k 0603", "Capacitor 100nF", "Inductor 22uH", "MCU STM32F103", "Connector USB-C",
|
||
"LED Red 0805", "Diode 1N4148", "Transistor 2N2222", "Regulator 3.3V", "Crystal 16MHz",
|
||
"Battery Holder", "Fuse 500mA", "Switch Tactile", "Header 2.54mm", "PCB Stiffener"
|
||
]
|
||
};
|
||
|
||
let state = {
|
||
data: [], // All Data
|
||
filteredData: [], // Filtered Data
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
sortBy: 'id', // id, date, qty
|
||
sortDesc: false
|
||
};
|
||
|
||
// --- Navigation & Common Modals ---
|
||
|
||
// --- Search Logic ---
|
||
function handleSearch() {
|
||
const spinner = document.getElementById('search-spinner');
|
||
spinner.style.display = 'inline-block';
|
||
|
||
// Get Filters
|
||
const kw = document.getElementById('searchInput').value.toLowerCase().trim();
|
||
const typeFilter = document.getElementById('typeSelect').value;
|
||
const checkedStatuses = [];
|
||
if(document.querySelector('input[name="st_normal"]').checked) checkedStatuses.push('NORMAL');
|
||
if(document.querySelector('input[name="st_low"]').checked) checkedStatuses.push('LOW');
|
||
if(document.querySelector('input[name="st_crit"]').checked) checkedStatuses.push('CRITICAL');
|
||
|
||
// Get Sort
|
||
const sortVal = document.querySelector('input[name="sort"]:checked').value;
|
||
|
||
// Simulate Network Delay
|
||
setTimeout(() => {
|
||
state.filteredData = state.data.filter(item => {
|
||
// 1. Keyword Match (ID, Name, Supplier, Batch)
|
||
const matchKw = !kw ||
|
||
item.id.toLowerCase().includes(kw) ||
|
||
item.name.toLowerCase().includes(kw) ||
|
||
item.supplier.toLowerCase().includes(kw) ||
|
||
item.batch.toLowerCase().includes(kw);
|
||
|
||
// 2. Type Match (Simple string inclusion check)
|
||
let matchType = true;
|
||
if (typeFilter !== 'all') {
|
||
if (typeFilter === 'res') matchType = item.name.includes('Resistor');
|
||
else if (typeFilter === 'cap') matchType = item.name.includes('Capacitor');
|
||
else if (typeFilter === 'ind') matchType = item.name.includes('Inductor');
|
||
else if (typeFilter === 'chip') matchType = item.name.includes('MCU') || item.name.includes('Regulator') || item.name.includes('Transistor') || item.name.includes('Diode');
|
||
else if (typeFilter === 'conn') matchType = item.name.includes('Connector') || item.name.includes('Header');
|
||
else if (typeFilter === 'other') matchType = !item.name.includes('Resist') && !item.name.includes('Capacit') && !item.name.includes('Induct') && !item.name.includes('Connect');
|
||
}
|
||
|
||
// 3. Status Match
|
||
// Logic: show if item.status is in checkedStatuses
|
||
// Special handling: CRITICAL check includes DAMAGED too (assumption)
|
||
let matchStatus = false;
|
||
if (checkedStatuses.length === 0) matchStatus = true;
|
||
|
||
if (checkedStatuses.includes(item.status)) matchStatus = true;
|
||
|
||
if (checkedStatuses.includes('CRITICAL') && item.status === 'DAMAGED') matchStatus = true;
|
||
|
||
return matchKw && matchType && matchStatus;
|
||
});
|
||
|
||
// Sorting
|
||
if (sortVal === 'id') {
|
||
state.filteredData.sort((a,b) => a.id.localeCompare(b.id));
|
||
} else if (sortVal === 'qty') {
|
||
state.filteredData.sort((a,b) => b.qty - a.qty);
|
||
} else if (sortVal === 'date') {
|
||
state.filteredData.sort((a,b) => new Date(b.date) - new Date(a.date));
|
||
}
|
||
|
||
state.currentPage = 1;
|
||
renderTable();
|
||
spinner.style.display = 'none';
|
||
showToast(`Search complete, found ${state.filteredData.length} records`, 'success');
|
||
}, 400);
|
||
}
|
||
|
||
function resetSearch() {
|
||
document.getElementById('searchInput').value = '';
|
||
document.getElementById('typeSelect').value = 'all';
|
||
document.querySelectorAll('input[type="checkbox"]').forEach(c => c.checked = true);
|
||
document.querySelector('input[value="date"]').checked = true;
|
||
handleSearch();
|
||
}
|
||
|
||
function openModal(id) {
|
||
const modal = document.getElementById(id);
|
||
if (modal) {
|
||
modal.style.display = 'flex';
|
||
modal.offsetHeight; // force reflow
|
||
modal.classList.add('show');
|
||
}
|
||
}
|
||
|
||
function openHelp() {
|
||
openModal('helpModal');
|
||
}
|
||
|
||
// Sidebar Category Filter Simulation
|
||
function filterCategory(category) {
|
||
// Reset all selection states
|
||
document.querySelectorAll('#sidebarList a').forEach(a => {
|
||
a.style.color = '';
|
||
a.style.backgroundColor = '';
|
||
});
|
||
|
||
showToast(`Switching to view: ${category}`, 'info');
|
||
|
||
// Mock Data Filter Logic
|
||
if (category === 'all') {
|
||
state.filteredData = [...state.data];
|
||
document.querySelector('.main-content h2').innerText = 'Inventory List - All';
|
||
} else if (category === 'urgent') {
|
||
document.querySelector('.main-content h2').innerText = 'Inventory List - Urgent';
|
||
state.filteredData = state.data.filter(d => d.status === 'CRITICAL' || d.status === 'LOW');
|
||
} else if (category === 'normal') {
|
||
document.querySelector('.main-content h2').innerText = 'Inventory List - Normal';
|
||
state.filteredData = state.data.filter(d => d.status === 'NORMAL');
|
||
} else if (category === 'out_a') {
|
||
document.querySelector('.main-content h2').innerText = 'Outbound Approval - Zone A';
|
||
// Mock random
|
||
state.filteredData = state.data.filter(d => d.id.endsWith('1') || d.id.endsWith('3'));
|
||
} else if (category === 'out_b') {
|
||
document.querySelector('.main-content h2').innerText = 'Outbound Approval - Zone B';
|
||
state.filteredData = state.data.filter(d => d.id.endsWith('2') || d.id.endsWith('4'));
|
||
}
|
||
|
||
// Reset Pagination
|
||
state.currentPage = 1;
|
||
renderTable();
|
||
}
|
||
|
||
// --- 1. Data Generator (Simulate Backend) ---
|
||
function generateMockData() {
|
||
console.time("DataGeneration");
|
||
const data = [];
|
||
for (let i = 1; i <= CONFIG.totalItems; i++) {
|
||
const padId = String(i).padStart(4, '0');
|
||
const typeIdx = Math.floor(Math.random() * CONFIG.components.length);
|
||
const supplIdx = Math.floor(Math.random() * CONFIG.suppliers.length);
|
||
const statusIdx = Math.random() > 0.8 ? (Math.random() > 0.5 ? 2 : 1) : 0; // Mostly Normal
|
||
|
||
// Random Date (Past 2 Years)
|
||
const date = new Date(Date.now() - Math.floor(Math.random() * 63072000000));
|
||
const batchYear = date.getFullYear();
|
||
const batchCode = `BATCH_${batchYear}_${String.fromCharCode(65 + Math.floor(Math.random() * 26))}${Math.floor(Math.random()*100)}`;
|
||
|
||
data.push({
|
||
id: `8821${padId}`,
|
||
name: CONFIG.components[typeIdx],
|
||
supplier: CONFIG.suppliers[supplIdx],
|
||
batch: batchCode,
|
||
qty: Math.floor(Math.random() * 10000),
|
||
weight: (Math.random() * 10).toFixed(2),
|
||
date: date.toISOString().split('T')[0],
|
||
status: CONFIG.statuses[statusIdx],
|
||
notes: "Automatically generated mock data record..."
|
||
});
|
||
}
|
||
console.timeEnd("DataGeneration");
|
||
return data;
|
||
}
|
||
|
||
// --- 2. Core Render Logic ---
|
||
function init() {
|
||
showToast("System initializing...", "info");
|
||
state.data = generateMockData();
|
||
applyFilter(); // Initial Filter (All)
|
||
console.log(`Loaded ${state.data.length} records.`);
|
||
|
||
// Bind Events
|
||
document.getElementById('editForm').addEventListener('submit', (e) => { e.preventDefault(); saveEdit(); });
|
||
}
|
||
|
||
// Filter & Sort
|
||
function applyFilter() {
|
||
// Simple Mock: Get search box value (Simplified here)
|
||
state.filteredData = [...state.data];
|
||
|
||
// Sort
|
||
state.filteredData.sort((a, b) => {
|
||
let valA = a[state.sortBy];
|
||
let valB = b[state.sortBy];
|
||
if (state.sortBy === 'qty' || state.sortBy === 'weight') {
|
||
valA = parseFloat(valA);
|
||
valB = parseFloat(valB);
|
||
}
|
||
if (valA < valB) return state.sortDesc ? 1 : -1;
|
||
if (valA > valB) return state.sortDesc ? -1 : 1;
|
||
return 0;
|
||
});
|
||
|
||
state.currentPage = 1;
|
||
renderTable();
|
||
}
|
||
|
||
function renderTable() {
|
||
const tbody = document.getElementById('tableBody');
|
||
tbody.innerHTML = ''; // Clear
|
||
|
||
const start = (state.currentPage - 1) * state.pageSize;
|
||
const end = Math.min(start + state.pageSize, state.filteredData.length);
|
||
const pageData = state.filteredData.slice(start, end);
|
||
|
||
if (pageData.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="9" align="center" style="padding:20px;">No matching data found</td></tr>';
|
||
return;
|
||
}
|
||
|
||
pageData.forEach(item => {
|
||
const tr = document.createElement('tr');
|
||
tr.className = 'data-row';
|
||
|
||
// Status Style
|
||
let statusColor = 'green';
|
||
if (item.status === 'LOW') statusColor = 'orange';
|
||
if (item.status === 'CRITICAL' || item.status === 'DAMAGED') statusColor = 'red';
|
||
|
||
tr.innerHTML = `
|
||
<td align="center"><input type="checkbox" class="row-check" value="${item.id}"></td>
|
||
<td><font face="monospace">#${item.id}</font></td>
|
||
<td>
|
||
<strong>${item.name}</strong><br>
|
||
<small style="color:#666">Supplier: ${item.supplier}</small>
|
||
</td>
|
||
<td><code style="background:#f1f5f9; padding:2px;">${item.batch}</code></td>
|
||
<td>${item.qty.toLocaleString()}</td>
|
||
<td>${item.weight}g</td>
|
||
<td>${item.date}</td>
|
||
<td><span style="color:${statusColor}; font-weight:bold; font-size:0.85rem;">${item.status}</span></td>
|
||
<td class="action-group">
|
||
<a href="javascript:void(0)" class="btn-view" onclick="openDetail('${item.id}')">[Detail]</a>
|
||
<a href="javascript:void(0)" class="btn-edit" onclick="openEdit('${item.id}')">[Edit]</a>
|
||
<a href="javascript:void(0)" class="btn-danger" onclick="deleteItem('${item.id}', this)">[Del]</a>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(tr);
|
||
});
|
||
|
||
renderPagination(start, end, state.filteredData.length);
|
||
}
|
||
|
||
function renderPagination(start, end, total) {
|
||
document.getElementById('startRecord').innerText = total === 0 ? 0 : start + 1;
|
||
document.getElementById('endRecord').innerText = end;
|
||
document.getElementById('totalRecords').innerText = total;
|
||
|
||
const controls = document.getElementById('paginationControls');
|
||
controls.innerHTML = '';
|
||
|
||
const totalPages = Math.ceil(total / state.pageSize);
|
||
|
||
// Simple Pagination Algorithm
|
||
const createBtn = (text, page, active = false, disabled = false) => {
|
||
const btn = document.createElement('button');
|
||
btn.className = `page-btn ${active ? 'active' : ''}`;
|
||
btn.innerText = text;
|
||
btn.disabled = disabled;
|
||
if (!disabled) btn.onclick = () => { state.currentPage = page; renderTable(); };
|
||
return btn;
|
||
};
|
||
|
||
controls.appendChild(createBtn('<<', 1, false, state.currentPage === 1));
|
||
controls.appendChild(createBtn('<', state.currentPage - 1, false, state.currentPage === 1));
|
||
|
||
// Show nearby pages
|
||
let pStart = Math.max(1, state.currentPage - 2);
|
||
let pEnd = Math.min(totalPages, state.currentPage + 2);
|
||
|
||
for (let p = pStart; p <= pEnd; p++) {
|
||
controls.appendChild(createBtn(p, p, p === state.currentPage));
|
||
}
|
||
|
||
controls.appendChild(createBtn('>', state.currentPage + 1, false, state.currentPage === totalPages));
|
||
controls.appendChild(createBtn('>>', totalPages, false, state.currentPage === totalPages));
|
||
}
|
||
|
||
// --- 3. Interaction Logic ---
|
||
|
||
// Change Page Size
|
||
function changePageSize() {
|
||
const select = document.getElementById('pageSizeSelect');
|
||
state.pageSize = parseInt(select.value);
|
||
state.currentPage = 1;
|
||
renderTable();
|
||
showToast(`Showing ${state.pageSize} items per page`, "info");
|
||
}
|
||
|
||
// Modal Control
|
||
function closeModal(result) {
|
||
const modal = document.getElementById(result); // result is ID
|
||
modal.classList.remove('show');
|
||
setTimeout(() => { modal.style.display = 'none'; }, 300);
|
||
}
|
||
|
||
// Open Edit Modal
|
||
function openEdit(id) {
|
||
const item = state.data.find(d => d.id === id);
|
||
if (!item) return;
|
||
|
||
// Fill Form
|
||
document.getElementById('edit_id').value = item.id;
|
||
document.getElementById('edit_name').value = item.name;
|
||
document.getElementById('edit_supplier').value = item.supplier;
|
||
document.getElementById('edit_qty').value = item.qty;
|
||
document.getElementById('edit_weight').value = item.weight;
|
||
document.getElementById('edit_status').value = item.status;
|
||
document.getElementById('edit_notes').value = item.notes || "";
|
||
|
||
const modal = document.getElementById('editModal');
|
||
modal.style.display = 'flex';
|
||
// Force Reflow
|
||
modal.offsetHeight;
|
||
modal.classList.add('show');
|
||
}
|
||
|
||
function saveEdit() {
|
||
const id = document.getElementById('edit_id').value;
|
||
const newName = document.getElementById('edit_name').value;
|
||
const newQty = parseInt(document.getElementById('edit_qty').value);
|
||
|
||
// Update Local Data
|
||
const idx = state.data.findIndex(d => d.id === id);
|
||
if (idx !== -1) {
|
||
state.data[idx].name = newName;
|
||
state.data[idx].qty = newQty;
|
||
state.data[idx].supplier = document.getElementById('edit_supplier').value;
|
||
state.data[idx].status = document.getElementById('edit_status').value;
|
||
state.data[idx].weight = document.getElementById('edit_weight').value;
|
||
// Note: Updated filteredData reference
|
||
}
|
||
|
||
closeModal('editModal');
|
||
showToast("Saved successfully! Data updated.", "success");
|
||
renderTable(); // Re-render
|
||
}
|
||
|
||
// Open Detail Modal
|
||
function openDetail(id) {
|
||
const item = state.data.find(d => d.id === id);
|
||
if(!item) return;
|
||
|
||
document.getElementById('detail_id').innerText = item.id;
|
||
document.getElementById('detail_name').innerText = item.name;
|
||
document.getElementById('detail_batch').innerText = item.batch;
|
||
document.getElementById('detail_qty').innerText = item.qty.toLocaleString();
|
||
document.getElementById('detail_date').innerText = item.date;
|
||
document.getElementById('detail_status').innerText = item.status;
|
||
|
||
// Mock History
|
||
const historyList = document.getElementById('detail_history');
|
||
historyList.innerHTML = '';
|
||
for(let i=0; i<3; i++) {
|
||
historyList.innerHTML += `<li>${new Date().toLocaleDateString()} - System automatically approved (Audit Log #${Math.floor(Math.random()*9000)})</li>`;
|
||
}
|
||
|
||
const modal = document.getElementById('detailModal');
|
||
modal.style.display = 'flex';
|
||
modal.offsetHeight;
|
||
modal.classList.add('show');
|
||
}
|
||
|
||
function printDetail() {
|
||
showCustomAlert("Connecting to printer... \n(Simulate: Sending command PCL_PRINT_JOB_001)");
|
||
}
|
||
|
||
// Delete Item
|
||
function deleteItem(id, btnElement) {
|
||
showCustomConfirm(`Are you sure you want to delete item ${id}?`, () => {
|
||
// Mock frontend delete
|
||
state.data = state.data.filter(d => d.id !== id);
|
||
applyFilter(); // Re-filter and sort
|
||
showToast(`Item ${id} has been deleted`, "warning");
|
||
});
|
||
}
|
||
|
||
// Batch Action
|
||
function executeBatchAction() {
|
||
const select = document.getElementById('batchActionSelect');
|
||
const action = select.value;
|
||
const checks = document.querySelectorAll('.row-check:checked');
|
||
|
||
if (checks.length === 0) {
|
||
showToast("Please select at least one item!", "error");
|
||
return;
|
||
}
|
||
|
||
if (select.selectedIndex === 0) {
|
||
showToast("Please select a valid action type", "warning");
|
||
} else {
|
||
// Mock Progress
|
||
showToast(`Processing ${checks.length} items: ${action}...`, "info");
|
||
setTimeout(() => {
|
||
showToast("Batch operation completed!", "success");
|
||
// Uncheck all
|
||
checks.forEach(c => c.checked = false);
|
||
document.getElementById('selectAll').checked = false;
|
||
}, 1500);
|
||
}
|
||
}
|
||
|
||
// Toast Tool
|
||
function showToast(message, type = 'info') {
|
||
const container = document.getElementById('toastContainer');
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast toast-${type}`;
|
||
|
||
let icon = 'ℹ️';
|
||
if (type === 'success') icon = '✅';
|
||
if (type === 'error') icon = '❌';
|
||
if (type === 'warning') icon = '⚠️';
|
||
|
||
toast.innerHTML = `
|
||
<div style="display:flex; align-items:center; gap:10px;">
|
||
<span style="font-size:1.2rem;">${icon}</span>
|
||
<span>${message}</span>
|
||
</div>
|
||
<span style="cursor:pointer; opacity:0.5;" onclick="this.parentElement.remove()">×</span>
|
||
`;
|
||
|
||
container.appendChild(toast);
|
||
// Animation
|
||
requestAnimationFrame(() => toast.classList.add('show'));
|
||
|
||
// Auto Dismiss
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
setTimeout(() => toast.remove(), 300);
|
||
}, 3000);
|
||
}
|
||
|
||
// Select All
|
||
document.getElementById('selectAll').addEventListener('change', function(e) {
|
||
const isChecked = e.target.checked;
|
||
document.querySelectorAll('.row-check').forEach(cb => cb.checked = isChecked);
|
||
});
|
||
|
||
// Start
|
||
window.onload = init;
|
||
|
||
</script>
|
||
|
||
</body>
|
||
</html> |