Improve oversight change previews

This commit is contained in:
JB
2026-07-25 21:25:15 -04:00
parent 65aa6361a6
commit c5036e7bf0
5 changed files with 124 additions and 4 deletions
@@ -0,0 +1,99 @@
<script lang="ts">
type Entry = { name: string; kind: 'text' | 'file'; value?: string; fileName?: string; type?: string };
let {
module,
action,
payload,
currentSnapshot = null
}: {
module: string;
action: string;
payload: string;
currentSnapshot?: Record<string, unknown> | null;
} = $props();
const entries = $derived(JSON.parse(payload) as Entry[]);
const proposed = $derived(Object.fromEntries(entries.filter((entry) => entry.kind === 'text').map((entry) => [entry.name, entry.value ?? ''])));
const label = (value: string) => value.replace(/([A-Z])/g, ' $1').replaceAll('_', ' ').trim().toUpperCase();
const shown = (value: unknown) => value === null || value === undefined || value === '' ? 'NOT SET' : String(value);
const jsonValue = (value: unknown) => {
if (typeof value !== 'string' || (!value.startsWith('[') && !value.startsWith('{'))) return null;
try { return JSON.parse(value); } catch { return null; }
};
const attributeValue = (side: 'current' | 'proposed', key: string) => {
if (side === 'current') return currentSnapshot?.[key];
const names: Record<string, string> = {
calculation_type: 'calculationType',
gear_min: 'gearMin',
gear_max: 'gearMax',
weapon_min: 'weaponMin',
weapon_max: 'weaponMax',
skill_chip_min: 'chipMin',
skill_chip_max: 'chipMax',
cap_value: 'capValue'
};
return proposed[names[key] ?? key];
};
</script>
<section class="proposal-change-preview" aria-label="Proposed data comparison">
<div class="proposal-preview-head">
<div><span>CHANGE PREVIEW</span><small>LIVE REGISTRY COMPARED WITH CONTRIBUTOR SUBMISSION</small></div>
<b>{currentSnapshot ? 'BEFORE / AFTER' : 'NEW RECORD'}</b>
</div>
{#if module === 'attributes' && action === 'save'}
<div class="proposal-compare-grid">
<article class="proposal-editor-preview before">
<header><span>CURRENT LIVE</span><b>{currentSnapshot ? 'BEFORE' : 'NO LIVE RECORD'}</b></header>
{#if currentSnapshot}
<div class="proposal-attribute-grid">
<div class="preview-field identity"><span>NAME</span><strong>{shown(attributeValue('current', 'name'))}</strong></div>
{#each [['type','TYPE'],['category','CATEGORY'],['calculation_type','CALCULATION'],['unit','UNIT']] as field}
<div class="preview-field"><span>{field[1]}</span><strong>{shown(attributeValue('current', field[0]))}</strong></div>
{/each}
{#each [['gear','GEAR'],['weapon','WEAPON'],['skill_chip','SKILL CHIP']] as source}
<div class="preview-field range"><span>{source[1]} RANGE · OPTIONAL</span><div><strong>{shown(attributeValue('current', `${source[0]}_min`))}</strong><i>TO</i><strong>{shown(attributeValue('current', `${source[0]}_max`))}</strong></div></div>
{/each}
<div class="preview-field"><span>OPTIONAL CAP</span><strong>{shown(attributeValue('current', 'cap_value'))}</strong></div>
</div>
{:else}
<div class="proposal-empty-state"><b>NEW ATTRIBUTE DEFINITION</b><span>No live registry record will be replaced.</span></div>
{/if}
</article>
<article class="proposal-editor-preview after">
<header><span>PROPOSED DEFINITION</span><b>AFTER</b></header>
<div class="proposal-attribute-grid">
<div class="preview-field identity"><span>NAME</span><strong>{shown(attributeValue('proposed', 'name'))}</strong></div>
{#each [['type','TYPE'],['category','CATEGORY'],['calculation_type','CALCULATION'],['unit','UNIT']] as field}
<div class="preview-field"><span>{field[1]}</span><strong>{shown(attributeValue('proposed', field[0]))}</strong></div>
{/each}
{#each [['gear','GEAR'],['weapon','WEAPON'],['skill_chip','SKILL CHIP']] as source}
<div class="preview-field range"><span>{source[1]} RANGE · OPTIONAL</span><div><strong>{shown(attributeValue('proposed', `${source[0]}_min`))}</strong><i>TO</i><strong>{shown(attributeValue('proposed', `${source[0]}_max`))}</strong></div></div>
{/each}
<div class="preview-field"><span>OPTIONAL CAP</span><strong>{shown(attributeValue('proposed', 'cap_value'))}</strong></div>
</div>
</article>
</div>
{:else}
<div class="proposal-editor-preview generic after">
<header><span>PROPOSED {label(module)} RECORD</span><b>AFTER</b></header>
<div class="proposal-field-summary">
{#each entries as entry}
<div>
<span>{label(entry.name)}</span>
{#if entry.kind === 'file'}
<strong>{entry.fileName || 'UPLOADED FILE'}</strong><small>{entry.type || 'FILE EVIDENCE'}</small>
{:else if jsonValue(entry.value)}
<strong>{Array.isArray(jsonValue(entry.value)) ? `${jsonValue(entry.value).length} CONFIGURED ITEMS` : 'CONFIGURED DATA'}</strong>
<small>{entry.value}</small>
{:else}
<strong>{shown(entry.value)}</strong>
{/if}
</div>
{/each}
</div>
</div>
{/if}
</section>