mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-10-07 16:18:54 +00:00
fix(form): resolve remaining review nitpicks
This commit is contained in:
parent
e00d74ebad
commit
88239424f3
4 changed files with 30 additions and 32 deletions
|
|
@ -115,10 +115,10 @@ function buildEditActions(email) {
|
|||
return wrap;
|
||||
}
|
||||
|
||||
function buildViewActions(idx, email, name) {
|
||||
function buildViewActions(email, name) {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'admin-actions';
|
||||
const editBtn = buildActionButton('Edit', 'start-edit', { index: String(idx), email });
|
||||
const editBtn = buildActionButton('Edit', 'start-edit', { email });
|
||||
editBtn.className = 'btn btn-sm btn-edit';
|
||||
const deleteBtn = buildActionButton('Delete', 'delete', { email, name });
|
||||
deleteBtn.className = 'btn btn-sm btn-delete';
|
||||
|
|
@ -229,7 +229,7 @@ function renderAdminTable(data) {
|
|||
if (isEditing) {
|
||||
actionsTd.appendChild(buildEditActions(order.employee_email));
|
||||
} else {
|
||||
actionsTd.appendChild(buildViewActions(idx, order.employee_email, order.employee_name));
|
||||
actionsTd.appendChild(buildViewActions(order.employee_email, order.employee_name));
|
||||
}
|
||||
|
||||
tr.appendChild(totalTd);
|
||||
|
|
@ -267,7 +267,8 @@ function downloadOrderList() {
|
|||
const names = Object.keys(totals).sort((a, b) => a.localeCompare(b));
|
||||
const esc = v => {
|
||||
const s = String(v);
|
||||
return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
|
||||
const safe = /^[=+\-@\t\r]/.test(s) ? "'" + s : s;
|
||||
return /[",\n]/.test(safe) ? '"' + safe.replace(/"/g, '""') + '"' : safe;
|
||||
};
|
||||
const rows = [['Item', 'Quantity']].concat(names.map(n => [n, totals[n]]));
|
||||
const csv = rows.map(r => r.map(esc).join(',')).join('\r\n');
|
||||
|
|
@ -307,9 +308,12 @@ function downloadSummaryPdf() {
|
|||
.finally(() => { if (btn) btn.disabled = false; });
|
||||
}
|
||||
|
||||
function adminStartEdit(idx, email) {
|
||||
function adminStartEdit(email) {
|
||||
fetch(ADMIN_URL + '?week=' + encodeURIComponent(currentAdminWeek), { headers: adminHeaders() })
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(data => {
|
||||
lastAdminData = data;
|
||||
const order = data.orders.find(o => o.employee_email === email);
|
||||
|
|
@ -318,7 +322,8 @@ function adminStartEdit(idx, email) {
|
|||
editQuantities = {};
|
||||
order.items.forEach(i => { editQuantities[i.name] = i.quantity; });
|
||||
renderAdminTable(data);
|
||||
});
|
||||
})
|
||||
.catch(() => alert('Failed to load this order for editing.'));
|
||||
}
|
||||
|
||||
function adminCancelEdit() {
|
||||
|
|
@ -376,7 +381,7 @@ function handleAdminPanelClick(e) {
|
|||
if (btn) {
|
||||
const action = btn.dataset.adminAction;
|
||||
if (action === 'start-edit') {
|
||||
adminStartEdit(parseInt(btn.dataset.index, 10), btn.dataset.email);
|
||||
adminStartEdit(btn.dataset.email);
|
||||
} else if (action === 'delete') {
|
||||
adminDeleteOrder(btn.dataset.email, btn.dataset.name || '');
|
||||
} else if (action === 'save-edit') {
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ header p { font-size: 0.85rem; opacity: 0.8; }
|
|||
@media (max-width: 480px) {
|
||||
.meal-card {
|
||||
display: grid;
|
||||
grid-template-columns: 80px minmax(0, 1fr);
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: start;
|
||||
}
|
||||
.qty-control {
|
||||
|
|
@ -469,7 +469,7 @@ header p { font-size: 0.85rem; opacity: 0.8; }
|
|||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admin-toolbar .back-btn { margin: 0; padding: 8px 16px; font-size: 0.85rem; margin-top: 0; }
|
||||
.admin-toolbar .back-btn { margin: 0; padding: 8px 16px; font-size: 0.85rem; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.admin-toolbar { width: 100%; }
|
||||
|
|
|
|||
12
tests/fixtures/menu.json
vendored
12
tests/fixtures/menu.json
vendored
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"scraped_at": "2026-07-27T12:00:00+00:00",
|
||||
"meal_count": 4,
|
||||
"meal_count": 5,
|
||||
"meals": [
|
||||
{
|
||||
"name": "Chicken Bowl",
|
||||
|
|
@ -45,6 +45,16 @@
|
|||
"dietary_tags": [],
|
||||
"image_url": "",
|
||||
"is_new": false
|
||||
},
|
||||
{
|
||||
"name": "<!--<script>alert(1)</script>-->",
|
||||
"price": 9.0,
|
||||
"calories": "300",
|
||||
"protein": "10g",
|
||||
"description": "Hostile script comment",
|
||||
"dietary_tags": [],
|
||||
"image_url": "",
|
||||
"is_new": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -138,7 +138,6 @@ class TestGenerateFormStructural:
|
|||
def test_no_onclick_admin_data_embedding(self):
|
||||
html = _render(google=True)
|
||||
assert 'onclick="admin' not in html
|
||||
assert 'onclick="admin' not in html
|
||||
# Static markup must not interpolate runtime data into onclick
|
||||
assert not re.search(r'onclick="[^"]*\$\{', html)
|
||||
|
||||
|
|
@ -162,6 +161,7 @@ class TestGenerateFormStructural:
|
|||
assert "Luis' Lomo Saltado" in names
|
||||
assert 'Meal with "quotes"' in names
|
||||
assert "</script><script>alert(1)</script>" in names
|
||||
assert "<!--<script>alert(1)</script>-->" in names
|
||||
|
||||
def test_meal_card_template_and_qty_input(self):
|
||||
html = _render()
|
||||
|
|
@ -187,14 +187,10 @@ class TestGenerateFormStructural:
|
|||
|
||||
|
||||
class TestGenerateFormPlaywright:
|
||||
@pytest.fixture(scope="class")
|
||||
@pytest.fixture
|
||||
def browser_page(self, tmp_path_factory):
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError as exc: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
"playwright package is required; install requirements.txt"
|
||||
) from exc
|
||||
sync_api = pytest.importorskip("playwright.sync_api")
|
||||
sync_playwright = sync_api.sync_playwright
|
||||
|
||||
html = _render(google=False, api_key="")
|
||||
# Stub status/roster so the page does not hang on network; roster fallback
|
||||
|
|
@ -285,19 +281,6 @@ class TestGenerateFormPlaywright:
|
|||
"() => document.querySelector('[data-meal-index=\"0\"] .qty').value === '0'"
|
||||
)
|
||||
assert qty.input_value() == "0"
|
||||
# Total may still include other meals from prior test if class-scoped page
|
||||
# is shared — set all known cards to 0 then set one meal to 2 via type-in.
|
||||
page.evaluate(
|
||||
"""() => {
|
||||
document.querySelectorAll('[data-meal-index] .qty').forEach((input) => {
|
||||
input.value = '0';
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
});
|
||||
}"""
|
||||
)
|
||||
page.wait_for_function(
|
||||
"() => document.getElementById('count-display').textContent === '0 meals'"
|
||||
)
|
||||
qty.fill("2")
|
||||
qty.blur()
|
||||
page.wait_for_function(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue