Fix the WP navigator and the squeezed embedded layout; add per-project permissions
Layout — the reported "skinny scrolling windows" - .content-area capped the whole suite at 1000px, so on a 1920 screen the embedded Work Package Creator ran in a ~930px column with its own scrollbar inside the page's. The wizard now caps at 1700px and the Creator/Dashboard tab goes full-bleed: the iframe fills the window below the app chrome and owns the only scrollbar. Needed `flex: none` on the content area — as a `flex: 1` item its flex-basis overrode `height`, leaving the used height indefinite so the child's `height: 100%` collapsed the iframe to its 150px default. - The SOP wizard's fields were one per row; they now flow into ~340px columns. Navigator — now an auto-hiding drawer - It was a fixed 262px column that stole width from the form AND was hidden below 1100px, so embedded (the normal path) it never appeared at all — that's the "broken side menu". It's now an overlay drawer behind a slim always-visible edge handle: hover or tap to open, move away / Escape / pick a package to close, or pin it to keep it open (pinned shifts the form and the page chrome across, and is remembered). A gutter keeps the handle off the section-nav chips. Bugs found while checking the site over - collectStepData() still read the SOP team fields as text inputs, but wave 1 made them account pickers — so it wrote a user ID into state.team.pm where the display NAME belongs, and the SOP would print `user_ab12…` as the PM. Now synced properly from the pickers. - loadSampleData() set .value on those selects with fictional names; setting an unmatched value on a <select> silently does nothing, so the sample lost its team. It now stores them as names without an account, which the picker shows as "(no account)". - My earlier CSS block replacement had deleted the SOP-chip, people-picker and critical-tag styles. Restored. Same picker everywhere the SOP names someone - Sign-off roles (step 3, required and optional) are account pickers now, storing userId alongside the name, so a signature belongs to an account that can be notified. Titles stay free text. Per-project permissions (asked for: "change project permissions for individual users") - project_members.role overrides the account's role on that project, so a PM on one job can be a Project User on another. Empty = inherit; app admin is admin everywhere. effective_role() feeds require_project_admin, so WP delete, completed- SOP edits and project delete are all judged per project. - Project access is now its own column in the admin console (it was buried among the action buttons, which is why it couldn't be found), showing the project count per account; the dialog sets access plus the role on each project. - The members endpoint reports each person's effective role on that project. Verified: 157 API checks across five suites on clean databases (44 permissions + 22 password reset + 34 search/localization + 39 gates/notifications + 18 new per-project permission checks), 16 drawer-behaviour + 4 pinned-mode UI checks driven in headless Chrome, and probes confirming the team/sign-off pickers populate and no longer corrupt state.team on step navigation. Screenshots reviewed at 1920x1080. Service-worker cache bumped to v3 so browsers pick up the new shell. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -100,10 +100,13 @@
|
||||
.step-num { display: block; font-size: 9px; opacity: .65; margin-bottom: 2px; }
|
||||
|
||||
/* ── MAIN ──
|
||||
The form sits in a wide two-column shell: a sticky work-package navigator on
|
||||
the left and the form itself filling the rest of the screen. */
|
||||
.wp-layout { display: flex; align-items: flex-start; gap: 0; max-width: 1760px; margin: 0 auto; }
|
||||
.main { flex: 1 1 auto; min-width: 0; max-width: none; margin: 0; padding: 28px 32px 64px; }
|
||||
The form uses the full width it's given. The work-package navigator is an
|
||||
auto-hiding overlay drawer (see below) rather than a column, so it never takes
|
||||
width away from the form — which matters most when this page is embedded in the
|
||||
suite's tab and every pixel is shared with the app chrome. */
|
||||
.wp-layout { display: block; width: 100%; margin: 0; }
|
||||
.main { min-width: 0; max-width: none; margin: 0; padding: 22px 28px 72px var(--gutter,34px);
|
||||
transition: padding-left .18s ease; }
|
||||
|
||||
.section { display: none; }
|
||||
.section.active { display: block; animation: fade .25s ease; }
|
||||
@@ -435,7 +438,7 @@
|
||||
border-radius:var(--radius); padding:7px 10px; font-size:11px; }
|
||||
|
||||
/* ── CREATION TOOL ───────────────────────────────────────────────── */
|
||||
.ctx-bar { max-width:1760px; margin:0 auto; padding:12px 28px; display:flex; align-items:center; gap:20px;
|
||||
.ctx-bar { max-width:none; margin:0; padding:12px 28px 12px var(--gutter,34px); display:flex; align-items:center; gap:20px;
|
||||
border-bottom:1px solid var(--border); background:var(--surface); flex-wrap:wrap; }
|
||||
.ctx-empty { color:var(--text-muted); font-size:13px; }
|
||||
.ctx-main .ctx-proj { font-weight:700; color:var(--text); font-size:14px; }
|
||||
@@ -447,7 +450,7 @@
|
||||
.ctx-meta code { background:var(--surface2); padding:1px 6px; border-radius:3px; color:var(--accent); }
|
||||
.link-btn { background:none; border:none; color:var(--accent); cursor:pointer; font-size:inherit; padding:0; text-decoration:underline; }
|
||||
|
||||
.mode-wrap { max-width:1760px; margin:0 auto; padding:16px 28px 0; display:flex; align-items:center; gap:16px; }
|
||||
.mode-wrap { max-width:none; margin:0; padding:16px 28px 0; display:flex; align-items:center; gap:16px; }
|
||||
.mode-toggle { display:inline-flex; border:1px solid var(--border-strong); border-radius:6px; overflow:hidden; }
|
||||
.mode-btn { padding:8px 18px; font-family:var(--sans); font-size:13px; font-weight:600; border:none; background:var(--surface);
|
||||
color:var(--text-muted); cursor:pointer; }
|
||||
@@ -471,7 +474,7 @@
|
||||
|
||||
/* ── WORK PACKAGE FORM ───────────────────────────────────────────── */
|
||||
.sop-hint { color:var(--accent) !important; }
|
||||
.release-banner { max-width:1760px; margin:0 auto; padding:0 28px; }
|
||||
.release-banner { max-width:none; margin:0; padding:0 28px 0 var(--gutter,34px); }
|
||||
.release-banner .rb-inner { margin-top:14px; border-radius:var(--radius); padding:11px 16px; font-size:13px; font-weight:600;
|
||||
display:flex; align-items:center; gap:10px; }
|
||||
.rb-ready { background:var(--accent-green-dim); color:var(--accent-green); border:1px solid #b6e3c6; }
|
||||
@@ -579,7 +582,7 @@
|
||||
|
||||
/* Section nav (jump chips) */
|
||||
.section-nav-bar{ position:sticky; top:0; z-index:30; display:flex; flex-wrap:wrap; gap:6px;
|
||||
padding:8px 12px; background:rgba(255,255,255,.94); backdrop-filter:blur(4px);
|
||||
padding:8px 12px 8px var(--gutter,34px); background:rgba(255,255,255,.94); backdrop-filter:blur(4px);
|
||||
border-bottom:1px solid var(--border); box-shadow:0 1px 4px rgba(20,30,50,.06);
|
||||
transition:transform .22s ease; }
|
||||
.section-nav-bar:empty{ display:none; }
|
||||
@@ -588,49 +591,6 @@
|
||||
border:1px solid var(--border); border-radius:14px; padding:4px 11px; cursor:pointer; white-space:nowrap; }
|
||||
.sec-chip:hover{ border-color:var(--accent); color:var(--accent); }
|
||||
|
||||
/* ── WP NAVIGATOR (left rail) ─────────────────────────────────────────
|
||||
Sticky list of every work package on the project. Click one to open it in
|
||||
the form; the one being edited is highlighted. */
|
||||
.wp-nav { flex:0 0 262px; width:262px; align-self:flex-start; position:sticky; top:var(--rail-top,0px);
|
||||
height:calc(100vh - var(--rail-top,0px)); display:flex; flex-direction:column;
|
||||
background:var(--surface); border-right:1px solid var(--border); }
|
||||
.wp-nav-head { display:flex; align-items:center; gap:8px; padding:14px 14px 8px; }
|
||||
.wp-nav-title { font-size:12px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted); }
|
||||
.wp-nav-count { color:var(--text-dim); font-weight:600; letter-spacing:0; }
|
||||
.wp-nav-collapse, .wp-nav-reopen { background:transparent; border:1px solid var(--border); border-radius:4px;
|
||||
color:var(--text-muted); cursor:pointer; font-size:14px; line-height:1; padding:2px 7px; margin-left:auto; }
|
||||
.wp-nav-collapse:hover, .wp-nav-reopen:hover { border-color:var(--accent); color:var(--accent); }
|
||||
.wp-nav-search { margin:0 14px 10px; padding:6px 9px; font-size:12px; font-family:inherit;
|
||||
border:1px solid var(--border); border-radius:4px; background:var(--bg); color:var(--text); }
|
||||
.wp-nav-search:focus { outline:none; border-color:var(--accent); }
|
||||
.wp-nav-list { flex:1 1 auto; overflow-y:auto; padding:0 8px 8px; }
|
||||
.wp-nav-group { font-size:10px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
|
||||
color:var(--text-dim); padding:10px 6px 5px; }
|
||||
.wp-nav-item { display:block; width:100%; text-align:left; background:transparent; border:0;
|
||||
border-left:3px solid transparent; border-radius:4px; padding:6px 8px; cursor:pointer;
|
||||
font-family:inherit; color:var(--text); }
|
||||
.wp-nav-item:hover { background:var(--surface2); }
|
||||
.wp-nav-item.active { background:var(--accent-dim); border-left-color:var(--accent); }
|
||||
.wp-nav-num { display:block; font-family:var(--mono); font-size:11px; font-weight:600; color:var(--accent); }
|
||||
.wp-nav-subj { display:block; font-size:12px; color:var(--text-muted); line-height:1.35;
|
||||
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.wp-nav-meta { display:flex; align-items:center; gap:6px; margin-top:3px; font-size:10px; color:var(--text-dim); }
|
||||
.wp-nav-dot { width:7px; height:7px; border-radius:50%; background:var(--text-dim); flex:0 0 auto; }
|
||||
.wp-nav-dot.ok { background:var(--accent-green); }
|
||||
.wp-nav-dot.open { background:var(--accent-amber); }
|
||||
.wp-nav-dot.hold { background:var(--red); }
|
||||
.wp-nav-empty { padding:12px 8px; font-size:12px; color:var(--text-dim); }
|
||||
.wp-nav-foot { border-top:1px solid var(--border); padding:9px 12px; display:flex; gap:8px; flex-wrap:wrap; }
|
||||
.wp-nav-reopen { display:none; position:sticky; top:var(--rail-top,0px); margin:10px 0 0 8px; align-self:flex-start; z-index:20; }
|
||||
/* Keep the rail's footer clear of the fixed save bar. */
|
||||
body.has-sticky-save .wp-nav { height:calc(100vh - var(--rail-top,0px) - 56px); }
|
||||
body.wp-nav-collapsed .wp-nav { display:none; }
|
||||
body.wp-nav-collapsed .wp-nav-reopen { display:block; }
|
||||
@media (max-width: 1100px) {
|
||||
.wp-nav { display:none; }
|
||||
.wp-nav-reopen { display:none !important; }
|
||||
}
|
||||
|
||||
/* ── SOP-inherited marker ───────────────────────────────────────────────────
|
||||
The "from SOP types" subtext used to sit under the field. It's now a small
|
||||
chip on the label with the detail in a hover tooltip (site comment 8/3).
|
||||
@@ -652,7 +612,7 @@
|
||||
.sop-chip:hover::after, .sop-chip:hover::before,
|
||||
.sop-chip:focus::after, .sop-chip:focus::before { opacity:1; }
|
||||
|
||||
/* ── people picker (Assignees / Distribution) ───────────────────────────────
|
||||
/* ── people picker (Assignees / Distribution / Predecessors) ─────────────────
|
||||
Multi-select over the SOP project team instead of a free-text list. */
|
||||
.people-pick { border:1px solid var(--border-strong); border-radius:4px; background:var(--surface);
|
||||
padding:5px 6px; min-height:38px; display:flex; flex-wrap:wrap; gap:5px; align-items:center; }
|
||||
@@ -689,6 +649,106 @@
|
||||
font-weight:700; letter-spacing:.02em; color:var(--red); background:var(--red-dim);
|
||||
border:1px solid #ffc4c4; white-space:nowrap; vertical-align:middle; }
|
||||
|
||||
/* ── WP NAVIGATOR — auto-hiding drawer ─────────────────────────────────────
|
||||
Was a fixed 262px column, which (a) stole width from the form and (b) vanished
|
||||
entirely under 1100px — so embedded in the suite tab it was never visible at
|
||||
all. Now it slides in over the content from a slim edge handle: hover (or tap)
|
||||
the handle to open, move away to close, or pin it open if you'd rather it stay.
|
||||
Nothing is taken from the form unless you pin it. */
|
||||
.wp-nav {
|
||||
position: fixed;
|
||||
top: var(--rail-top, 0px);
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 300px;
|
||||
max-width: 86vw;
|
||||
z-index: 120;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface);
|
||||
border-right: 1px solid var(--border-strong);
|
||||
box-shadow: 6px 0 22px rgba(20,30,50,.16);
|
||||
transform: translateX(-100%);
|
||||
transition: transform .18s ease;
|
||||
}
|
||||
body.wp-nav-open .wp-nav,
|
||||
body.wp-nav-pinned .wp-nav { transform: none; }
|
||||
/* Pinned: no shadow (it's part of the layout now) and the form shifts over. */
|
||||
body.wp-nav-pinned .wp-nav { box-shadow: none; }
|
||||
|
||||
/* The always-visible edge handle. Vertical so it costs ~26px of width. */
|
||||
.wp-nav-handle {
|
||||
position: fixed;
|
||||
top: calc(var(--rail-top, 0px) + 14px);
|
||||
left: 0;
|
||||
z-index: 119;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 12px 5px;
|
||||
writing-mode: vertical-rl;
|
||||
background: var(--surface);
|
||||
color: var(--text-muted);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-left: 0;
|
||||
border-radius: 0 5px 5px 0;
|
||||
box-shadow: 2px 0 8px rgba(20,30,50,.10);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
.wp-nav-handle:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.wp-nav-handle .wp-nav-handle-count {
|
||||
writing-mode: horizontal-tb; font-size: 10px; font-weight: 700; letter-spacing: 0;
|
||||
background: var(--accent-dim); color: var(--accent); border-radius: 8px; padding: 0 5px;
|
||||
}
|
||||
body.wp-nav-open .wp-nav-handle,
|
||||
body.wp-nav-pinned .wp-nav-handle { display: none; }
|
||||
|
||||
.wp-nav-head { display:flex; align-items:center; gap:8px; padding:12px 12px 8px; }
|
||||
.wp-nav-title { font-size:12px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted); }
|
||||
.wp-nav-count { color:var(--text-dim); font-weight:600; letter-spacing:0; }
|
||||
.wp-nav-btn { background:transparent; border:1px solid var(--border); border-radius:4px;
|
||||
color:var(--text-muted); cursor:pointer; font-size:13px; line-height:1; padding:3px 7px; }
|
||||
.wp-nav-btn:hover { border-color:var(--accent); color:var(--accent); }
|
||||
.wp-nav-btn.is-on { border-color:var(--accent); color:var(--accent); background:var(--accent-dim); }
|
||||
.wp-nav-head .wp-nav-btn:first-of-type { margin-left:auto; }
|
||||
.wp-nav-search { margin:0 12px 10px; padding:6px 9px; font-size:12px; font-family:inherit;
|
||||
border:1px solid var(--border); border-radius:4px; background:var(--bg); color:var(--text); }
|
||||
.wp-nav-search:focus { outline:none; border-color:var(--accent); }
|
||||
.wp-nav-list { flex:1 1 auto; overflow-y:auto; padding:0 8px 8px; }
|
||||
.wp-nav-group { font-size:10px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
|
||||
color:var(--text-dim); padding:10px 6px 5px; }
|
||||
.wp-nav-item { display:block; width:100%; text-align:left; background:transparent; border:0;
|
||||
border-left:3px solid transparent; border-radius:4px; padding:6px 8px; cursor:pointer;
|
||||
font-family:inherit; color:var(--text); }
|
||||
.wp-nav-item:hover { background:var(--surface2); }
|
||||
.wp-nav-item.active { background:var(--accent-dim); border-left-color:var(--accent); }
|
||||
.wp-nav-num { display:block; font-family:var(--mono); font-size:11px; font-weight:600; color:var(--accent); }
|
||||
.wp-nav-subj { display:block; font-size:12px; color:var(--text-muted); line-height:1.35;
|
||||
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.wp-nav-meta { display:flex; align-items:center; gap:6px; margin-top:3px; font-size:10px; color:var(--text-dim); }
|
||||
.wp-nav-dot { width:7px; height:7px; border-radius:50%; background:var(--text-dim); flex:0 0 auto; }
|
||||
.wp-nav-dot.ok { background:var(--accent-green); }
|
||||
.wp-nav-dot.open { background:var(--accent-amber); }
|
||||
.wp-nav-dot.hold { background:var(--red); }
|
||||
.wp-nav-empty { padding:12px 8px; font-size:12px; color:var(--text-dim); }
|
||||
.wp-nav-foot { border-top:1px solid var(--border); padding:9px 12px; display:flex; gap:8px; flex-wrap:wrap; }
|
||||
/* Keep the drawer's footer clear of the fixed save bar. */
|
||||
body.has-sticky-save .wp-nav { bottom: 54px; }
|
||||
/* Pinned: the page chrome shifts with the form so nothing hides behind the panel. */
|
||||
body.wp-nav-pinned { --gutter: 328px; }
|
||||
body.wp-nav-pinned .sticky-save { left: 300px; }
|
||||
|
||||
/* Narrow screens: never pin (there isn't the width) — overlay only. */
|
||||
@media (max-width: 900px) {
|
||||
body.wp-nav-pinned { --gutter: 34px; } /* no room to pin — overlay only */
|
||||
.wp-nav { width: 290px; }
|
||||
}
|
||||
|
||||
/* Sticky save bar */
|
||||
.sticky-save{ position:fixed; left:0; right:0; bottom:0; z-index:40; display:flex; align-items:center;
|
||||
justify-content:space-between; gap:14px; padding:10px 20px; background:#fff;
|
||||
|
||||
Reference in New Issue
Block a user