mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 14:25:23 +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 [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">
|
||||
|
||||
Reference in New Issue
Block a user