Add dropdown selector for individual row field changes in Dimensions tab

- Added dropdown that shows unique values from the group (outer/units/moq)
- Click icon to open dropdown and select value to change single row
- Icon turns green on hover (emerald color)
- Replaces sync all icon with ChevronDown dropdown icon
This commit is contained in:
Christian Vidal Wolf
2026-04-20 11:46:55 +02:00
parent f358b61229
commit 86c3ace6f8
+123 -12
View File
@@ -60,6 +60,7 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [lastClickedIndex, setLastClickedIndex] = useState<number | null>(null);
const [activeClusterKey, setActiveClusterKey] = useState<string | null>(null);
const [openDropdown, setOpenDropdown] = useState<{ rowIndex: number; field: 'outer' | 'units' | 'moq' } | null>(null);
const isDraggingRef = React.useRef(false);
const dragStartRef = React.useRef<number | null>(null);
@@ -253,6 +254,18 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
return () => window.removeEventListener('mouseup', handleGlobalMouseUp);
}, [nearDuplicateClusters]);
React.useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (openDropdown && !(e.target as Element)?.closest('.dropdown-container')) {
setOpenDropdown(null);
}
};
if (openDropdown) {
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}
}, [openDropdown]);
const toggleGroup = (key: string) => {
const next = new Set(expandedGroups);
if (next.has(key)) {
@@ -271,6 +284,47 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
setPendingAction({ group, sourceRow, fieldType: 'all' });
};
const handleSingleFieldChange = async (rowIndex: number, field: 'outer' | 'units' | 'moq', value: string | number) => {
const row = data[rowIndex];
const updatedRow = [...row];
if (field === 'outer') {
const parts = String(value).split('x').map(Number);
if (parts.length === 3) {
updatedRow[COLUMNS.OUTER_L] = parts[0];
updatedRow[COLUMNS.OUTER_W] = parts[1];
updatedRow[COLUMNS.OUTER_H] = parts[2];
}
} else if (field === 'units') {
updatedRow[COLUMNS.UNITS_OUTER] = value;
} else if (field === 'moq') {
updatedRow[COLUMNS.MOQ] = value;
}
await onSaveRow(rowIndex, updatedRow);
onCaptureState(`Updated ${field} for article ${row[COLUMNS.ARTICLE_NO]}`);
setOpenDropdown(null);
};
const getUniqueGroupValues = (group: DimensionGroup, field: 'outer' | 'units' | 'moq'): string[] => {
const values = new Set<string>();
group.rows.forEach(({ row }) => {
if (field === 'outer') {
const outer = `${row[COLUMNS.OUTER_L]}x${row[COLUMNS.OUTER_W]}x${row[COLUMNS.OUTER_H]}`;
if (outer !== 'undefinedxundefinedxundefined' && row[COLUMNS.OUTER_L] != null) {
values.add(outer);
}
} else if (field === 'units') {
const val = String(row[COLUMNS.UNITS_OUTER] ?? '');
if (val) values.add(val);
} else if (field === 'moq') {
const val = String(row[COLUMNS.MOQ] ?? '');
if (val) values.add(val);
}
});
return Array.from(values).sort();
};
const handleVerifyGroup = (e: React.MouseEvent, group: DimensionGroup) => {
e.stopPropagation();
group.rows.forEach(({ row, index }) => {
@@ -738,48 +792,105 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
"px-4 py-3 font-mono",
group.discrepancies.outer ? "text-amber-300" : "text-slate-400"
)}>
<div className="flex items-center gap-2 group/cell">
<div className="flex items-center gap-2 group/cell dropdown-container relative">
<button
onClick={() => handleSyncField(group, row, 'outer')}
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(openDropdown?.rowIndex === index && openDropdown?.field === 'outer' ? null : { rowIndex: index, field: 'outer' });
}}
disabled={syncing?.key === group.key}
title="Apply these Outer Dims to all in group"
title="Change value"
className="p-1 hover:bg-emerald-600/20 text-slate-600 hover:text-emerald-400 rounded opacity-0 group-hover:opacity-100 transition-opacity"
>
{syncing?.key === group.key && syncing?.field === 'outer' ? <Loader2 className="w-3 h-3 animate-spin" /> : <Layers className="w-3 h-3" />}
{syncing?.key === group.key && syncing?.field === 'outer' ? <Loader2 className="w-3 h-3 animate-spin" /> : <ChevronDown className="w-3 h-3" />}
</button>
<span>{row[COLUMNS.OUTER_L] !== undefined && row[COLUMNS.OUTER_L] !== null ? row[COLUMNS.OUTER_L] : '-'} × {row[COLUMNS.OUTER_W] !== undefined && row[COLUMNS.OUTER_W] !== null ? row[COLUMNS.OUTER_W] : '-'} × {row[COLUMNS.OUTER_H] !== undefined && row[COLUMNS.OUTER_H] !== null ? row[COLUMNS.OUTER_H] : '-'}</span>
{openDropdown?.rowIndex === index && openDropdown?.field === 'outer' && (
<div className="absolute top-full left-0 mt-1 bg-slate-800 border border-slate-600 rounded-md shadow-lg z-50 py-1 min-w-[150px]">
{getUniqueGroupValues(group, 'outer').map(val => (
<button
key={val}
onClick={(e) => {
e.stopPropagation();
handleSingleFieldChange(index, 'outer', val);
}}
className="w-full px-3 py-1.5 text-left text-xs text-slate-300 hover:bg-emerald-600/20 hover:text-emerald-400 transition-colors"
>
{val}
</button>
))}
</div>
)}
</div>
</td>
<td className={cn(
"px-4 py-3",
group.discrepancies.units ? "text-amber-300 font-bold" : "text-slate-400"
)}>
<div className="flex items-center gap-2 group/cell">
<div className="flex items-center gap-2 group/cell dropdown-container relative">
<button
onClick={() => handleSyncField(group, row, 'units')}
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(openDropdown?.rowIndex === index && openDropdown?.field === 'units' ? null : { rowIndex: index, field: 'units' });
}}
disabled={syncing?.key === group.key}
title="Apply this Units/Outer to all in group"
title="Change value"
className="p-1 hover:bg-emerald-600/20 text-slate-600 hover:text-emerald-400 rounded opacity-0 group-hover:opacity-100 transition-opacity"
>
{syncing?.key === group.key && syncing?.field === 'units' ? <Loader2 className="w-3 h-3 animate-spin" /> : <Layers className="w-3 h-3" />}
{syncing?.key === group.key && syncing?.field === 'units' ? <Loader2 className="w-3 h-3 animate-spin" /> : <ChevronDown className="w-3 h-3" />}
</button>
<span>{row[COLUMNS.UNITS_OUTER] !== undefined && row[COLUMNS.UNITS_OUTER] !== null ? row[COLUMNS.UNITS_OUTER] : '-'}</span>
{openDropdown?.rowIndex === index && openDropdown?.field === 'units' && (
<div className="absolute top-full left-0 mt-1 bg-slate-800 border border-slate-600 rounded-md shadow-lg z-50 py-1 min-w-[100px]">
{getUniqueGroupValues(group, 'units').map(val => (
<button
key={val}
onClick={(e) => {
e.stopPropagation();
handleSingleFieldChange(index, 'units', val);
}}
className="w-full px-3 py-1.5 text-left text-xs text-slate-300 hover:bg-emerald-600/20 hover:text-emerald-400 transition-colors"
>
{val}
</button>
))}
</div>
)}
</div>
</td>
<td className={cn(
"px-4 py-3",
group.discrepancies.moq ? "text-amber-300 font-bold" : "text-slate-400"
)}>
<div className="flex items-center gap-2 group/cell">
<div className="flex items-center gap-2 group/cell dropdown-container relative">
<button
onClick={() => handleSyncField(group, row, 'moq')}
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(openDropdown?.rowIndex === index && openDropdown?.field === 'moq' ? null : { rowIndex: index, field: 'moq' });
}}
disabled={syncing?.key === group.key}
title="Apply this MOQ to all in group"
title="Change value"
className="p-1 hover:bg-emerald-600/20 text-slate-600 hover:text-emerald-400 rounded opacity-0 group-hover:opacity-100 transition-opacity"
>
{syncing?.key === group.key && syncing?.field === 'moq' ? <Loader2 className="w-3 h-3 animate-spin" /> : <Layers className="w-3 h-3" />}
{syncing?.key === group.key && syncing?.field === 'moq' ? <Loader2 className="w-3 h-3 animate-spin" /> : <ChevronDown className="w-3 h-3" />}
</button>
<span>{row[COLUMNS.MOQ] !== undefined && row[COLUMNS.MOQ] !== null ? row[COLUMNS.MOQ] : '-'}</span>
{openDropdown?.rowIndex === index && openDropdown?.field === 'moq' && (
<div className="absolute top-full left-0 mt-1 bg-slate-800 border border-slate-600 rounded-md shadow-lg z-50 py-1 min-w-[100px]">
{getUniqueGroupValues(group, 'moq').map(val => (
<button
key={val}
onClick={(e) => {
e.stopPropagation();
handleSingleFieldChange(index, 'moq', val);
}}
className="w-full px-3 py-1.5 text-left text-xs text-slate-300 hover:bg-emerald-600/20 hover:text-emerald-400 transition-colors"
>
{val}
</button>
))}
</div>
)}
</div>
</td>
<td className="px-4 py-3 text-right">