Restore invoicing period right rail layout (#249)
## What changed - restore the desktop invoicing-period category groups as a vertical right-hand rail - use a 3:1 content-to-navigation split at desktop widths with responsive stacking below 1024px - size the inline month selector to the available content width with a readable 32rem cap - adapt the invoice review workspace to the narrower content region - add unit contracts and browser geometry coverage for desktop, tablet, and mobile ## Why The grouped period categories had moved into a wide horizontal row, leaving unused space on the right and constraining the period content. The inline month picker was also too small to read comfortably. ## User impact Superusers again see period categories in the right rail on desktop. Compact layouts retain responsive navigation, and the review/object-tree content avoids field wrapping when the main region becomes narrow. ## Validation - 60/60 focused unit tests - 30/30 full Chromium desktop invoicing-period browser tests - 9/9 priority browser tests across Chromium desktop, tablet, and mobile - ESLint - production Vite build - focused Prettier checks - git diff check
This commit is contained in:
@@ -1288,6 +1288,27 @@ test.describe("Invoicing period tab", () => {
|
||||
await expect(page.getByTestId("date-period-start")).toHaveCount(0);
|
||||
expect(selectedPeriodRequests).toHaveLength(0);
|
||||
|
||||
const mainRegion = page.getByTestId("invoicing-period-layout-main");
|
||||
const navigationRegion = page.getByTestId("invoicing-period-layout-navigation");
|
||||
const mainBox = await getBoundingBox(mainRegion, "period main region");
|
||||
const navigationBox = await getBoundingBox(navigationRegion, "period navigation region");
|
||||
const monthSelectorBox = await getBoundingBox(monthSelector, "inline month selector");
|
||||
const viewportWidth = page.viewportSize()?.width ?? 0;
|
||||
|
||||
expectBoxInside(monthSelectorBox, mainBox, "inline month selector");
|
||||
expect(monthSelectorBox.width).toBeGreaterThanOrEqual(Math.min(320, mainBox.width - 2));
|
||||
expect(monthSelectorBox.width).toBeLessThanOrEqual(514);
|
||||
if (viewportWidth >= 1024) {
|
||||
expect(navigationBox.x).toBeGreaterThanOrEqual(mainBox.x + mainBox.width + 8);
|
||||
expect(navigationBox.width / (mainBox.width + navigationBox.width)).toBeGreaterThanOrEqual(0.2);
|
||||
expect(navigationBox.width / (mainBox.width + navigationBox.width)).toBeLessThanOrEqual(0.4);
|
||||
} else {
|
||||
expect(navigationBox.y + navigationBox.height).toBeLessThanOrEqual(mainBox.y + 1);
|
||||
}
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth))
|
||||
.toBeLessThanOrEqual(2);
|
||||
|
||||
const julyOption = monthSelector.locator(".datepicker-months .datepicker-cell").filter({ hasText: /jul/i });
|
||||
await expect(julyOption).toHaveCount(1);
|
||||
await julyOption.click();
|
||||
@@ -1366,7 +1387,7 @@ test.describe("Invoicing period tab", () => {
|
||||
const tabletQueueBox = await page.locator(".period-review-queue").boundingBox();
|
||||
expect(tabletDetailBox).not.toBeNull();
|
||||
expect(tabletQueueBox).not.toBeNull();
|
||||
expect(tabletDetailBox.x).toBeGreaterThan(tabletQueueBox.x);
|
||||
expect(tabletDetailBox.y).toBeLessThan(tabletQueueBox.y);
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth))
|
||||
.toBe(true);
|
||||
@@ -2294,12 +2315,14 @@ test.describe("Invoicing period tab", () => {
|
||||
const tableContainer = document.querySelector(
|
||||
'[data-testid="department-75-combined-distribution-table-container"]'
|
||||
);
|
||||
const navigation = document.querySelector('[data-testid="invoicing-period-view-selectors"]');
|
||||
if (!tableContainer || !navigation) {
|
||||
const content = document.querySelector('[data-testid="invoicing-period-layout-main"]');
|
||||
const navigation = document.querySelector('[data-testid="invoicing-period-layout-navigation"]');
|
||||
if (!tableContainer || !content || !navigation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tableContainerRect = tableContainer.getBoundingClientRect();
|
||||
const contentRect = content.getBoundingClientRect();
|
||||
const navigationRect = navigation.getBoundingClientRect();
|
||||
const selectorRects = Array.from(navigation.querySelectorAll(".is-selector-view")).map((selector) =>
|
||||
selector.getBoundingClientRect()
|
||||
@@ -2308,7 +2331,9 @@ test.describe("Invoicing period tab", () => {
|
||||
document.documentElement.scrollWidth - document.documentElement.clientWidth <= 2 &&
|
||||
navigation.scrollWidth - navigation.clientWidth <= 2 &&
|
||||
tableContainer.scrollWidth - tableContainer.clientWidth <= 2 &&
|
||||
tableContainerRect.top >= navigationRect.bottom - 1 &&
|
||||
tableContainerRect.left >= contentRect.left - 1 &&
|
||||
tableContainerRect.right <= contentRect.right + 1 &&
|
||||
contentRect.right <= navigationRect.left - 8 &&
|
||||
selectorRects.every(
|
||||
(rect) =>
|
||||
rect.left >= navigationRect.left - 1 &&
|
||||
|
||||
@@ -38,6 +38,13 @@ const periodSource = readFileSync(
|
||||
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/InvoicingBillingPeriod.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const periodLayoutSource = readFileSync(
|
||||
join(
|
||||
root,
|
||||
"src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/InvoicingBillingPeriodLayoutDefault.vue"
|
||||
),
|
||||
"utf8"
|
||||
);
|
||||
const periodTopSource = readFileSync(
|
||||
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/layout/Top.vue"),
|
||||
"utf8"
|
||||
@@ -89,6 +96,10 @@ const periodViewAllSource = readFileSync(
|
||||
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const periodViewHomeSource = readFileSync(
|
||||
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewHome.vue"),
|
||||
"utf8"
|
||||
);
|
||||
const periodObjectTreeSource = readFileSync(
|
||||
join(root, "src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodObjectTree.vue"),
|
||||
"utf8"
|
||||
@@ -409,6 +420,38 @@ describe("Periode tab contract", () => {
|
||||
expect(periodSource).toContain("<template #content-bottom>");
|
||||
});
|
||||
|
||||
it("places period content beside a three-to-one right navigation rail on desktop", () => {
|
||||
expect(periodLayoutSource).toContain('data-testid="invoicing-period-layout-navigation"');
|
||||
expect(periodLayoutSource).toContain('data-testid="invoicing-period-layout-main"');
|
||||
expect(periodLayoutSource).toContain('"content navigation"');
|
||||
expect(periodLayoutSource).toContain("grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);");
|
||||
expect(periodLayoutSource).toContain("@media screen and (min-width: 1024px)");
|
||||
expect(periodLayoutSource).toContain('"navigation"');
|
||||
expect(periodLayoutSource).toContain('"content"');
|
||||
});
|
||||
|
||||
it("stacks grouped selectors in the desktop rail and keeps compact navigation responsive", () => {
|
||||
expect(periodRightSource).toContain("grid-template-columns: minmax(0, 1fr);");
|
||||
expect(periodRightSource).toContain("@media screen and (min-width: 601px) and (max-width: 1023px)");
|
||||
expect(periodRightSource).toContain("grid-template-columns: repeat(2, minmax(0, 1fr));");
|
||||
expect(periodRightSource).toContain("@media screen and (max-width: 600px)");
|
||||
expect(periodRightSource).toContain("flex: 0 0 min(82vw, 320px);");
|
||||
});
|
||||
|
||||
it("adapts the review workspace to the main region width created by the right rail", () => {
|
||||
expect(periodViewAllSource).toContain('class="invoicing-period-view-all"');
|
||||
expect(periodViewAllSource).toContain("container-type: inline-size;");
|
||||
expect(periodViewAllSource).toContain("grid-template-columns: minmax(330px, 0.8fr) minmax(0, 1.6fr);");
|
||||
expect(periodViewAllSource).toContain("@container (max-width: 1024px)");
|
||||
});
|
||||
|
||||
it("sizes the inline month selector to the available content width with a readable cap", () => {
|
||||
expect(periodViewHomeSource).toContain('class="invoicing-period-month-entry__selector"');
|
||||
expect(periodViewHomeSource).toContain("max-width: 32rem;");
|
||||
expect(periodViewHomeSource).toContain(":deep(.datepicker-months)");
|
||||
expect(periodViewHomeSource).toContain("min-height: 3rem;");
|
||||
});
|
||||
|
||||
it("keeps top selector and left dynamic view renderer", () => {
|
||||
expect(periodTopSource).toContain("<InvoicingBillingPeriodDatePeriodSelector");
|
||||
expect(periodTopSource).toContain("dates.variables.hasSelection.value");
|
||||
|
||||
Reference in New Issue
Block a user