mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-10-07 16:18:54 +00:00
fix(form): address mobile admin review findings
This commit is contained in:
parent
8c7d8d4723
commit
85023a68bf
2 changed files with 79 additions and 68 deletions
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue