Group history entries together, remove (optional) from change reason
1. History entries saved at the same time by the same user now display as a single grouped entry (resolution date + remediation plan together) 2. Removed '(optional)' from the change reason placeholder — engineers should treat it as expected, even though the backend allows empty 3. Save button now saves both resolution date AND remediation plan in one call (removed the onBlur auto-save on the date field) so they share a timestamp and group correctly in history
This commit is contained in:
@@ -255,7 +255,6 @@ export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded,
|
||||
type="date"
|
||||
value={resolutionDate}
|
||||
onChange={e => setResolutionDate(e.target.value)}
|
||||
onBlur={() => handleSaveMetadata({ resolution_date: resolutionDate || null })}
|
||||
style={{
|
||||
width: '100%',
|
||||
background: 'rgba(15,23,42,0.8)',
|
||||
@@ -299,7 +298,7 @@ export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded,
|
||||
{remediationPlan.length}/2000
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleSaveMetadata({ remediation_plan: remediationPlan || null })}
|
||||
onClick={() => handleSaveMetadata({ resolution_date: resolutionDate || null, remediation_plan: remediationPlan || null })}
|
||||
disabled={metaSaving}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: '0.3rem',
|
||||
@@ -326,7 +325,7 @@ export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded,
|
||||
type="text"
|
||||
value={changeReason}
|
||||
onChange={e => { if (e.target.value.length <= 500) setChangeReason(e.target.value); }}
|
||||
placeholder="Reason for change (optional)"
|
||||
placeholder="Reason for change"
|
||||
style={{
|
||||
width: '100%', marginTop: '0.5rem',
|
||||
background: 'rgba(15,23,42,0.6)',
|
||||
@@ -346,26 +345,44 @@ export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded,
|
||||
{/* Change History */}
|
||||
{detail.history && detail.history.length > 0 && (
|
||||
<Section title="Change History" icon={<Clock style={{ width: '14px', height: '14px' }} />}>
|
||||
{detail.history.map(h => (
|
||||
<div key={h.id} style={{ marginBottom: '0.6rem', paddingBottom: '0.5rem', borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||
<span style={{ fontSize: '0.68rem', fontFamily: 'monospace', color: '#94A3B8' }}>
|
||||
{h.field_name === 'resolution_date' ? '📅' : '📋'} {h.field_name.replace('_', ' ')}
|
||||
</span>
|
||||
<span style={{ fontSize: '0.6rem', color: '#475569' }}>
|
||||
{h.changed_at ? new Date(h.changed_at).toLocaleDateString() : ''}
|
||||
</span>
|
||||
{(() => {
|
||||
// Group entries by timestamp + user (entries saved together appear as one)
|
||||
const groups = [];
|
||||
for (const h of detail.history) {
|
||||
const ts = h.changed_at ? new Date(h.changed_at).toISOString().slice(0, 19) : '';
|
||||
const lastGroup = groups[groups.length - 1];
|
||||
if (lastGroup && lastGroup.ts === ts && lastGroup.user === h.changed_by) {
|
||||
lastGroup.entries.push(h);
|
||||
} else {
|
||||
groups.push({ ts, user: h.changed_by, reason: h.change_reason, date: h.changed_at, entries: [h] });
|
||||
}
|
||||
}
|
||||
return groups.map((group, gi) => (
|
||||
<div key={gi} style={{ marginBottom: '0.6rem', paddingBottom: '0.5rem', borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '0.2rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: '#475569' }}>{group.user}</span>
|
||||
<span style={{ fontSize: '0.6rem', color: '#475569' }}>
|
||||
{group.date ? new Date(group.date).toLocaleDateString() : ''}
|
||||
</span>
|
||||
</div>
|
||||
{group.entries.map(h => (
|
||||
<div key={h.id} style={{ marginBottom: '0.2rem' }}>
|
||||
<span style={{ fontSize: '0.65rem', color: '#64748B' }}>
|
||||
{h.field_name === 'resolution_date' ? '📅 ' : '📋 '}
|
||||
</span>
|
||||
<span style={{ fontSize: '0.68rem', color: '#EF4444' }}>{h.old_value || '—'}</span>
|
||||
<span style={{ fontSize: '0.68rem', color: '#475569' }}> → </span>
|
||||
<span style={{ fontSize: '0.68rem', color: '#10B981' }}>
|
||||
{h.field_name === 'remediation_plan' ? (h.new_value && h.new_value.length > 60 ? h.new_value.slice(0, 60) + '…' : (h.new_value || '—')) : (h.new_value || '—')}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{group.reason && (
|
||||
<div style={{ fontSize: '0.6rem', color: '#64748B', fontStyle: 'italic', marginTop: '0.15rem' }}>{group.reason}</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.7rem', color: '#64748B', marginTop: '0.15rem' }}>
|
||||
<span style={{ color: '#EF4444' }}>{h.old_value || '—'}</span>
|
||||
<span style={{ color: '#475569' }}> → </span>
|
||||
<span style={{ color: '#10B981' }}>{h.field_name === 'remediation_plan' ? (h.new_value && h.new_value.length > 60 ? h.new_value.slice(0, 60) + '…' : (h.new_value || '—')) : (h.new_value || '—')}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.6rem', color: '#475569', marginTop: '0.1rem' }}>
|
||||
{h.changed_by}{h.change_reason ? ` · ${h.change_reason}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
));
|
||||
})()}
|
||||
</Section>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user