diff --git a/frontend/DEVELOPMENT-STANDARDS.md b/frontend/DEVELOPMENT-STANDARDS.md index 700750f8..b3e32ce6 100644 --- a/frontend/DEVELOPMENT-STANDARDS.md +++ b/frontend/DEVELOPMENT-STANDARDS.md @@ -96,6 +96,22 @@ Use `PrintersList.vue` as the reference for new list pages. It has NO scoped sty Dark mode is automatic via `@media (prefers-color-scheme: dark)`. Using CSS variables ensures colors adapt automatically - no extra work needed per page. +## Data Entry: Page or Modal + +Both patterns are in use on purpose. Pick by what the record IS, not by how big the form feels today. + +**Routed form page** (`views/Form.vue` + a route) when the record has its own detail page and its own URL: machines, PCs, printers, network devices, measuring tools, USB devices, applications, knowledge-base articles, notifications, printed items. These forms are long, often carry an image upload, a map-position picker or relationship editing, and someone will want to link straight to one. + +**Modal over its list** when the record is reference data that only exists inside the list it belongs to: every `*TypesList`, plus locations, vendors, models, VLANs, subnets, support teams, custom fields, API tokens, printer drivers, dashboard defaults. The form is a handful of fields, and keeping the list visible behind the dialog is the point. + +Rule of thumb: **a thing with a detail page gets a form page; a lookup row gets a modal.** + +Modal rules (from the overlay-close fix, commit d8fe0a4): + +- A modal holding typed input must NOT close on overlay click or Escape. Losing a part-filled form to a stray click is not an acceptable failure. +- A confirmation dialog MAY close on overlay click (`@click.self`), since it holds nothing to lose. +- The panel background is `var(--bg-card-solid)`, never `var(--bg-card)`: the card variable is translucent in dark mode, which leaves a dialog see-through over the overlay. The shared `.modal` in `style.css` already does this; hand-rolled `.modal-panel` rules must too. + ## Component Organization - **Global styles**: `src/assets/style.css` diff --git a/frontend/src/views/ShopfloorDashboard.vue b/frontend/src/views/ShopfloorDashboard.vue index f98521f6..4ce6bc7e 100644 --- a/frontend/src/views/ShopfloorDashboard.vue +++ b/frontend/src/views/ShopfloorDashboard.vue @@ -1,818 +1,872 @@ - - - - - + + + + + diff --git a/plugins/computers/frontend/views/AccessProtocolsList.vue b/plugins/computers/frontend/views/AccessProtocolsList.vue index 1682de52..016b9675 100644 --- a/plugins/computers/frontend/views/AccessProtocolsList.vue +++ b/plugins/computers/frontend/views/AccessProtocolsList.vue @@ -192,7 +192,9 @@ onMounted(load) z-index: 1000; } .modal-panel { - background: var(--bg-card); + /* Solid: var(--bg-card) is translucent in dark mode, which made this + dialog see-through. */ + background: var(--bg-card-solid); color: var(--text); border: 1px solid var(--border); border-radius: 10px; diff --git a/plugins/network/api/routes.py b/plugins/network/api/routes.py index 3dfdc875..a011fa84 100644 --- a/plugins/network/api/routes.py +++ b/plugins/network/api/routes.py @@ -24,9 +24,13 @@ def _upsert_primary_ip(asset, ip): """Create/update/clear the asset's primary IP Communication from an ipaddress string. No-op if the IP CommunicationType is not seeded.""" ip = (ip or '').strip() - if not ip: - return comm = Communication.query.filter_by(assetid=asset.assetid, isprimary=True).first() + if not ip: + # Clearing the field clears the row's IP - it used to return early, so + # an address could be typed but never taken back out. + if comm: + comm.ipaddress = None + return if comm: comm.ipaddress = ip return diff --git a/plugins/network/frontend/views/NetworkDeviceForm.vue b/plugins/network/frontend/views/NetworkDeviceForm.vue index a10fe551..dbaa4b3b 100644 --- a/plugins/network/frontend/views/NetworkDeviceForm.vue +++ b/plugins/network/frontend/views/NetworkDeviceForm.vue @@ -112,6 +112,16 @@ placeholder="e.g., sw-bldg1-floor2" /> +
+ + +
+ +