| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| export interface OdooGrid { |
| key: string; |
| label: string; |
| enabled: boolean; |
| } |
|
|
| |
| |
| export interface OdooConfig { |
| applicable?: boolean; |
| |
| source?: string; |
| label?: string; |
| |
| preview?: string; |
| serverDb?: string; |
| serverUrl?: string; |
| apiUser?: string; |
| |
| serverDbEditable?: boolean; |
| grids?: OdooGrid[]; |
| syncEvery?: string; |
| syncOptions?: string[]; |
| syncFloorSeconds?: number; |
| frozen?: boolean; |
| frozenAt?: string; |
| canDisconnect?: boolean; |
| |
| notes?: string[]; |
| } |
|
|
| |
| |
| |
| |
| |
| const EVERY_LABEL: Record<string, string> = { |
| "30m": "Every 30 minutes", |
| "1h": "Every hour", |
| "4h": "Every 4 hours", |
| daily: "Once a day", |
| manual: "Only when I ask", |
| }; |
| export const everyLabel = (k: string): string => EVERY_LABEL[k] || k; |
|
|
| |
| export function sourceLine(cfg: OdooConfig): string { |
| if (cfg.frozen) return "Disconnected β these databases are frozen as static data"; |
| if (cfg.source === "env") return "Connected using this deployment's own credentials"; |
| if (cfg.source === "keychain") return "Connected using a key stored in this workspace"; |
| return "Not connected"; |
| } |
|
|
| export default function OdooConfigPanel({ |
| cfg, |
| busy, |
| onToggleGrid, |
| onSyncEvery, |
| onServerDb, |
| onDisconnect, |
| onReconnect, |
| }: { |
| cfg: OdooConfig; |
| busy?: boolean; |
| onToggleGrid: (key: string, enabled: boolean) => void; |
| onSyncEvery: (every: string) => void; |
| onServerDb: (db: string) => void; |
| onDisconnect: () => void; |
| onReconnect: () => void; |
| }) { |
| const grids = cfg.grids ?? []; |
| const options = cfg.syncOptions ?? []; |
| const on = grids.filter((g) => g.enabled).length; |
| return ( |
| <div className="conn-odoo"> |
| <p className="conn-card-meta">{sourceLine(cfg)}</p> |
| |
| {/* ββ the credential, as much of it as may be shown ββββββββββββββββββββββββββββββββββ */} |
| <div className="conn-odoo-row"> |
| <span className="conn-odoo-k">Key</span> |
| <span className="conn-odoo-v"> |
| {cfg.preview ? cfg.preview : "held on this deployment"} |
| {cfg.apiUser ? " Β· " + cfg.apiUser : ""} |
| </span> |
| </div> |
| {cfg.serverUrl ? ( |
| <div className="conn-odoo-row"> |
| <span className="conn-odoo-k">Server</span> |
| <span className="conn-odoo-v">{cfg.serverUrl}</span> |
| </div> |
| ) : null} |
| <div className="conn-odoo-row"> |
| <span className="conn-odoo-k">Database</span> |
| {cfg.serverDbEditable ? ( |
| // β A `defaultValue` + blur, not a controlled input: this component is PURE and holds |
| // no state, and a controlled field with no owner is a box you cannot type in. |
| <input |
| className="conn-odoo-v" |
| defaultValue={cfg.serverDb ?? ""} |
| maxLength={80} |
| disabled={busy || cfg.frozen} |
| aria-label="Odoo server database" |
| onBlur={(e) => { |
| const next = e.target.value.trim(); |
| if (next && next !== (cfg.serverDb ?? "")) onServerDb(next); |
| }} |
| /> |
| ) : ( |
| <span className="conn-odoo-v"> |
| {cfg.serverDb || "β"} |
| {/* R6's second sentence: a thing you cannot change says WHY, not nothing. */} |
| <em> β set on this deployment, not in this workspace</em> |
| </span> |
| )} |
| </div> |
| |
| {/* ββ R9's second reading: WHICH grids this workspace materialises βββββββββββββββββββ */} |
| <p className="conn-odoo-h"> |
| Databases to build <span className="conn-odoo-count">{on} of {grids.length}</span> |
| </p> |
| <div className="conn-odoo-grids"> |
| {grids.map((g) => ( |
| <label className="conn-odoo-grid" key={g.key}> |
| <input |
| type="checkbox" |
| checked={g.enabled} |
| disabled={busy || cfg.frozen} |
| onChange={(e) => onToggleGrid(g.key, e.target.checked)} |
| /> |
| <span>{g.label}</span> |
| </label> |
| ))} |
| </div> |
| {/* β SAID BEFORE IT IS DONE, not after. Unticking stops the NEXT sync from rebuilding a |
| database; it does not delete the one you already have. A checkbox that silently threw |
| away rows would be the worst control on this page. */} |
| <p className="conn-card-meta"> |
| Unticking one stops it being rebuilt on the next sync. Nothing you already have is |
| deleted. |
| </p> |
| |
| {/* ββ R11: the cadence, from the server's own preset list, floor included ββββββββββββ */} |
| <p className="conn-odoo-h">How often to sync</p> |
| <select |
| className="conn-odoo-every" |
| value={cfg.syncEvery ?? ""} |
| disabled={busy || cfg.frozen} |
| aria-label="How often to sync" |
| onChange={(e) => onSyncEvery(e.target.value)} |
| > |
| {options.map((o) => ( |
| <option value={o} key={o}> |
| {everyLabel(o)} |
| </option> |
| ))} |
| </select> |
| |
| {/* ββ R10: off, and back on ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */} |
| {cfg.frozen ? ( |
| <div className="conn-odoo-off"> |
| <p className="conn-card-meta"> |
| Disconnected{cfg.frozenAt ? " on " + cfg.frozenAt.slice(0, 10) : ""}. Every row and |
| every column you had is still here and still readable β nothing is being refreshed. |
| </p> |
| <button type="button" className="conn-card-go" disabled={busy} onClick={onReconnect}> |
| Reconnect |
| </button> |
| </div> |
| ) : cfg.canDisconnect ? ( |
| <button type="button" className="conn-odoo-danger" disabled={busy} onClick={onDisconnect}> |
| Disconnect Odoo |
| </button> |
| ) : null} |
| |
| {/* Whatever the server did with what it was sent β a clamp, a rewrite, a warning. */} |
| {(cfg.notes ?? []).length ? ( |
| <ul className="conn-odoo-notes"> |
| {(cfg.notes ?? []).map((n) => ( |
| <li key={n}>{n}</li> |
| ))} |
| </ul> |
| ) : null} |
| </div> |
| ); |
| } |
|
|