fix(pitch-admin): render JSONB arrays as inline table editors
Some checks failed
Build pitch-deck / build-and-push (push) Failing after 57s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 30s
CI / Deploy (push) Failing after 3s
Some checks failed
Build pitch-deck / build-and-push (push) Failing after 57s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 30s
CI / Deploy (push) Failing after 3s
Arrays of objects (funding_schedule, founder_salary_schedule, etc.) now render as editable tables with per-field inputs, add/remove row buttons, instead of a raw JSON string in a single text input. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,92 @@ export default function EditScenarioPage() {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{items.map(a => {
|
{items.map(a => {
|
||||||
const isEdited = edits[a.id] !== undefined
|
const isEdited = edits[a.id] !== undefined
|
||||||
|
// Detect arrays of objects for structured editing
|
||||||
|
const isObjectArray = Array.isArray(a.value) && a.value.length > 0 && typeof a.value[0] === 'object' && a.value[0] !== null
|
||||||
|
|
||||||
|
if (isObjectArray) {
|
||||||
|
const rows = isEdited ? (JSON.parse(edits[a.id]) as Record<string, unknown>[]) : (a.value as Record<string, unknown>[])
|
||||||
|
const cols = Object.keys(rows[0] || {})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={a.id} className="border border-white/[0.06] rounded-xl overflow-hidden">
|
||||||
|
<div className="flex items-center justify-between px-4 py-2.5 bg-white/[0.02]">
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-white/90">{a.label_en || a.label_de}</span>
|
||||||
|
<span className="text-xs text-white/40 font-mono ml-2">{a.key}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const newRow: Record<string, unknown> = {}
|
||||||
|
cols.forEach(c => { newRow[c] = typeof rows[0][c] === 'number' ? 0 : '' })
|
||||||
|
const updated = [...rows, newRow]
|
||||||
|
setEdit(a.id, JSON.stringify(updated))
|
||||||
|
}}
|
||||||
|
className="text-[10px] px-2 py-1 rounded bg-white/[0.06] text-white/60 hover:text-white hover:bg-white/[0.1]"
|
||||||
|
>
|
||||||
|
+ Row
|
||||||
|
</button>
|
||||||
|
{isEdited && (
|
||||||
|
<button
|
||||||
|
onClick={() => saveAssumption(a)}
|
||||||
|
disabled={savingId === a.id}
|
||||||
|
className="bg-indigo-500 hover:bg-indigo-600 text-white text-[10px] px-2.5 py-1 rounded flex items-center gap-1 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<Save className="w-3 h-3" /> Save
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table className="w-full text-xs">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-white/[0.06]">
|
||||||
|
{cols.map(c => (
|
||||||
|
<th key={c} className="text-left py-2 px-3 text-white/40 font-medium uppercase tracking-wider">{c}</th>
|
||||||
|
))}
|
||||||
|
<th className="w-8" />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows.map((row, ri) => (
|
||||||
|
<tr key={ri} className="border-b border-white/[0.04] hover:bg-white/[0.02]">
|
||||||
|
{cols.map(c => (
|
||||||
|
<td key={c} className="py-1.5 px-3">
|
||||||
|
<input
|
||||||
|
type={typeof row[c] === 'number' ? 'number' : 'text'}
|
||||||
|
value={row[c] as string | number}
|
||||||
|
onChange={e => {
|
||||||
|
const updated = rows.map((r, i) => {
|
||||||
|
if (i !== ri) return r
|
||||||
|
const val = typeof r[c] === 'number' ? Number(e.target.value) || 0 : e.target.value
|
||||||
|
return { ...r, [c]: val }
|
||||||
|
})
|
||||||
|
setEdit(a.id, JSON.stringify(updated))
|
||||||
|
}}
|
||||||
|
className="w-full bg-transparent border-b border-transparent hover:border-white/10 focus:border-indigo-500/50 text-white font-mono py-0.5 focus:outline-none"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
<td className="py-1.5 px-1">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const updated = rows.filter((_, i) => i !== ri)
|
||||||
|
setEdit(a.id, JSON.stringify(updated))
|
||||||
|
}}
|
||||||
|
className="text-white/30 hover:text-rose-400 p-1"
|
||||||
|
title="Remove row"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const currentValue = isEdited
|
const currentValue = isEdited
|
||||||
? edits[a.id]
|
? edits[a.id]
|
||||||
: typeof a.value === 'object'
|
: typeof a.value === 'object'
|
||||||
|
|||||||
Reference in New Issue
Block a user