Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions deployments/stitch-frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

219 changes: 136 additions & 83 deletions deployments/stitch-frontend/src/pages/ResourceDetailPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import SourceMixBar from "../components/SourceMixBar";
import SectionHeader from "../components/SectionHeader";
import { FieldCard, FieldGrid } from "../components/FieldCard";
import { SOURCE_LABELS } from "../constants/sourceMeta";
import StructuredDataView from "../components/StructuredDataView";
import Button from "../components/Button";
import {
AI_SUGGESTION_FIELDS,
Expand All @@ -24,6 +23,19 @@ import {

const LLM_AUDIT_PRODUCER = "stitch-frontend";

const OBSERVED_AT_FORMATTER = new Intl.DateTimeFormat(undefined, {
year: "numeric",
month: "short",
day: "numeric",
});

function formatObservedAt(value) {
if (!value) return "—";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "—";
return OBSERVED_AT_FORMATTER.format(date);
}

function createPersistIntentId() {
if (globalThis.crypto?.randomUUID) {
return globalThis.crypto.randomUUID();
Expand Down Expand Up @@ -359,7 +371,45 @@ function OrganizationsSection({ data }) {
);
}

function SourceDetailCard({ source }) {
function TechnicalImportRecord({ sourceRecord }) {
const [isOpen, setIsOpen] = useState(false);
const panelId = useId();

return (
<div className="rounded-md border border-line bg-surface">
<button
type="button"
aria-expanded={isOpen}
aria-controls={panelId}
onClick={() => setIsOpen((current) => !current)}
className="flex w-full items-center justify-between px-4 py-3 text-sm font-semibold text-ink"
>
<span>Technical import record</span>
<span aria-hidden="true" className="text-ink-muted">
{isOpen ? "−" : "+"}
</span>
</button>
{isOpen && (
<div id={panelId} className="space-y-3 border-t border-line px-4 py-3">
<FieldGrid>
<FieldCard label="Record ID" value={sourceRecord.record_id} />
<FieldCard label="Run ID" value={sourceRecord.run_id} />
</FieldGrid>
<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-ink-muted">
Raw payload
</p>
<pre className="overflow-x-auto rounded-md border border-line bg-panel p-4 text-xs leading-6 text-ink">
{JSON.stringify(sourceRecord.payload, null, 2)}
</pre>
</div>
</div>
)}
</div>
);
}

function SourceRow({ source }) {
const [isOpen, setIsOpen] = useState(false);
const panelId = useId();
const hasId = Number.isFinite(source.id);
Expand All @@ -368,107 +418,112 @@ function SourceDetailCard({ source }) {
isLoading,
isError,
error,
} = useSourceDetail("oil-gas-field-sources", source.id, isOpen && hasId);
} = useSourceDetail("oil-gas-field-sources", source.id, hasId);

const sourceLabel = SOURCE_LABELS[source.source] ?? source.source;
const sourceRecord = sourceDetail?.source_record ?? null;

let metaLine;
if (sourceRecord) {
const producer = sourceRecord.producer ?? "—";
const observed = formatObservedAt(sourceRecord.observed_at);
metaLine = (
<span>
Imported by {producer} · {observed}
</span>
);
} else if (isLoading) {
metaLine = <span className="text-ink-muted">Loading source details…</span>;
} else if (isError) {
metaLine = (
<span className="text-danger">Unable to load source details</span>
);
} else {
metaLine = <span className="text-ink-muted">Imported by — · —</span>;
}

return (
<div className="rounded-md border border-gray-dark/15 bg-white p-4 space-y-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-1">
<p className="text-xs font-medium uppercase tracking-wide text-gray-dark/60">
<div className="rounded-md border border-line bg-panel p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0 space-y-1">
<p className="text-xs font-semibold uppercase tracking-wide text-ink-muted">
{sourceLabel}
</p>
<p className="text-lg font-semibold text-gray-dark">
<p className="text-sm text-ink">{metaLine}</p>
<p className="text-xs text-ink-muted">
{source.name ?? "Unnamed source"}
</p>
<p className="text-sm text-gray-dark/70">
Source row ID: {source.id ?? "Unavailable"}
</p>
</div>
<button
type="button"
disabled={!hasId}
<Button
variant="secondary"
aria-expanded={isOpen}
aria-controls={panelId}
disabled={!hasId}
onClick={() => setIsOpen((current) => !current)}
className="rounded-md border border-gray-dark bg-gray-light px-3 py-2 text-sm text-gray-dark hover:cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
>
{isOpen ? "Hide details" : "Show details"}
</button>
{isOpen ? "Hide" : "View"}
</Button>
</div>

<div
id={panelId}
hidden={!isOpen}
aria-hidden={!isOpen}
className="space-y-3 border-t border-gray-dark/10 pt-4"
>
{isLoading && (
<p className="text-sm text-gray-dark/70">Loading source details…</p>
)}
{isOpen && (
<div id={panelId} className="mt-4 space-y-4 border-t border-line pt-4">
{isLoading && (
<p className="text-sm text-ink-muted">Loading source details…</p>
)}

{isError && (
<p className="text-sm text-red-600">
Failed to load source details
{error?.message ? `: ${error.message}` : "."}
</p>
)}
{isError && (
<p className="text-sm text-danger">
Failed to load source details
{error?.message ? `: ${error.message}` : "."}
</p>
)}

{sourceDetail && (
<>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<FieldCard
label="Producer"
value={sourceDetail.source_record?.producer}
/>
</div>
<div className="space-y-2">
<p className="text-xs font-medium uppercase tracking-wide text-gray-dark/60">
Source Record
</p>
<pre className="overflow-x-auto rounded-md bg-gray-light p-4 text-xs leading-6 text-gray-dark">
{JSON.stringify(sourceDetail.source_record, null, 2)}
</pre>
</div>
</>
)}
</div>
{sourceDetail && !sourceRecord && (
<p className="text-sm text-ink-muted">
No import record available.
</p>
)}

{sourceRecord && (
<>
<FieldGrid>
<FieldCard label="Source name" value={source.name} />
<FieldCard label="Producer" value={sourceRecord.producer} />
<FieldCard
label="Observed at"
value={formatObservedAt(sourceRecord.observed_at)}
/>
<FieldCard label="Source row ID" value={source.id} />
</FieldGrid>
<TechnicalImportRecord sourceRecord={sourceRecord} />
</>
)}
</div>
)}
</div>
);
}

function SourceDetailsSection({ sources }) {
if (!Array.isArray(sources) || sources.length === 0) return null;

return (
<section>
<SectionHeader title="Source Details" />
<div className="space-y-4">
{sources.map((source) => (
<SourceDetailCard
key={`${source.source}-${source.id ?? source.name ?? "source"}`}
source={source}
/>
))}
</div>
</section>
);
}
function SourcesSection({ sources }) {
const hasSources = Array.isArray(sources) && sources.length > 0;

function SourceDataSection({ sourceData }) {
return (
<section>
<SectionHeader title="Source data" />
<details className="rounded-md border border-line bg-panel px-4 py-3">
<summary className="cursor-pointer text-sm font-semibold text-ink">
Source records
</summary>
<StructuredDataView
data={sourceData}
label="Source records"
className="mt-4"
/>
</details>
<SectionHeader title="Sources" />
{hasSources ? (
<div className="space-y-4">
{sources.map((source, idx) => (
<SourceRow
key={`${source.source}-${source.id ?? source.name ?? idx}`}
source={source}
/>
))}
</div>
) : (
<p className="text-sm text-ink-muted">
No sources attached to this resource.
</p>
)}
</section>
);
}
Expand Down Expand Up @@ -556,9 +611,7 @@ export default function ResourceDetailPage() {

<AISuggestionPanel endpoint={endpoint} resourceId={numericId} />

<SourceDetailsSection sources={detailView.source_data} />

<SourceDataSection sourceData={detailView.source_data} />
<SourcesSection sources={detailView.source_data} />
</div>
)}
</div>
Expand Down
Loading
Loading