mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 11:45:24 +02:00
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:
@@ -60,6 +60,7 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||||
const [lastClickedIndex, setLastClickedIndex] = useState<number | null>(null);
|
const [lastClickedIndex, setLastClickedIndex] = useState<number | null>(null);
|
||||||
const [activeClusterKey, setActiveClusterKey] = useState<string | 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 isDraggingRef = React.useRef(false);
|
||||||
const dragStartRef = React.useRef<number | null>(null);
|
const dragStartRef = React.useRef<number | null>(null);
|
||||||
@@ -253,6 +254,18 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
return () => window.removeEventListener('mouseup', handleGlobalMouseUp);
|
return () => window.removeEventListener('mouseup', handleGlobalMouseUp);
|
||||||
}, [nearDuplicateClusters]);
|
}, [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 toggleGroup = (key: string) => {
|
||||||
const next = new Set(expandedGroups);
|
const next = new Set(expandedGroups);
|
||||||
if (next.has(key)) {
|
if (next.has(key)) {
|
||||||
@@ -271,6 +284,47 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
setPendingAction({ group, sourceRow, fieldType: 'all' });
|
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) => {
|
const handleVerifyGroup = (e: React.MouseEvent, group: DimensionGroup) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
group.rows.forEach(({ row, index }) => {
|
group.rows.forEach(({ row, index }) => {
|
||||||
@@ -738,48 +792,105 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
"px-4 py-3 font-mono",
|
"px-4 py-3 font-mono",
|
||||||
group.discrepancies.outer ? "text-amber-300" : "text-slate-400"
|
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
|
<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}
|
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"
|
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>
|
</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>
|
<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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className={cn(
|
<td className={cn(
|
||||||
"px-4 py-3",
|
"px-4 py-3",
|
||||||
group.discrepancies.units ? "text-amber-300 font-bold" : "text-slate-400"
|
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
|
<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}
|
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"
|
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>
|
</button>
|
||||||
<span>{row[COLUMNS.UNITS_OUTER] !== undefined && row[COLUMNS.UNITS_OUTER] !== null ? row[COLUMNS.UNITS_OUTER] : '-'}</span>
|
<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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className={cn(
|
<td className={cn(
|
||||||
"px-4 py-3",
|
"px-4 py-3",
|
||||||
group.discrepancies.moq ? "text-amber-300 font-bold" : "text-slate-400"
|
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
|
<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}
|
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"
|
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>
|
</button>
|
||||||
<span>{row[COLUMNS.MOQ] !== undefined && row[COLUMNS.MOQ] !== null ? row[COLUMNS.MOQ] : '-'}</span>
|
<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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right">
|
<td className="px-4 py-3 text-right">
|
||||||
|
|||||||
Reference in New Issue
Block a user