fix(form): address mobile admin review findings

This commit is contained in:
Adam Moussa 2026-07-31 09:51:46 -04:00
parent 8c7d8d4723
commit 85023a68bf
No known key found for this signature in database
2 changed files with 79 additions and 68 deletions

View file

@ -182,10 +182,11 @@ function editTotal() {
}, 0); }, 0);
} }
function populateOrderItems(container, order, isEditing) { function populateOrderItems(container, order, isEditing, separatorFactory) {
if (isEditing) { if (isEditing) {
Object.keys(editQuantities).forEach(name => { Object.keys(editQuantities).forEach(name => {
container.appendChild(buildEditQtyRow(name, editQuantities[name] || 0)); container.appendChild(buildEditQtyRow(name, editQuantities[name] || 0));
if (separatorFactory) container.appendChild(separatorFactory());
}); });
const availableMeals = MEALS.filter( const availableMeals = MEALS.filter(
m => !Object.prototype.hasOwnProperty.call(editQuantities, m.name) m => !Object.prototype.hasOwnProperty.call(editQuantities, m.name)
@ -196,15 +197,18 @@ function populateOrderItems(container, order, isEditing) {
return '$' + editTotal().toFixed(2); return '$' + editTotal().toFixed(2);
} }
order.items.forEach(item => { order.items.forEach((item, itemIdx) => {
const row = document.createElement('div'); if (separatorFactory && itemIdx > 0) {
row.className = 'admin-item'; container.appendChild(separatorFactory());
}
const row = separatorFactory ? container : document.createElement('div');
if (!separatorFactory) row.className = 'admin-item';
row.appendChild(document.createTextNode(item.name + ' ×' + item.quantity + ' ')); row.appendChild(document.createTextNode(item.name + ' ×' + item.quantity + ' '));
const sub = document.createElement('span'); const sub = document.createElement('span');
sub.className = 'admin-muted'; sub.className = 'admin-muted';
sub.textContent = '($' + item.subtotal.toFixed(2) + ')'; sub.textContent = '($' + item.subtotal.toFixed(2) + ')';
row.appendChild(sub); row.appendChild(sub);
container.appendChild(row); if (!separatorFactory) container.appendChild(row);
}); });
return '$' + order.total.toFixed(2); return '$' + order.total.toFixed(2);
} }
@ -317,33 +321,14 @@ function renderAdminTable(data) {
const itemsTd = document.createElement('td'); const itemsTd = document.createElement('td');
itemsTd.className = 'items-cell'; itemsTd.className = 'items-cell';
let totalHtml; const totalHtml = populateOrderItems(
if (isEditing) { itemsTd,
const editNames = Object.keys(editQuantities); order,
editNames.forEach(name => { isEditing,
itemsTd.appendChild(buildEditQtyRow(name, editQuantities[name] || 0)); () => document.createElement('br')
itemsTd.appendChild(document.createElement('br')); );
}); tr.appendChild(empTd);
const availableMeals = MEALS.filter(m => !Object.prototype.hasOwnProperty.call(editQuantities, m.name)); tr.appendChild(itemsTd);
if (availableMeals.length > 0) {
itemsTd.appendChild(buildAddItemSelect(availableMeals));
}
totalHtml = '$' + editTotal().toFixed(2);
tr.appendChild(empTd);
tr.appendChild(itemsTd);
} else {
order.items.forEach((item, itemIdx) => {
if (itemIdx > 0) itemsTd.appendChild(document.createElement('br'));
itemsTd.appendChild(document.createTextNode(item.name + ' ×' + item.quantity + ' '));
const sub = document.createElement('span');
sub.className = 'admin-muted';
sub.textContent = '($' + item.subtotal.toFixed(2) + ')';
itemsTd.appendChild(sub);
});
totalHtml = '$' + order.total.toFixed(2);
tr.appendChild(empTd);
tr.appendChild(itemsTd);
}
const totalTd = document.createElement('td'); const totalTd = document.createElement('td');
totalTd.className = 'total-cell'; totalTd.className = 'total-cell';

View file

@ -686,40 +686,44 @@ class TestGenerateFormPlaywright:
has_touch=True, has_touch=True,
is_mobile=True, is_mobile=True,
) )
page = context.new_page() try:
page.route( page = context.new_page()
"**/api/form-status/**", page.route(
lambda route: route.fulfill( "**/api/form-status/**",
status=200, lambda route: route.fulfill(
content_type="application/json", status=200,
body='{"status":"open"}', content_type="application/json",
), body='{"status":"open"}',
) ),
page.route( )
"**/api/roster", page.route(
lambda route: route.fulfill( "**/api/roster**",
status=200, lambda route: route.fulfill(
content_type="application/json", status=200,
body='{"employees":[]}', content_type="application/json",
), body='{"employees":[]}',
) ),
page.route("**/api/admin/orders**", handle_admin) )
page.on( page.route("**/api/admin/orders**", handle_admin)
"dialog", page.on(
lambda dialog: ( "dialog",
state["dialogs"].append(dialog.message), lambda dialog: (
dialog.accept(), state["dialogs"].append(dialog.message),
), dialog.accept()
) if state.get("accept_dialogs", True)
page.goto(out.as_uri()) else dialog.dismiss(),
page.evaluate( ),
"() => { window.showAdmin(); window.loadWeekOrders('2026-W30'); }" )
) page.goto(out.as_uri())
page.wait_for_selector(".admin-order-card") page.evaluate(
yield page, state, hostile_name "() => { window.showAdmin(); window.loadWeekOrders('2026-W30'); }"
context.close() )
page.wait_for_selector(".admin-order-card")
yield page, state, hostile_name
finally:
context.close()
def test_mobile_admin_card_complete_workflow(self, mobile_admin_page): def test_mobile_admin_card_initial_render_and_overflow(self, mobile_admin_page):
page, state, hostile_name = mobile_admin_page page, state, hostile_name = mobile_admin_page
card = page.locator(".admin-order-card") card = page.locator(".admin-order-card")
@ -727,13 +731,17 @@ class TestGenerateFormPlaywright:
assert not page.locator(".admin-table-wrap").is_visible() assert not page.locator(".admin-table-wrap").is_visible()
assert hostile_name in card.inner_text() assert hostile_name in card.inner_text()
assert "hostile@example.com" in card.inner_text() assert "hostile@example.com" in card.inner_text()
assert "Chicken Bowl ×1" in card.inner_text() assert "Chicken Bowl ×1" in card.inner_text() # noqa: RUF001
assert "$12.00" in card.inner_text() assert "$12.00" in card.inner_text()
assert page.evaluate( assert page.evaluate(
"() => document.documentElement.scrollWidth === " "() => document.documentElement.scrollWidth === "
"document.documentElement.clientWidth" "document.documentElement.clientWidth"
) )
def test_mobile_admin_card_edit_and_save(self, mobile_admin_page):
page, state, hostile_name = mobile_admin_page
card = page.locator(".admin-order-card")
card.locator('[data-admin-action="start-edit"]').click() card.locator('[data-admin-action="start-edit"]').click()
page.wait_for_selector('.admin-order-card [data-admin-action="save-edit"]') page.wait_for_selector('.admin-order-card [data-admin-action="save-edit"]')
minus = page.locator( minus = page.locator(
@ -755,11 +763,14 @@ class TestGenerateFormPlaywright:
page.locator('.admin-order-card [data-admin-action="save-edit"]').click() page.locator('.admin-order-card [data-admin-action="save-edit"]').click()
page.wait_for_function( page.wait_for_function(
"() => document.querySelector('.admin-order-card')" "() => document.querySelector('.admin-order-card')"
'.textContent.includes("Luis\' Lomo Saltado ×2")' '.textContent.includes("Luis\' Lomo Saltado ×2")' # noqa: RUF001
) )
assert state["orders"][0]["employee_name"] == hostile_name assert state["orders"][0]["employee_name"] == hostile_name
assert state["orders"][0]["total"] == 41.0 assert state["orders"][0]["total"] == 41.0
def test_mobile_admin_card_cancel(self, mobile_admin_page):
page, _, _ = mobile_admin_page
page.locator('.admin-order-card [data-admin-action="start-edit"]').click() page.locator('.admin-order-card [data-admin-action="start-edit"]').click()
page.wait_for_selector('.admin-order-card [data-admin-action="cancel-edit"]') page.wait_for_selector('.admin-order-card [data-admin-action="cancel-edit"]')
page.locator( page.locator(
@ -770,6 +781,9 @@ class TestGenerateFormPlaywright:
'.admin-order-card [data-admin-action="start-edit"]' '.admin-order-card [data-admin-action="start-edit"]'
).is_visible() ).is_visible()
def test_mobile_admin_card_responsive_breakpoints(self, mobile_admin_page):
page, _, _ = mobile_admin_page
for width in (320, 375, 767): for width in (320, 375, 767):
page.set_viewport_size({"width": width, "height": 800}) page.set_viewport_size({"width": width, "height": 800})
assert page.locator(".admin-order-card").is_visible() assert page.locator(".admin-order-card").is_visible()
@ -787,7 +801,9 @@ class TestGenerateFormPlaywright:
assert page.locator(".admin-table-wrap").is_visible() assert page.locator(".admin-table-wrap").is_visible()
assert not page.locator(".admin-order-card").is_visible() assert not page.locator(".admin-order-card").is_visible()
page.set_viewport_size({"width": 375, "height": 800}) def test_mobile_admin_card_save_failure_and_retry(self, mobile_admin_page):
page, state, _ = mobile_admin_page
state["fail_put"] = True state["fail_put"] = True
page.locator('.admin-order-card [data-admin-action="start-edit"]').click() page.locator('.admin-order-card [data-admin-action="start-edit"]').click()
page.wait_for_selector('.admin-order-card [data-admin-action="save-edit"]') page.wait_for_selector('.admin-order-card [data-admin-action="save-edit"]')
@ -801,6 +817,16 @@ class TestGenerateFormPlaywright:
page.wait_for_selector('.admin-order-card [data-admin-action="start-edit"]') page.wait_for_selector('.admin-order-card [data-admin-action="start-edit"]')
assert page.locator("#admin-status-alert").inner_text() == "" assert page.locator("#admin-status-alert").inner_text() == ""
def test_mobile_admin_card_delete(self, mobile_admin_page):
page, state, hostile_name = mobile_admin_page
orders_before = list(state["orders"])
state["accept_dialogs"] = False
page.locator('.admin-order-card [data-admin-action="delete"]').click()
assert page.locator(".admin-order-card").is_visible()
assert state["orders"] == orders_before
state["accept_dialogs"] = True
page.locator('.admin-order-card [data-admin-action="delete"]').click() page.locator('.admin-order-card [data-admin-action="delete"]').click()
page.wait_for_selector("#admin-empty:not(.is-hidden)") page.wait_for_selector("#admin-empty:not(.is-hidden)")
assert hostile_name in state["dialogs"][-1] assert hostile_name in state["dialogs"][-1]