From a4805cf0cca50156a60231e7161b4f20630d311d Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Fri, 3 Oct 2025 15:56:35 +0000
Subject: [PATCH] feat: Implement Array Field Comparison
---
src/components/moderation/ArrayFieldDiff.tsx | 218 ++++++++++++++++++
src/components/moderation/FieldComparison.tsx | 15 +-
2 files changed, 232 insertions(+), 1 deletion(-)
create mode 100644 src/components/moderation/ArrayFieldDiff.tsx
diff --git a/src/components/moderation/ArrayFieldDiff.tsx b/src/components/moderation/ArrayFieldDiff.tsx
new file mode 100644
index 00000000..584b596d
--- /dev/null
+++ b/src/components/moderation/ArrayFieldDiff.tsx
@@ -0,0 +1,218 @@
+import { Badge } from '@/components/ui/badge';
+import { Plus, Minus, Edit, Check } from 'lucide-react';
+import { formatFieldValue } from '@/lib/submissionChangeDetection';
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+
+interface ArrayFieldDiffProps {
+ fieldName: string;
+ oldArray: any[];
+ newArray: any[];
+ compact?: boolean;
+}
+
+interface ArrayDiffItem {
+ type: 'added' | 'removed' | 'modified' | 'unchanged';
+ oldValue?: any;
+ newValue?: any;
+ index: number;
+}
+
+export function ArrayFieldDiff({ fieldName, oldArray, newArray, compact = false }: ArrayFieldDiffProps) {
+ const [showUnchanged, setShowUnchanged] = useState(false);
+
+ // Compute array differences
+ const differences = computeArrayDiff(oldArray || [], newArray || []);
+ const changedItems = differences.filter(d => d.type !== 'unchanged');
+ const unchangedCount = differences.filter(d => d.type === 'unchanged').length;
+ const totalChanges = changedItems.length;
+
+ if (compact) {
+ return (
+
+
+ {fieldName} ({totalChanges} changes)
+
+ );
+ }
+
+ return (
+
+
+
+ {fieldName} ({differences.length} items, {totalChanges} changed)
+
+ {unchangedCount > 0 && (
+
+ )}
+
+
+
+ {differences.map((diff, idx) => {
+ if (diff.type === 'unchanged' && !showUnchanged) {
+ return null;
+ }
+
+ return (
+
+ );
+ })}
+
+
+ );
+}
+
+function ArrayDiffItemDisplay({ diff }: { diff: ArrayDiffItem }) {
+ const isObject = typeof diff.newValue === 'object' || typeof diff.oldValue === 'object';
+
+ switch (diff.type) {
+ case 'added':
+ return (
+
+
+
+ {isObject ? (
+
+ ) : (
+
+ {formatFieldValue(diff.newValue)}
+
+ )}
+
+
+ );
+
+ case 'removed':
+ return (
+
+
+
+ {isObject ? (
+
+ ) : (
+
+ {formatFieldValue(diff.oldValue)}
+
+ )}
+
+
+ );
+
+ case 'modified':
+ return (
+
+
+
+
+
+ {isObject ? (
+
+ ) : (
+ formatFieldValue(diff.oldValue)
+ )}
+
+
+ {isObject ? (
+
+ ) : (
+ formatFieldValue(diff.newValue)
+ )}
+
+
+
+
+ );
+
+ case 'unchanged':
+ return (
+
+
+
+ {isObject ? (
+
+ ) : (
+ formatFieldValue(diff.newValue)
+ )}
+
+
+ );
+ }
+}
+
+function ObjectDisplay({ value, className = '' }: { value: any; className?: string }) {
+ if (!value || typeof value !== 'object') {
+ return {formatFieldValue(value)};
+ }
+
+ return (
+
+ {Object.entries(value).map(([key, val]) => (
+
+ {key.replace(/_/g, ' ')}:
+ {formatFieldValue(val)}
+
+ ))}
+
+ );
+}
+
+/**
+ * Compute differences between two arrays
+ */
+function computeArrayDiff(oldArray: any[], newArray: any[]): ArrayDiffItem[] {
+ const results: ArrayDiffItem[] = [];
+ const maxLength = Math.max(oldArray.length, newArray.length);
+
+ // Simple position-based comparison
+ for (let i = 0; i < maxLength; i++) {
+ const oldValue = i < oldArray.length ? oldArray[i] : undefined;
+ const newValue = i < newArray.length ? newArray[i] : undefined;
+
+ if (oldValue === undefined && newValue !== undefined) {
+ // Added
+ results.push({ type: 'added', newValue, index: i });
+ } else if (oldValue !== undefined && newValue === undefined) {
+ // Removed
+ results.push({ type: 'removed', oldValue, index: i });
+ } else if (!isEqual(oldValue, newValue)) {
+ // Modified
+ results.push({ type: 'modified', oldValue, newValue, index: i });
+ } else {
+ // Unchanged
+ results.push({ type: 'unchanged', oldValue, newValue, index: i });
+ }
+ }
+
+ return results;
+}
+
+/**
+ * Deep equality check
+ */
+function isEqual(a: any, b: any): boolean {
+ if (a === b) return true;
+ if (a == null || b == null) return a === b;
+ if (typeof a !== typeof b) return false;
+
+ if (typeof a === 'object') {
+ if (Array.isArray(a) && Array.isArray(b)) {
+ if (a.length !== b.length) return false;
+ return a.every((item, i) => isEqual(item, b[i]));
+ }
+
+ const keysA = Object.keys(a);
+ const keysB = Object.keys(b);
+ if (keysA.length !== keysB.length) return false;
+
+ return keysA.every(key => isEqual(a[key], b[key]));
+ }
+
+ return false;
+}
diff --git a/src/components/moderation/FieldComparison.tsx b/src/components/moderation/FieldComparison.tsx
index 24c154e8..96876565 100644
--- a/src/components/moderation/FieldComparison.tsx
+++ b/src/components/moderation/FieldComparison.tsx
@@ -1,7 +1,8 @@
+import { Badge } from '@/components/ui/badge';
import { formatFieldName, formatFieldValue } from '@/lib/submissionChangeDetection';
import type { FieldChange, ImageChange } from '@/lib/submissionChangeDetection';
-import { Badge } from '@/components/ui/badge';
import { ArrowRight } from 'lucide-react';
+import { ArrayFieldDiff } from './ArrayFieldDiff';
interface FieldDiffProps {
change: FieldChange;
@@ -11,6 +12,18 @@ interface FieldDiffProps {
export function FieldDiff({ change, compact = false }: FieldDiffProps) {
const { field, oldValue, newValue, changeType } = change;
+ // Check if this is an array field that needs special handling
+ if (Array.isArray(oldValue) && Array.isArray(newValue)) {
+ return (
+
+ );
+ }
+
const getChangeColor = () => {
switch (changeType) {
case 'added': return 'text-green-600 dark:text-green-400';