Files
ever-gauzy/.stylelintrc.json
joel kalema 1f69869be0 Fix/polish organizations pages (#10140)
* fix(candidates): give the candidate profile its own stylesheet

`edit-candidate-main.component.ts` listed the Organization > Main tab's
stylesheet in its own `styleUrls`, so every selector in that file was compiled a
SECOND time carrying this template's `_ngcontent` attribute. The two templates
share no markup — only the file — which meant the organization tab could not be
restyled without silently restyling the candidate profile with it.

Copy across the rules this template actually renders: `.content`, the
`.organization-container` panel with its `.organization-photo` children (the
overlay `div`, the 100px `img`, the file `input` and the 68px `svg` placeholder),
the `.row .col` halving, and both responsive blocks.

Left behind, because this template renders none of them: `.main-form` (this one
uses `.employee-form`), `.employees-count-text`, `.tax-form-group`,
`.registration-form-group`, the `ga-currency` and `nb-select` overrides, `.col-6`,
and `.content.main` — the candidate's `.content` carries no `main` class.

Two declarations are dropped rather than copied, both no-ops here:
`width: min(563px, 100%)` on `.organization-container`, which this template
already overrides with bootstrap's `w-25` (`width: 25% !important`), and
`margin-left: 0 !important` on `.organization-photo`, which existed to cancel an
`ml-4` that only the organization template carried.

* fix(organizations): repair the edit-organization header content

Three things were wrong with what the card header rendered.

The headcount ran through the `json` pipe — `{{ organization?.totalEmployees |
json }}`. On a number that is a no-op, but `JSON.stringify(null)` is the STRING
"null", so an organization whose count had not been computed yet announced
"null Employees". It reads the value directly now, falling back to 0.

The logo was an unguarded `<img>`, so an organization with no image got the
browser's broken-image glyph and the alt text sitting in the header. Guard on
there being a URL and fall back to a briefcase icon, the same shape the employee
page uses for its own missing avatars. `logoFailed` also covers the case where a
URL is present but fails to load.

The name and the metadata beneath it were both plain lines of body text, so the
organization read no louder than its own headcount. Name and supporting copy are
now separate rows, with the count joined by official name and currency.

`setOrganization` exists so both subscriptions clear `logoFailed` — without that
a single broken image would leave the placeholder in place for every
organization selected afterwards. A back button joins the header, matching the
employee edit page.

* fix(organizations): restyle the edit-organization page header

The contact meta read louder than the organization it described. `Edit Public
Page` was `display: flex`, so the control stretched the full width of the header
and read as a banner rather than an action, and the organization name was 14px
600 — the same size as the headcount line beneath it.

Lay the header out as one row: back button, logo, identity, then the action
pinned to the trailing edge. The name takes a heading size and the supporting
copy sits under it as a wrapping muted row, dot-separated. `Edit Public Page` is
a real `<button>` now with a resting fill and a hover state, and the logo
placeholder is sized to match the image it replaces.

The old `@include respond(sm)` block stacked `.header-container` into a column,
which put the avatar above the name; the replacement wraps the action under the
identity instead and keeps the avatar beside it.

This stylesheet is NOT scoped to one template — `accounting.component.ts` lists
it alongside its own, so every selector here is compiled a second time against
the Accounting page. Everything above is nested under `.card-scroll`, a class
the Accounting card does not carry (its `nb-card` is `.card`), so none of it can
reach that page. `.body-header` and `.sub-header` stay exactly where they were,
for the reasons already recorded above them.

* fix(organizations): import SharedModule for the header back button

`EditOrganizationModule` declares `EditOrganizationComponent`, whose template now
renders `<ngx-back-navigation>`. That component is declared and exported by
`ComponentsModule`, which reaches this module through `SharedModule`; without the
import the element would be parsed as an unknown tag and silently render nothing.

* fix(organizations): repair headcount, logo and save behaviour on the Main tab

Three defects in this component, none of them cosmetic.

The headcount was destructured out of `route.parent.data`:

    tap(({ employeesCount }) => (this.employeesCount = employeesCount))

but the parent route resolves only `organization` and `organizationTaskSetting`.
There has never been an `employeesCount` key to pick up, so the field stayed
undefined and the panel rendered a bare "Employees" with no number in front of
it. The count lives on the organization itself, maintained by the employee
subscriber, so read it from there.

Picking a logo threw you off the page. `updateImageAsset` called
`updateOrganizationSettings()`, which ends by navigating to the organizations
list — so choosing an image saved the form and then abandoned it. Split the save
in two: `saveOrganization()` persists and returns whether it worked,
`updateOrganizationSettings()` persists and then navigates. The upload path takes
the first, the Save button the second, which also means Save no longer navigates
after a failed request.

The form seeded `imageUrl` from the `imageUrl` column alone, while the card
header a few pixels above resolves `image.fullUrl || imageUrl`. An organization
whose logo came from an image asset therefore showed the logo in the header and a
placeholder in the panel. Both read the same expression now; `imageUrl` is a
disabled control, so this is display-only and never reaches the update payload.

* fix(organizations): rebuild the Main tab as an identity panel and a form

The left panel was a 563px box holding a 100px logo and one line of text, so
most of it was empty; the form beside it was a bootstrap `.row`/`.col` grid whose
fields did not line up, and the Save button was positioned by `margin-top: 60px`
with nothing to scroll when the form outgrew the tab.

Lay it out the way the employee Account tab is laid out: an identity panel and a
form panel, each a bordered flex column. The panel now carries the name, official
name, description, status chips and a facts grid — headcount, time zone, start of
week, date and time format, bonus rules — all guarded, so an organization with
none of them shows a header and nothing else. The logo gains a placeholder, a
hover scrim and a camera badge, so the upload affordance is visible whether or
not an image is set.

The form is a two-column CSS grid. Three fields used to size themselves
independently of it: `ga-currency` was `inline-flex` with a 180px floor and tax
ID was pinned to `width: 80%`, both of which left their columns visibly narrower
than the fields beside them. Tags spans the full row. The fields scroll and Save
sits in a bordered footer beneath them.

Two `::ng-deep` blocks were bare top-level selectors. Angular drops the scoping
attribute from those, so `ga-currency .form-group` and
`nb-select.shape-rectangle .select-button` were global rules applying to every
such element in the app, not just this tab's. Both are `:host`-scoped now.

The tab keeps `height: calc(100vh - 19.25rem)`, which is how the Location and
Settings tabs size themselves too; converting the shared tabset to a flex chain
is a change to all three, not to this one. What changes is that the panels now
scroll their own overflow inside it.

The panel fill was a literal `rgba(126, 126, 143, 0.1)`, which is one fixed grey
across all eight themes; it follows `--gauzy-card-3` now.

* fix(organizations): stop stranding the Settings tab at the far edge

The route tabs are laid out as a flex row, and the strip carried

    li:last-of-type { margin-left: auto; }

An auto inline-start margin on the final flex item absorbs all the free space
before it, so with three route tabs "Settings" was pushed hard against the
opposite edge of the card while "Main" and "Location" stayed together on the
left. The three are peers — one group of tabs — and they now sit as one;
where that group goes is `justify-content`'s job, which the `.full-width` rule
below already sets.

The tab padding was declared only inside three nested max-width queries: `dsk`
(<=1532px) at 20px, a bare `1440px` at 25px and `xxl` (<=1280px) at 20px. They
share a specificity, so the later ones won where they overlapped and the
horizontal padding ran 20 -> 25 -> 20 as the window got NARROWER, while anything
wider than 1532px matched none of them and fell through to Nebular's own
padding. Replaced with one base value, narrowed once below `xxl`.

* fix(organizations): make the settings section index usable

The aside listing the ten settings sections had three problems.

Its markup was `<ul><span (click)="general.toggle()"><li>…</li></span>…</ul>`,
repeated ten times. A `<span>` is not a legal child of `<ul>`, and a span with a
click handler has no role, no tabindex and no Enter/Space handling — the entire
index was unreachable by keyboard. Each entry is now an `<li>` holding a real
`<button>`, and the ten hand-written copies collapse into a `@for` over a
`settingsSections` array declared beside the accordion refs.

Clicking an entry called `toggle()`, so clicking the section you were already
reading closed it. An index into a page should open, not toggle.

Nothing scrolled. The accordion is a single column of roughly two thousand lines,
so opening a section below the fold — Task Setting and Integrations are ninth and
tenth, behind a five-hundred-line Timer section — changed something entirely
off-screen and the lower half of the index looked inert. `openSection` now brings
the section it opened into view.

The ten `@ViewChild` accordion-item fields go with the old markup: they existed
only so the template could call `general.toggle()` and read `general?.expanded`,
and nothing reads them once the aside addresses sections by position. Two
`@ViewChildren` queries replace them — one for the items, one for their elements,
which is what the scroll needs. The `#general` template refs stay on the items as
labels; the `#generalBody` refs they sit beside are still read by each body's
`[hidden]`.

The row styling moves from the `<li>` to the button, which means handing back the
font, colour, border and text alignment a `<button>` overrides by default, and
adding the focus ring the list never needed while it could not be focused. On
small screens the list was laid out by setting `display: inline` on the `<ul>`
and hanging a `margin-right` off each item; it is a wrapping flex row now.

* fix(organizations): repair the settings page layout

Four measurements on this page disagreed with each other.

`.main-form` declared `overflow: hidden` and, three lines later, `overflow-y:
scroll`, while `.accordion-section` inside it declared `overflow: auto`. Two
nested scrollbars ran over the same content, and because the outer one moved
everything, the section index scrolled away from the sections it points at. The
accordion column is the only scroller now and the aside stays put beside it.

The column widths were stated three times and did not agree: a 180px aside, a
`.fields-section` of `calc(100% - 230px)`, and `justify-content: space-between`
to absorb whatever was left. That 50px discrepancy was the gap between the index
and the sections. The aside is a fixed 180px track, the sections take the rest,
and a real `gap` separates them.

Save sat ABOVE the accordion — the control that commits the page placed before
any of the fields it commits, scrolling out of reach on a page this long. It is
the foot of the column now, pinned below the scrolling sections and bordered,
matching the Main tab. The small-screen `order: 2` that used to flip it to the
bottom goes with it, since it is last in the markup.

`.col-6` was capped at `max-width: 49% !important` against bootstrap's own
`flex: 0 0 50%`, so every pair of half-width fields rendered 49% wide with a 2%
hole between them that no gutter accounted for.

Stacked below `md` the whole column scrolls as one, as it did before — the aside
is a wrapping row of ten buttons there, and pinning it would leave almost nothing
for the sections. The aside's flex basis is reset to `auto` in that block: the
form is a column there, so the 180px basis that sets its width on desktop would
otherwise set its height.

* fix(organizations): rebuild the Location tab to match its sibling tabs

The last of the three edit tabs still laid out as a bare 40%/60% split held
apart by `justify-content: space-between`, with no panels, no scrolling and the
Save button pushed down by the same trick. Give it the two bordered panels the
Main tab uses: address fields on the left, map on the right, fields scrolling
with Save pinned in a bordered footer beneath them.

`ga-location-form` places its fields on the bootstrap grid at widths picked for a
full-width form — col-11 for the search and both address lines, col-8 for country
and city, col-6 for the postcode. In a panel this narrow that read as five
different field widths stacked on top of each other rather than one column, so
every field now takes the panel's width. Latitude and longitude stay paired.

`leaflet.component.scss` pins the map to `calc(100vh - 30rem) !important`,
measured against the viewport rather than the box it sits in, so inside a panel
it either left a gap below itself or pushed out past the bottom. It fills the
panel now. Because that changes the map's box, `_setLocationFormValue` calls
`invalidateSize()` — Leaflet caches the container size when it builds the map, on
its own timer, and the method's docs ask callers who resize its box to do exactly
this.

Three rules went out with the rewrite. `.content` named nothing in this
template. `order: 1` on the map put it after a form already ahead of it in the
markup. And `:host ::ng-deep input { background-color: background-basic-color-1 }`
fought the card shell's own `input-appearance(42px, var(--gauzy-card-1, …))`,
winning on injection order — which is why this tab's inputs were the only ones on
the page that did not match the Main tab's.

`:host.ng-star-inserted` is now plain `:host`: `ng-star-inserted` is Angular's
marker for a node created by an embedded view, not an API to hang a layout on.
Save also gains the `type="button"` it needed to stop doubling as a submit.

* fix(organizations): repair the organization name cell in the list

This renderer draws the first column of the organizations list, once per row.

Its logo was a bare `<img src="{{ rowData.imageUrl }}">` with nothing guarding
it, so an organization with no logo drew the browser's broken-image glyph in its
row. It also read `imageUrl` alone, while the card header and the Main tab both
resolve `image.fullUrl` first and fall back to the column — so an organization
whose logo came from an upload had one everywhere except here. Guarded, resolved
the same way, and backed by a briefcase placeholder sized like the image it
replaces so rows without one keep the same rhythm.

The Default badge was painted one colour and lettered for another:

    [style.background]="background(rowData?.color)"
    [style.color]="backgroundContrast(rowData?.brandColor)"

`IOrganization` has no `color` — the fill was computed from `undefined` while the
text contrast was matched against `brandColor`. Both read `brandColor` now.

Two strings were hard-coded English in a translated app: `text="Default"` and
`Members count {{ n }}`. Both have keys already — `FORM.LABELS.DEFAULT` and
`ORGANIZATIONS_PAGE.EMPLOYEES`.

`rowData.tags.length` was reached without a guard, on a row object whose every
other access is optional; the list does load the relation, but this renderer is
not the one deciding that.

In the stylesheet, the logo's `0px 1px 1px rgba(0, 0, 0, 0.25)` drop shadow is
invisible against the dark themes and becomes `var(--gauzy-shadow, …)`, and the
member line's `11px`/`rgba(126, 126, 143, 0.75)` becomes the shared chip size and
the muted text ramp — that literal is the ramp at 75%, which read as a third,
dimmer grey beside every other muted label in the table.

* fix(organizations): tidy the remaining organizations list cells

`OrganizationsFullnameComponent` is gone. It was declared in `OrganizationsModule`
and re-exported from the table-components barrel, but the module declares no
`exports` and no template anywhere renders `ngx-organizations-fullname` — the
list's name column goes through `OrganizationWithTagsComponent`. It carried its
own copy of the unguarded-`<img>` bug fixed in that renderer, on a 70x63 tile
nothing drew, so it was dead code that still had to be read and kept honest.

The currency and employee-count cells each wrapped their value in
`<div class="m-2">`. That is a 0.5rem margin inside a cell the smart table already
pads, and the Name and Status columns add none — so those two columns' contents
sat half a step in from everything else in the row. The margin goes; the values
keep their weight.

* fix(organizations): drop dead CSS from the organization tabset

This component's template is two elements: `.org-settings` wrapping an
`<nb-route-tabset>`. Angular puts the component's content attribute on the
DESCENDANT half of a compiled selector, so a rule here can match neither an
ancestor nor anything inside the routed tab components below it. Five things in
this file were in one of those two positions.

`@forward '@shared/_edit-profile-form'` emitted 28 rules into this stylesheet,
every one rooted at `:host nb-card` or `:host nb-card-body` — an editing form's
chrome, including a whole `.employee-container` photo widget, in the file that
styles a tab strip. It re-exports mixins too, but none are used here and both
`var` and `themes` are already used directly.

`nb-card-body { overflow-y: hidden }` aimed at the card body in
`edit-organization.component.html`, which is this component's PARENT — it is
projected into that element, not the other way round.

`.form-group input`, `nb-tab`, `nb-tabset` and `.header` name nothing in the
template; the tabset element is `nb-route-tabset`, which the `::ng-deep` block
below reaches, and that is exactly why that block works where these did not.

Verified by compiling the file before and after: 45 rules to 13, 5203 bytes to
1713, with every removed selector rooted at `nb-card`, `nb-card-body`, or one of
the four names above, and nothing new appearing.

* fix(organizations): let the organization card fill the layout column

The empty band between the card and the footer on
/pages/organizations/edit/:id had one cause: `:host nb-card { height: unset }`
in this stylesheet compiles to (0,2,1) and silently outranked the global
`.card-scroll { height: calc(...) }` (0,1,0) from styles/_overrides.scss. The
card therefore sized to its CONTENT -- a header, a tab strip, and a tab body
that measured itself off `100vh` -- and always came out shorter than the column
it sits in.

The shell already hands the page the right box: `nb-layout-column` is a flex
column and `> router-outlet + *` (the routed page component, i.e. this `:host`)
gets `flex: 1 1 auto; min-height: 0`. That height just was not being passed on.
The card now takes it and hands it down through its body, which is the first
step of a continuous chain -- column -> host -> card -> body -> tabset -> tab.

`height: unset` is kept for `nb-card:not(.card-scroll)`, because this file is
also listed in `accounting.component.ts`'s styleUrls and Accounting's nested
cards still rely on it; that page has no flex chain to inherit a height from.

Also drops the header's `25px 20px 0` to `1rem 1.25rem 0.75rem`. The band above
the tab strip was the largest block of empty space on the page and it is chrome,
not content.

* fix(organizations): size and shape the organization tab strip

Two things, both about the tabset element itself.

The height. `nb-route-tabset`'s template is `<ul class="route-tabset">`
followed by a bare `<router-outlet>`, so the routed tab component is a SIBLING
of the strip inside this host -- which makes this the place to close the chain
the card now starts. The host, `.org-settings` and the tabset become a flex
column and the routed tab gets `flex: 1 1 auto; min-height: 0`, so each tab is
handed the space left under the strip instead of measuring the viewport for
itself. The outlet element is hidden: it is an anchor for the routed view, and
as a flex item it would take a share of the column.

The tabs. `.tab-text` was 16px/600, sitting on the inner `<span>` where it beat
both shared tab rules -- `route-tabset-tab-text-font-size` (0.875rem, weight
400) and the active-tab weight from `nb-tabset-overrides()` -- so every tab on
this page was a size larger and a weight heavier than the tab bars everywhere
else in the app, active or not. It now sets only what the shared rules do not.
Padding goes from 10px/25px to 7px/16px and the icon from 18x19 to 15px, both
sized for the type rather than for the 16px it used to be, and the link takes
the top half of the card's radius so the active tab and the panel under it read
as one folder.

The small-screen release moves from `sm` (480px) to `lg` (991px), where the
three tabs actually stack their panels. Between the two there was a 500px band
in which the tabs had stacked but nothing had stopped constraining them.

* fix(organizations): drop the 100vh guess from the three organization tabs

Main, Location and Settings each sized themselves with
`height: calc(100vh - 19.25rem)` -- three copies of one estimate of the page's
chrome, and an estimate that cannot be right in both states: the demo-account
banner alone puts the header band about 23px over the theme's `header-height`,
so the tab was that much shorter than the space it had. That is the other half
of the empty band under the card.

The card is a real frame now and the tabset passes its height down, so each tab
takes what it is given -- `flex: 1 1 auto; min-height: 0` on the host, and the
tab's own panel row fills it. Same box in all three, from one source.

Also removes `:host { nb-card-body { overflow: visible } }` from the Settings
tab. The card body belongs to `edit-organization.component.html`, two components
up, and a component stylesheet cannot match an ancestor -- this compiled to
`[_nghost-x] nb-card-body[_ngcontent-x]` against a template that renders no card
at all. It is called out rather than quietly deleted because the card body is
now the page frame's last flexible step.

* fix(organizations): put Registration Date and Tags on one row

The tags field carried `org__field--wide`, whose only rule was
`grid-column: 1 / -1`. In an eight-field, two-column grid that left Registration
Date alone on its row with a column of empty space beside it and then gave Tags
a row of its own -- two half-empty rows for two fields that pair. They share a
row now: eight fields, four rows, no holes. The rule went with it, since tags
was its only user.

Pairing them means the two halves have to agree, and they did not.
`ga-tags-color-input` renders its label and `ng-select` inside a plain `<div>`
rather than a `.form-group`, so nothing the grid says about a field reached it.
It is given the same column shape as its neighbour, and the label gap is stated
once for the whole grid -- two of the eight fields bring their label from a
child component (`ga-currency` and now `ga-tags-color-input`) while the other
six declare it inline, so the space under a label was previously whatever each
one happened to inherit.

* fix(organizations): lay the organization settings fields out on one grid

Every section of this form uses the bootstrap grid, and every section also
decided its own measure on top of it: General's fields were 75% of the section
body, its Time Format select 30% of its own half and its two time pickers 60% of
theirs; Design was a pair of 140px selects; Accounting's fiscal years a 400px row
of 170px pickers and its toggles a 500px column; Date limit a 50% row. Nine
widths down one column of one form, so no two sections began or ended at the same
x and almost nothing lined up with the field above it.

One measure now -- the section body -- divided by the `col-*` classes the markup
already carries. The four fields that use no column at all (Design's selects,
the fiscal years, and the bare `.form-group`s in Accounting and Date limit) take
the same halves through their own wrappers.

The rest of the misalignment was smaller and more mechanical:

- The gutter disagreed with itself. Bootstrap's is 30px (`.row { margin: 0 -15px }`
  against `.col-* { padding: 0 15px }`) and this file narrowed only the column
  half, to 10px, so every row of fields began and ended 5px outside its panel.
  Both halves are now declared together.
- Rows had no vertical rhythm. `.row` wraps, so one row in the markup is usually
  two or three on screen, and with `.form-group { margin-bottom: 0 }` those sat
  flush against each other.
- General's rows were `align-items: center`, which centres a short field against
  a tall one and takes its label out of line with the label beside it.
- Four info buttons sat OUTSIDE their label, between it and the control, each
  pushing its own field a full button-height down the row. They are inside the
  label now, and a label with one is no longer taller than a label without.
- "Standard Work Hours Per Day" was wrapped in a `col-6` nested inside a
  `col-xl-6 col-12`: half of a half, a quarter-width field beside full-width
  toggles.
- Time Zone sat low because `ga-timezone-selector` brings its own `.form-group`
  (keeping bootstrap's 1rem margin) and its own label, neither of which a rule
  scoped to this component can reach.
- General's selects were painted `background-basic-color-1 !important`, so ten
  controls in the first section were a different colour from the same controls
  in the other nine. They all take the card shell's `input-appearance` now.

The per-section rules this replaces were also addressed by POSITION, and had
already gone stale: an `#agent` section was added at index 8 and every
`:nth-of-type` rule from there down slid onto the wrong section, so the pair
meant for Task settings landed on Agent and Integrations lost its own. Nothing
here counts sections any more.

* fix(ui-core): carry a registered tab's router-link inputs onto its tab link

`DynamicTabsComponent.getRegisteredNbTabs()` mapped a `PageTabRegistryConfig`
onto an `NbRouteTab` by copying six fields — title, tabId, route, icon,
responsive and activeLinkOptions — and silently dropped the rest. Seven of the
dropped ones are inputs `nb-route-tabset` binds on every tab link:
`queryParams`, `queryParamsHandling`, `fragment`, `preserveFragment`,
`skipLocationChange`, `replaceUrl` and `state`.

So a tab that asked for `queryParamsHandling: 'merge'` navigated with
`undefined` instead and lost the page's query parameters on every switch. The
Time & Activity page registers all five of its tabs that way
(`employees/activity/layout/layout.component.ts`), and nothing said otherwise.

Also widens `PageTabsetPageId` with 'organization-edit-page', for the tabset
the organization edit page registers next.

* fix(organizations): render the organization tabs through the shared tabset

The organization edit page hand-built an `<nb-route-tabset [tabs] fullWidth>`
and then restated the whole tab bar locally under `:host ::ng-deep`, where no
shared rule could reach it: a 7px/16px tab box against the theme's
0.75rem/1.25rem, 15px icons with a 6px gap, an `!important` svg fill fighting
the `currentColor` chain, `letter-spacing: 0.02em` on `.tab-text`, its own copy
of the active fill, and a `display: flex !important` that suppressed the shared
stacked icon layout at every width. The result was a tab bar visibly smaller
and tighter than every other tab bar in the app.

It now renders `<gz-dynamic-tabs class="tabset-container">` over the page tab
registry — the same component, the same registry and the same template the
employee edit page uses — and registers its three tabs as `PageTabRegistryConfig`
instead of building `NbRouteTab[]` by hand. Nothing here styles a tab link any
more; the strip takes `includes/_tabset.scss`, `nb-tabset-overrides()` and
`nb-overrides()` like every other tabset. The compiled CSS for the strip and its
container chain is byte-identical to `edit-employee-profile.component.scss`.

Two things went with it. `fullWidth` sets Nebular's own
`:host(.full-width) .route-tabset` to `space-around`, which a local
`justify-content: flex-start !important` then had to undo — and that override
was written as a bare `::ng-deep` with no `:host`, so Angular emitted it
globally and it leaked onto the five plugin pages that do use `fullWidth`, but
only once this page had been visited. And `flex-wrap: wrap` on the strip is
replaced by the shared horizontal scroll port with the scrollbar hidden in all
three engines.

* fix(organizations): give the organization edit card the employee card's frame

Two differences from the employee edit card, both visible on the tab strip.

The card body kept Nebular's default `overflow: auto` where the employee card
is `overflow: hidden` (via `nb-card_overrides(hidden, …)`). The tabs own the
scrolling — each has a scroll port over its own fields — so the card body had
nothing to scroll and that `auto` only ever produced a SECOND scrollbar, down
the full height of the body and alongside the tab strip, on top of the real one
inside the panel. It is `hidden` now; the below-`lg` media query still hands the
overflow back, so a stacked tab cannot be clipped. The body is also a flex
column, so the settings component takes its height as a flex item the way
`ngx-edit-employee-profile` does.

The card also painted itself `gauzy-card-2` — the exact colour `nb-overrides()`
paints the ACTIVE route tab — so the selected tab was the same colour as the
strip it sat in and only its text weight marked it. The card takes the theme's
own `card-background-color` now, which is what makes the active tab read as a
folder tab lifted off the header; each tab still paints its own `gauzy-card-2`
panel, so the tint stays where it belongs.

Also drops `ngx-back-navigation` from the card header, and with it the
`SharedModule` import that existed only to provide it — nothing else in this
template comes from that module.

* fix(organizations): put the Location and Settings tabs on the shared panel scale

The Main tab already matches `@shared/_employee-tab-panel` step for step. Its
two siblings did not.

Location was on the same system but compacted at `$break-sm` (480px) where Main
and every employee tab compact at `$break-md` (767px), so between those widths
it was the only tab on the page still holding desktop padding — and its Save row
never tightened at all. It takes the `md` step now, actions included.

Settings was on a px chrome of its own:

  * the tab box framed its panels with `gap: 20px; padding: 20px` and the
    trailing side cut to 8px, against the `1rem` on all four sides its siblings
    use, so its content began at a different x. The 8px bought nothing — the
    scroll port below keeps its own gutter;
  * the section index had the card-3 fill and the radius of a panel but no
    hairline, `13px 18px` of padding, and `height: fit-content`, so it stopped
    at its last item while the sections beside it ran to the bottom of the row;
  * its heading was 14px/600 — the same size as the items under it — rather than
    the shared panel legend `.tab__legend` and `.org__group-title` both use;
  * an accordion section is this tab's panel, and had no hairline either, so ten
    stacked sections read as one undivided block;
  * Nebular renders `<div class="item-body">` INSIDE `nb-accordion-item-body`
    and both carried 15px, so a section's fields sat 30px in from the panel edge
    against the 1.25rem every other panel uses. The host is 0 now and the
    padding lives on the box that holds the fields. The `lg` rule that used to
    zero the host — a no-op once the host is 0 — narrows `.item-body` instead.

Nav items, list gaps and the `md` step follow the same rem scale.

* fix(organizations): rebuild the settings tab as one panel of section rows

The ten sections were ten cards: a `gauzy-card-3` fill, a hairline and the card
radius each, stacked on the `gauzy-card-2` tab body. On the light themes those
two surfaces composite to #fcfcfc boxes on a #f9f9f9 ground — three values apart
out of 255 — so the frames never read as frames. The right-hand column was a
ladder of faint near-identical bars with nine redundant edges down it, while the
index beside it was a single solid panel: two columns of one form built out of
different things.

`.fields-section` is the panel now, put together the way `.org__panel--form` and
`.tab__panel` already are — panel -> scroll port -> foot:

  * a section is a row, told apart from the row above it by a hairline. Nothing
    in the accordion paints a surface, draws a box or reserves a gap any more,
    which also means `nb-accordion-item` had to be told to let the panel through:
    Nebular's default theme paints one a literal `#fbfbfb` and `gauzy-dark` a
    sidebar token;
  * open is painted as a state. The open row takes the shared hover tint at rest
    — the same fill the index gives the entry pointing at it — with its title at
    full strength and a hairline under it. Before, an open section and a closed
    one were the same box with more inside it;
  * the header answers the pointer. It IS the control that opens its section, and
    it had no hover state at all; Nebular also host-binds `tabindex` onto it and
    then strips the ring back off with its own `:host:focus { outline: 0 }`;
  * Save is the panel's foot, `0.875rem 1.25rem` over a hairline, the box
    `.org__actions` and `.tab__actions` are. It was a bare row on the card body
    with a rule across the top of nothing.

The section index gives up its ten raised chips — each a `gauzy-sidebar-background-3`
fill, i.e. a SIDEBAR token inside a card, under a `--gauzy-shadow` that resolves
to a black drop shadow on six of the eight themes — for the shared flat-chrome
tokens every other list-of-links in the app uses: `gauzy-radius-sm` for the row,
`gauzy-hover-tint` for hover, `gauzy-active-tint` plus a primary rail for the row
you are on. Its two states used to be told apart by exactly one thing, since
hover and active both painted `background-basic-color-1`. It is also sized to its
content rather than stretched down the column, so ten short rows stop sitting
inside a panel drawn the whole way down the viewport.

Colour and metric fixes that came with it:

  * `.item-body` was targeted without `::ng-deep`. That div belongs to Nebular's
    template, so it carries Nebular's content attribute and never this
    component's, and Angular stamps this component's onto every compound selector
    that is not behind `::ng-deep` — the rule matched nothing, and every section's
    fields started 10px to the left of the section title they belong to;
  * the select trigger carried `background-color: background-basic-color-1
    !important` plus a `margin-right: 10px` on a `.select-button` already set to
    `width: 100%`, so every select ran 10px past its own field;
  * the unchecked toggle track was a solid mid grey, which at a glance reads as a
    toggle that is on. It takes `toggle-basic-background-color` with
    `toggle-basic-border-color` for the edge;
  * `::ng-deep .ng-value` was written bare at the start of a selector list, which
    drops the scoping attribute — one tab of the organization editor was setting
    `z-index: 2` on every selected value in every `ng-select` in the app;
  * the chevron sat at Nebular's `right: 1rem` against a header padded to
    1.25rem, and was the only icon on the page drawn inside a ring;
  * `pt-2` on seventeen columns and `mt-2` on three stacked on top of the grid's
    own rhythm, so the longest section ran 1.5rem loose against 1rem elsewhere;
  * `.select-wrapper` took the row rhythm twice, and the last-row release could
    not reach `.design-select`, `.year-pick` or the bare `.form-group`s, so those
    sections closed with a 2.25rem gutter against every other section's 1.25rem.

* fix(ui-core): paint the edit-card fields on the surface they actually sit on

Both edit cards passed `gauzy-card-1` to `input-appearance()`. That is the CARD's
surface, the bottom of the elevation ramp, and it is the right fill for a field
that sits on the card — but no field on either page does. Every organization tab
and every employee tab puts its fields on a `gauzy-card-3` panel, two steps up
that ramp: card-1 -> the `gauzy-card-2` tab body -> the card-3 panel.

On the light themes the mistake is invisible, card-1 being white against a card-3
that composites to about #fcfcfc. On `gauzy-dark` it is the whole problem: card-1
is an opaque `rgba(18, 18, 20, 1)` while the panel composites to about #202022,
so each field read as a black hole punched through its panel rather than as a
control resting on it. `gauzy-card-4` is the next step up from the panel, which
is what a control on a card-3 surface should be — 6 % white over the panel's 4 %
on `gauzy-dark`, 75 % over 50 % on the light themes — and it is already the
choice `job-search.component.scss` makes for the same situation. The old value
stays as the fallback for any theme that does not emit card-4.

The height stops being a hardcoded `42px` and takes `$default-height`, which is
`calc(select-medium-text-line-height + input-medium-padding-y * 2)` — one line
box plus both block insets, i.e. the height `nb-select` computes for ITSELF out
of the density tokens, and 2.375rem at the current preset. The literal predates
that preset, so the mixin's `min-height: … !important` was propping every field
4px above the box its own control had already worked out, and the two would drift
further apart the next time the density tokens move.

The organization call is scoped to `.card-scroll` rather than replacing the one
on `:host`. That stylesheet is also listed in `accounting.component.ts`'s own
`styleUrls`, so everything in it compiles a second time against that template,
and Accounting's fields DO sit directly on its card, where card-1 is correct.
`.card-scroll` is on this page's `nb-card` and not on Accounting's, which is the
hook the rest of that file already uses to mean "the organization editor only";
at (0,2,2) against (0,1,1) it wins both arguments and leaves every other
declaration the mixin emits identical. `edit-employee.component.scss` has no such
second template — it is that component's alone — so it is changed in place.

* fix(employees): give the settings tab the organization settings design

The two pages present the same thing — a long form split into collapsible
sections with an index into them — and were built out of different parts. This
sheet is now the counterpart of `edit-organization-other-settings.component.scss`
rule for rule: compiling both and diffing the analogous rules leaves 23 of 25
byte-identical, and the two that differ do so only in where the value sits (the
field inset lives on `.item-body` there and on `.settings__fields` here, because
that page is stuck on bootstrap rows while this one has a grid).

  * ONE panel, not four. The sections were four cards on the card-2 tab body,
    which on the light themes is #fcfcfc boxes on a #f9f9f9 ground. They are rows
    of `.settings__sections` now, divided by a hairline, with the open row taking
    the shared hover tint and a divider under it, and the header answering hover
    and keyboard focus;
  * Save is the panel's foot rather than a bare row that scrolled away with the
    sections, which also makes this tab a fixed frame: `:host` drops the
    `height: auto` / `overflow-y: auto` pair it shared with `employee-tab-host`
    and takes the definite height the tabset already hands it;
  * the index drops its 12.5rem track of outlined pills — each reserving a
    transparent 1px border so the active one could swap it for
    `color-primary-transparent-default` — for the shared flat-chrome tokens, and
    is sized to its content instead of stretched down the column, where four
    entries sat inside a panel drawn the whole way down the tab;
  * the chevron loses the ring drawn round it on every row and moves to the x the
    section title starts at, Nebular having pinned it at its own `accordion-padding`;
  * a toggle is a cell of the field grid, so it is drawn as a control: the same
    fill, the same `$control-hairline` inset ring and the same `$default-height`
    floor as the input beside it. It was a transparent box outlined in
    `gauzy-border-default-color` — the hairline meant for dividing a panel from
    the page, not for drawing a control — which on the light themes is a #f0f0f2
    line around nothing on a #fcfcfc panel;
  * every toggle on this page carries an info button, and `nbButton` brings a
    control-sized box with it, so it was the tallest thing in the box — taller
    than the 24px text line and much taller than the 16px switch. Clamped, the
    way the field labels' buttons already are;
  * `.settings__fields` had `padding: 0 1.25rem 0.25rem`, i.e. no top inset at
    all, so the first row of fields started hard against the header above it. It
    takes the 1.25rem `.tab__fields` uses, and `gap` now does both halves of the
    rhythm so there is no per-field margin to release under the last row;
  * the local `.ng-select-container, .select-button { background-color:
    var(--gauzy-card-1) !important }` goes; the card shell hands every control
    under it one fill.

* fix(employees): open a settings section from the rail instead of toggling it

Each rail entry called `toggle()` on its accordion item and stopped there, which
had two consequences. Clicking the section you were already reading closed it,
leaving the rail with nothing marked active while that section's fields were
still the ones on screen. And because the sections are one scrolling column,
opening anything below the fold moved nothing into view, so the lower entries
looked inert.

The rail is an index into the page, so it opens rather than toggles and brings
the section it opened with it — the same behaviour, and the same implementation,
as the organization settings rail.

The item is matched to its element through two `ViewChildren` queries over
`NbAccordionItemComponent`, one read as the component and one as an `ElementRef`.
They walk the same template in the same order, so an item's position in one is
its element's position in the other; that is what lets the existing per-section
template refs keep addressing the rail entries by name.

* fixed the spell error

* fix AI comments

* style(organizations): tidy settings layout styles

* style(organizations): format settings components

* fix(organizations): address review findings

- Remove unused Input import from OrganizationWithTagsComponent
- Add missing id="settings-section-taskSetting" so the aside Task
  Settings button's aria-controls resolves to a real element
- Declare postcss-scss devDependency required by .stylelintrc.json
  customSyntax

* build: add postcss-scss to yarn.lock

package.json declared postcss-scss without a matching lockfile entry,
so CI's `yarn install --frozen-lockfile` failed with "Your lockfile
needs to be updated".
2026-09-11 08:53:06 +02:00

7 lines
88 B
JSON

{
"customSyntax": "postcss-scss",
"rules": {
"selector-type-no-unknown": false
}
}