diff --git a/gui-ts/src/styles.css b/gui-ts/src/styles.css index 84690ea..820b712 100644 --- a/gui-ts/src/styles.css +++ b/gui-ts/src/styles.css @@ -149,6 +149,8 @@ html, body, #root { @media (max-width: 880px) { .tab-grid.two, .tab-grid.three { grid-template-columns: 1fr; } .panel-row { grid-template-columns: 140px 1fr; } + .action-row-inline { flex-direction: column; align-items: stretch; } + .action-row-inline .btn { width: 100%; } } /* Inputs */ @@ -202,6 +204,10 @@ html, body, #root { align-items: center; margin: 4px 0 8px 0; } +.action-row-inline { + justify-content: flex-end; + margin: 0; +} .action-row { display: flex; justify-content: flex-end; gap: 8px; padding-top: 10px; } /* Tables */ @@ -241,8 +247,43 @@ html, body, #root { outline-offset: -1px; background: rgba(56, 189, 248, 0.1); } +.data-table tbody tr:focus-within td { + background: rgba(56, 189, 248, 0.08); +} .data-table tbody td .panel-input { padding: 3px 6px; font-size: 11px; } .data-table .empty-row { text-align: center; color: var(--text-muted); padding: 18px; } +.data-table-compact thead th { padding: 4px 6px; font-size: 11px; } +.data-table-compact tbody td { padding: 2px 4px; } +.data-table-compact tbody td .panel-input { padding: 2px 4px; font-size: 10px; } + +.table-actions-col { + position: sticky; + right: 0; + z-index: 2; + background: var(--panel-3); + box-shadow: -1px 0 0 var(--border); +} +.data-table thead .table-actions-col { + background: var(--panel); + z-index: 3; +} +.data-table tbody tr:focus-within .table-actions-col, +.data-table tbody tr.row-selected .table-actions-col { + background: #162338; +} + +.data-table .btn:focus-visible, +.data-table .panel-input:focus-visible, +.data-table select:focus-visible, +.data-table input[type="checkbox"]:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; + box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.18); +} + +.data-table-compact .btn { + min-height: 24px; +} /* KPI grid */ .kpi-grid { @@ -338,6 +379,18 @@ html, body, #root { .uplot-host .u-legend { color: var(--text-dim); font-size: 11px; } .uplot-host .u-wrap { background: var(--panel-3); } +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + /* 3D wellbore */ .wellbore-3d-wrap { border: 1px solid var(--border); diff --git a/gui-ts/src/ui/common/CheckboxField.tsx b/gui-ts/src/ui/common/CheckboxField.tsx index fd82aef..732ca42 100644 --- a/gui-ts/src/ui/common/CheckboxField.tsx +++ b/gui-ts/src/ui/common/CheckboxField.tsx @@ -4,6 +4,7 @@ export type CheckboxFieldProps = { onChange: (checked: boolean) => void; label?: string; disabled?: boolean; + ariaLabel?: string; }; export function CheckboxField(props: CheckboxFieldProps) { @@ -12,6 +13,7 @@ export function CheckboxField(props: CheckboxFieldProps) { props.onChange(e.target.checked)} diff --git a/gui-ts/src/ui/tabs/FluidTab.tsx b/gui-ts/src/ui/tabs/FluidTab.tsx index 7d8a97d..652eeeb 100644 --- a/gui-ts/src/ui/tabs/FluidTab.tsx +++ b/gui-ts/src/ui/tabs/FluidTab.tsx @@ -12,6 +12,7 @@ type Props = { store: CaseStore }; export function FluidTab({ store }: Props) { const { state, update } = store; + const isMetric = state.unitsSelection === 1; const estimated = useMemo( () => ({ @@ -65,7 +66,7 @@ export function FluidTab({ store }: Props) { label="Tubing Gradient" htmlFor="tubingGrad" hint={ - state.unitsSelection === 1 + isMetric ? "Pa/m when SI (UnitsSelection=1); from XML" : "psi/ft imperial oilfield; converted to Pa/m in the API" } @@ -79,16 +80,19 @@ export function FluidTab({ store }: Props) {
Heuristic bulk-liquid ρ(water cut, API, water SG) → gradient ≈{" "} - {state.unitsSelection === 1 + {isMetric ? `${estimated.paM.toFixed(1)} Pa/m` : `${estimated.psiPerFt.toFixed(4)} psi/ft`} . Not wired into the C solve yet (see COMPUTE_PLAN).
+

+ Dual reference: {estimated.psiPerFt.toFixed(4)} psi/ft · {estimated.paM.toFixed(1)} Pa/m +

); diff --git a/gui-ts/src/ui/tabs/PumpTab.tsx b/gui-ts/src/ui/tabs/PumpTab.tsx index 30cf6cb..d617eba 100644 --- a/gui-ts/src/ui/tabs/PumpTab.tsx +++ b/gui-ts/src/ui/tabs/PumpTab.tsx @@ -14,6 +14,7 @@ type Props = { store: CaseStore }; export function PumpTab({ store }: Props) { const { state, update } = store; + const isMetric = state.unitsSelection === 1; const pumpOptions = useMemo(() => { const mmVals = PUMP_PLUNGER_INCH_OPTIONS.map((inchVal) => pumpDiameterMmFromInches(inchVal)); @@ -37,7 +38,11 @@ export function PumpTab({ store }: Props) { - + update("pumpFriction", v)} /> - + ("depth"); const [selectedSegment, setSelectedSegment] = useState(null); + const [traceabilityQuery, setTraceabilityQuery] = useState(""); + const dlsUnitLabel = caseState.unitsSelection === 1 ? "deg/100 m" : "deg/100 ft"; const trajectorySegments = useMemo(() => buildTrajectorySegments(caseState.survey), [caseState.survey]); const sideLoadProfile = primary?.profiles?.sideLoadProfile ?? null; const pumpDiag = useMemo(() => computePumpPlacement(caseState), [caseState]); + const filteredTraceability = useMemo(() => { + const fields = result?.fieldTraceability?.fields ?? []; + const q = traceabilityQuery.trim().toLowerCase(); + if (!q) return fields; + return fields.filter((row) => { + return ( + row.xmlKey.toLowerCase().includes(q) || + row.category.toLowerCase().includes(q) || + row.notes.toLowerCase().includes(q) + ); + }); + }, [result?.fieldTraceability?.fields, traceabilityQuery]); const dynacardData = useMemo(() => { const fdmSeries = toSeries(fdm ?? undefined); @@ -240,8 +254,7 @@ export function ResultsTab({

- Click a row or 3D segment to cross-highlight. DLS is shown numerically only (deg per - 100 ft MD). + Click a row or 3D segment to cross-highlight. DLS is shown numerically only ({dlsUnitLabel}).

@@ -251,7 +264,7 @@ export function ResultsTab({ - + @@ -449,6 +462,18 @@ export function ResultsTab({ Categories mirror docs/engineering/field-traceability.md ( physics, metadata, parseCalibration, payloadInactive, parsedUnused).

+
+ setTraceabilityQuery(e.target.value)} + /> + + Showing {filteredTraceability.length} / {result.fieldTraceability.fields.length} + +
MD start MD end ΔMDDLS (deg/100)DLS ({dlsUnitLabel})
@@ -456,14 +481,16 @@ export function ResultsTab({ + - {result.fieldTraceability.fields.map((row) => ( + {filteredTraceability.map((row) => ( + ))} diff --git a/gui-ts/src/ui/tabs/RodStringTab.tsx b/gui-ts/src/ui/tabs/RodStringTab.tsx index c499d89..4335de1 100644 --- a/gui-ts/src/ui/tabs/RodStringTab.tsx +++ b/gui-ts/src/ui/tabs/RodStringTab.tsx @@ -57,6 +57,7 @@ function diameterOptionsFor(rowDiameter: number) { export function RodStringTab({ store }: Props) { const { state, addTaperRow, removeTaperRow, updateTaperRow, setTaper, update } = store; const [taperLengthMode, setTaperLengthMode] = useState<"length" | "count">("length"); + const [compactRows, setCompactRows] = useState(false); const depthUnit = state.unitsSelection === 1 ? "m" : "ft"; @@ -102,6 +103,13 @@ export function RodStringTab({ store }: Props) { heuristic — verify for your vendor tables.

+ {taperLengthMode === "count" && ( +

+ Joint counts are converted using RodLengthForSteel /{" "} + RodLengthForFiberglass from the case (fallbacks: 25 ft / 37.5 ft in + imperial). Non-integer equivalent counts are allowed. +

+ )}
Section length: @@ -119,6 +127,15 @@ export function RodStringTab({ store }: Props) { > Joint count + @@ -133,9 +150,12 @@ export function RodStringTab({ store }: Props) { {totals.length.toFixed(1)} {depthUnit}
+

+ Rod table compact mode is {compactRows ? "enabled" : "disabled"}. +

-
XML field Category In fileNotes
{row.xmlKey} {row.category} {row.presentInXml ? "yes" : "—"}{row.notes}
+
@@ -148,8 +168,8 @@ export function RodStringTab({ store }: Props) { - - + + @@ -221,6 +241,7 @@ export function RodStringTab({ store }: Props) { updateTaperRow(i, { @@ -244,6 +265,7 @@ export function RodStringTab({ store }: Props) { updateTaperRow(i, { guideTypeToken: v })} ariaLabel={`Taper ${i + 1} guide type`} /> @@ -252,16 +274,18 @@ export function RodStringTab({ store }: Props) { updateTaperRow(i, { rodGuideWeightLbfPerFt: v })} ariaLabel={`Taper ${i + 1} rod guide weight`} /> -
#Guide # guides Guide typeRod guide wtGuide wt (lb/ft)
+ diff --git a/gui-ts/src/ui/tabs/TrajectoryTab.tsx b/gui-ts/src/ui/tabs/TrajectoryTab.tsx index 09fc2d4..ccde9f8 100644 --- a/gui-ts/src/ui/tabs/TrajectoryTab.tsx +++ b/gui-ts/src/ui/tabs/TrajectoryTab.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import type { CaseStore } from "../../state/useCaseStore"; import { Fieldset } from "../common/Fieldset"; import { NumberField } from "../common/NumberField"; @@ -7,6 +8,7 @@ type Props = { store: CaseStore }; export function TrajectoryTab({ store }: Props) { const { state, addSurveyRow, insertSurveyRowBelow, removeSurveyRow, updateSurveyRow, setSurvey } = store; + const [compactRows, setCompactRows] = useState(false); function loadVertical() { const depth = state.pumpDepth || 1727; @@ -40,6 +42,10 @@ export function TrajectoryTab({ store }: Props) { Enter survey stations from surface to TD. Minimum curvature method (API Bulletin D20) is applied in the solver. First row should be 0 MD, 0 Inc, 0 Az.

+

+ Use Insert below to add stations mid-trajectory without rebuilding the + full table. +

+ {state.survey.length} station{state.survey.length === 1 ? "" : "s"}
+

+ Trajectory table compact mode is {compactRows ? "enabled" : "disabled"}. +

- +
- + @@ -100,13 +120,14 @@ export function TrajectoryTab({ store }: Props) { ariaLabel={`Azi ${i + 1}`} /> - -
# MD Inclination (°) Azimuth (°)ActionsActions
-
+
+
@@ -124,7 +145,7 @@ export function TrajectoryTab({ store }: Props) { ))} {state.survey.length === 0 && (
+ No survey stations. Add rows or load a preset.