Przeglądaj źródła

fix(ui): stop the Spool Inventory header from scrolling the page sideways (#2813)

Five header buttons in a row that could neither wrap nor shrink came to
~600px, so on a 390px screen the header ran past the viewport and took the
whole page with it -- <main> is the scroll container, so everything inside
it panned.

Stack below sm and wrap the actions, matching the pattern the Statistics,
Settings and Archives headers and this page's own filter bar already use.
Identical at >=640px. The System Information header had the same
construction with one button and gets the same treatment.
maziggy 3 tygodni temu
rodzic
commit
12e0a60e8b

Plik diff jest za duży
+ 1 - 0
CHANGELOG.md


+ 77 - 0
frontend/src/__tests__/pages/InventoryPageHeaderLayout.test.tsx

@@ -0,0 +1,77 @@
+/**
+ * The Spool Inventory header must not overflow a phone viewport (#2813).
+ *
+ * Its five buttons come to roughly 600px side by side, and a flex row whose
+ * items cannot shrink is as wide as its contents. At 390px that pushed the
+ * header past the viewport, and since the whole page scrolls as one region
+ * everything below it panned with the header.
+ *
+ * jsdom does no layout, so this asserts the two properties that make the
+ * overflow impossible rather than a measured width: the header stacks below
+ * `sm`, and the button group wraps. Same approach as the VirtualPrinterCard
+ * header test (#2808).
+ */
+
+import { describe, it, expect, beforeEach } from 'vitest';
+import { screen, waitFor } from '@testing-library/react';
+import { http, HttpResponse } from 'msw';
+import { render } from '../utils';
+import InventoryPageRouter from '../../pages/InventoryPage';
+import { server } from '../mocks/server';
+
+function setupHandlers() {
+  server.use(
+    http.get('/api/v1/settings/spoolman', () =>
+      HttpResponse.json({
+        spoolman_enabled: 'false',
+        spoolman_url: '',
+        spoolman_sync_mode: 'auto',
+        spoolman_disable_weight_sync: 'false',
+        spoolman_report_partial_usage: 'true',
+      })
+    ),
+    http.get('/api/v1/inventory/spools', () => HttpResponse.json([])),
+    http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
+    http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
+    http.get('/api/v1/printers/', () => HttpResponse.json([])),
+  );
+}
+
+describe('InventoryPage — header layout', () => {
+  beforeEach(() => {
+    setupHandlers();
+  });
+
+  it('stacks the header below sm and keeps it a row from sm up', async () => {
+    render(<InventoryPageRouter />);
+
+    const heading = await screen.findByRole('heading', { name: 'Spool Inventory' });
+    // h1 -> title block -> header row
+    const header = heading.parentElement?.parentElement as HTMLElement;
+
+    expect(header.className).toContain('flex-col');
+    expect(header.className).toContain('sm:flex-row');
+    // Desktop is unchanged: the row still spreads title and actions apart.
+    expect(header.className).toContain('sm:justify-between');
+  });
+
+  it('lets the action buttons wrap instead of widening the header', async () => {
+    render(<InventoryPageRouter />);
+
+    // Located through the heading rather than by button name: the empty-state
+    // panel offers its own "Add Spool" further down the page.
+    const heading = await screen.findByRole('heading', { name: 'Spool Inventory' });
+    const header = heading.parentElement?.parentElement as HTMLElement;
+    const group = header.lastElementChild as HTMLElement;
+    await waitFor(() => {
+      expect(group.querySelector('button')).toBeInTheDocument();
+    });
+
+    // Without this the group is as wide as all five buttons laid end to end,
+    // whatever the viewport is.
+    expect(group.className).toContain('flex-wrap');
+    // All five actions stay in that one group -- wrapping them is the fix,
+    // hiding any of them is not.
+    expect(group.querySelectorAll('button')).toHaveLength(5);
+  });
+});

+ 8 - 3
frontend/src/pages/InventoryPage.tsx

@@ -1308,8 +1308,13 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
 
   return (
     <div className="p-4 md:p-8 space-y-6">
-      {/* Header */}
-      <div className="flex items-center justify-between">
+      {/* Header. Stacks below sm and the actions wrap (#2813): five buttons
+          side by side are ~600px, and nothing in that row can shrink, so on a
+          phone the header pushed past the viewport and took the whole page
+          with it -- <main> is the scroll container, so everything inside it
+          panned sideways. Same pattern the Statistics, Settings and Archives
+          headers use, and the filter bar further down this page. */}
+      <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
         <div>
           <h1 className="text-2xl font-bold text-white flex items-center gap-3">
             <Package className="w-7 h-7 text-bambu-green" />
@@ -1317,7 +1322,7 @@ function InventoryPage({ spoolmanMode = false, spoolmanModeReady = true }: { spo
           </h1>
           <p className="text-bambu-gray mt-1">{t('inventory.subtitle')}</p>
         </div>
-        <div className="flex items-center gap-2">
+        <div className="flex flex-wrap items-center gap-2">
           {/* CSV import/export (#1576). Operates on Bambuddy's local inventory.
               In Spoolman mode the buttons stay visible (feature parity) but are
               disabled with a hint pointing at Spoolman's own CSV export, since

+ 5 - 2
frontend/src/pages/SystemInfoPage.tsx

@@ -201,8 +201,11 @@ export function SystemInfoPage() {
 
   return (
     <div className="p-6 space-y-6">
-      {/* Header */}
-      <div className="flex items-center justify-between">
+      {/* Header. Same stacking as the Spool Inventory header (#2813) -- one
+          button rather than five, so it only just overflows at 390px, but the
+          overflow is the same. items-start keeps Refresh at its own width
+          while stacked instead of stretching it across the page. */}
+      <div className="flex flex-col items-start gap-4 sm:flex-row sm:items-center sm:justify-between">
         <div>
           <h1 className="text-2xl font-bold text-white">{t('system.title', 'System Information')}</h1>
           <p className="text-bambu-gray mt-1">

Plik diff jest za duży
+ 0 - 0
static/assets/index-DAVz1Dw0.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-Bg0yeHsW.js"></script>
+    <script type="module" crossorigin src="/assets/index-DAVz1Dw0.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-D4EtyCBI.css">
   </head>
   <body>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików