// --------------------------------------------------------------------------- // settings / DatabasePermsPane.tsx — ONE DATABASE'S RULE, given the whole pane. // (wave 40, owner instruction 7 · W40-T14.) // // ⭐ WHY IT EXISTS, IN THE OWNER'S OWN WORDS: *"Manage user, Access section: show // only the Database, with just a checkbox and an Edit button (put the Metrics // field toggle under there). Edit opens the whole database's Fields and Filters // to toggle, with a Back button to return."* // // ⚠ THE PARENTHESIS IN THAT QUOTE IS SUPERSEDED, AND ONLY THE PARENTHESIS. Owner instruction 13 // (W40-T16) folded the Metrics toggle one level further in: it is no longer a box "under there" // but one CHECKBOX PER METRIC inside this pane's Hide-fields list. The room, the Back button and // the Fields/Filters pairing are instruction 7's and are unchanged. Read the quote as the reason // this file exists, never as a description of the controls it draws today. // // What it replaces is a single-row INLINE ACCORDION. That shape was right when // the list was two databases and the panels were the only thing under them; it // is wrong now, because a filter builder and a field list opened inside a row of // a list of every database the tenant has are two menus that failed to close. // A rule about one database gets a room, and the room says which database. // // ⛔ IT OWNS NO STATE AND WRITES NOTHING. Every value comes from the caller's // DRAFT and every change goes back out as a callback, exactly as // `ModulePermsList` does — `PermsEditor` holds the draft, the confirm and the // PUT. This component is a layout and a set of handlers, so the account page // keeps one source of truth for what is about to be saved. // // ⛔⛔ THE `lockedKey` / `hideableKeys` PAIR BELOW IS LOAD-BEARING AND ITS // FAILURE IS SILENT. Drop either and one click on "Hide all" hides the database's // own identity column: a record whose faithful enforcement is a table of blank // rows, which the server's PUT validation ACCEPTS because the identity column is // a perfectly known field key. Both halves ship together or the pane is a defect. // --------------------------------------------------------------------------- import { FilterBuilderPanel, FieldsHidePanel } from "../filter-kit"; import type { FilterTree } from "../customer-grid/types"; import type { PermsModule, PermsRecord } from "./permsModel"; import { filterOf, hiddenSetFor, hideAllKeys, identityKey, showAllKeys } from "./permsModel"; import "./perms.css"; export interface DatabasePermsPaneProps { /** The database being edited: its label titles the pane, its fields feed both panels. */ module: PermsModule; /** * ⚠ THE WHOLE DRAFT, keyed by module, NOT this module's entry. `filterOf` and `hiddenSetFor` * are where "an absent filter is an empty tree" and "absent hidden fields is an empty set" are * spelled, once; taking a bare entry would make this file spell them a second time, and two * spellings of one default are what those helpers exist to prevent. */ entries: PermsRecord; /** Return to the account page. The SAME function the account page's Escape handler calls. */ onBack: () => void; onFilter: (next: FilterTree | null) => void; onToggleHidden: (fieldKey: string) => void; onSetHidden: (keys: string[]) => void; /* * ⭐⭐ W40-T16 (OWNER INSTRUCTION 13) — `onMetrics` IS GONE FROM THIS PANE, AND SO IS THE BOX * IT DREW. Owner, verbatim: *"Fold 'Metrics fields (lookback measures over this database)' into * the 'Hide fields' checkboxes, one row per metric, named 'Metric - Revenue', 'Metric - Order' * and so on, so a user can check the ones the permissioning is limited to."* * * ⛔ A DELETION RATHER THAN A HIDE, because the control it replaces answered a COARSER * question. One box said "this account may build measures over this database, or may not"; * the Hide-fields list below now carries one checkbox per bound measure, so the same admin * decides WHICH ones. Leaving the blanket box beside them would put two controls on one screen * that can contradict each other, and the record has no way to express the contradiction. * * ⚠ THE CAPABILITY ITSELF IS UNTOUCHED. `PermsEntry.metrics` still rides the wire, still parses * (`parseEntry`) and is still emitted (`toPutBody`), because a record written before this * ticket may carry an explicit revocation. What honours it here is `hiddenSetFor` below, on the * READ side: a blanket `metrics: false` paints every metric row as hidden without rewriting * anybody's stored rule. See the note at that call. */ /** People this tenant can name in a `user` condition. Absent ⇒ the panel says so. */ userOptions?: string[]; } export function DatabasePermsPane({ module, entries, onBack, onFilter, onToggleHidden, onSetHidden, userOptions, }: DatabasePermsPaneProps) { return (