fix(pricing): edit units inline

This commit is contained in:
Christian Vidal Wolf
2026-05-25 16:24:28 +02:00
parent 9c3e26c523
commit 5537fea03e
+219 -6
View File
@@ -858,11 +858,16 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
return isNaN(n) ? String(val) : n.toFixed(2); return isNaN(n) ? String(val) : n.toFixed(2);
}; };
const formatUnits = (val: any) => { const formatPlainValue = (val: any) => {
if (val === undefined || val === null || val === '') return '—'; if (val === undefined || val === null || val === '') return '—';
return String(val); return String(val);
}; };
const getEditableCellValue = (val: any) => {
if (val === undefined || val === null) return '';
return String(val);
};
const unitBadge = (val: any, label: string) => { const unitBadge = (val: any, label: string) => {
const n = Number(val); const n = Number(val);
if (!val || n === 0) return ( if (!val || n === 0) return (
@@ -2188,31 +2193,239 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
{/* Units columns */} {/* Units columns */}
<td className={cn("px-3 py-2.5", isPinned('unitsOuter') && "sticky bg-slate-800")} style={isPinned('unitsOuter') ? { left: getStickyLeft('unitsOuter') ?? 0, zIndex: getStickyRank('unitsOuter') ?? 0 } : {}}> <td className={cn("px-3 py-2.5", isPinned('unitsOuter') && "sticky bg-slate-800")} style={isPinned('unitsOuter') ? { left: getStickyLeft('unitsOuter') ?? 0, zIndex: getStickyRank('unitsOuter') ?? 0 } : {}}>
{(() => {
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === unitsOuterIdx;
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === unitsOuterIdx;
const value = getEditableCellValue(row[unitsOuterIdx]);
return isEditing ? (
<input
ref={inputRef}
type="text"
value={editingCell.value}
onChange={e => setEditingCell(prev => prev ? { ...prev, value: e.target.value } : null)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
className="w-full bg-slate-900 border border-blue-500 rounded px-2 py-1 text-sm text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
) : (
<button
onClick={() => startEdit(dataIndex, unitsOuterIdx, value)}
className={cn(
'group flex items-center justify-between gap-1 px-2 py-1 rounded text-xs transition-colors hover:bg-slate-700/60 w-full overflow-hidden',
!value || Number(value) === 0
? 'text-amber-400 border border-amber-500/40 bg-amber-500/5'
: 'text-slate-200 border border-transparent hover:border-slate-600'
)}
title={isSaving ? 'Saving…' : 'Click to edit Units/Outer'}
>
{isSaving ? (
<span className="text-slate-500 italic truncate">saving</span>
) : (
<>
{unitOuterBadge(row[unitsOuterIdx])} {unitOuterBadge(row[unitsOuterIdx])}
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50 shrink-0" />
</>
)}
</button>
);
})()}
</td> </td>
<td className={cn("px-3 py-2.5 text-slate-400 font-mono text-xs truncate", isPinned('outerW') && "sticky bg-slate-800")} style={isPinned('outerW') ? { left: getStickyLeft('outerW') ?? 0, zIndex: getStickyRank('outerW') ?? 0 } : {}}> <td className={cn("px-3 py-2.5 text-slate-400 font-mono text-xs truncate", isPinned('outerW') && "sticky bg-slate-800")} style={isPinned('outerW') ? { left: getStickyLeft('outerW') ?? 0, zIndex: getStickyRank('outerW') ?? 0 } : {}}>
{row[COLUMNS.OUTER_W] ?? '-'} {(() => {
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === COLUMNS.OUTER_W;
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === COLUMNS.OUTER_W;
const value = getEditableCellValue(row[COLUMNS.OUTER_W]);
return isEditing ? (
<input
ref={inputRef}
type="text"
value={editingCell.value}
onChange={e => setEditingCell(prev => prev ? { ...prev, value: e.target.value } : null)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
className="w-full bg-slate-900 border border-blue-500 rounded px-2 py-1 text-sm text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
) : (
<button
onClick={() => startEdit(dataIndex, COLUMNS.OUTER_W, value)}
className={cn(
'group flex items-center justify-between gap-1 px-2 py-1 rounded text-xs transition-colors hover:bg-slate-700/60 w-full overflow-hidden',
!value || Number(value) === 0
? 'text-amber-400 border border-amber-500/40 bg-amber-500/5'
: 'text-slate-200 border border-transparent hover:border-slate-600'
)}
title={isSaving ? 'Saving…' : 'Click to edit Outer W'}
>
{isSaving ? (
<span className="text-slate-500 italic truncate">saving</span>
) : (
<>
<span>{formatPlainValue(row[COLUMNS.OUTER_W])}</span>
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50 shrink-0" />
</>
)}
</button>
);
})()}
</td> </td>
<td className={cn("px-3 py-2.5 text-slate-400 font-mono text-xs truncate", isPinned('outerL') && "sticky bg-slate-800")} style={isPinned('outerL') ? { left: getStickyLeft('outerL') ?? 0, zIndex: getStickyRank('outerL') ?? 0 } : {}}> <td className={cn("px-3 py-2.5 text-slate-400 font-mono text-xs truncate", isPinned('outerL') && "sticky bg-slate-800")} style={isPinned('outerL') ? { left: getStickyLeft('outerL') ?? 0, zIndex: getStickyRank('outerL') ?? 0 } : {}}>
{row[COLUMNS.OUTER_L] ?? '-'} {(() => {
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === COLUMNS.OUTER_L;
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === COLUMNS.OUTER_L;
const value = getEditableCellValue(row[COLUMNS.OUTER_L]);
return isEditing ? (
<input
ref={inputRef}
type="text"
value={editingCell.value}
onChange={e => setEditingCell(prev => prev ? { ...prev, value: e.target.value } : null)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
className="w-full bg-slate-900 border border-blue-500 rounded px-2 py-1 text-sm text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
) : (
<button
onClick={() => startEdit(dataIndex, COLUMNS.OUTER_L, value)}
className={cn(
'group flex items-center justify-between gap-1 px-2 py-1 rounded text-xs transition-colors hover:bg-slate-700/60 w-full overflow-hidden',
!value || Number(value) === 0
? 'text-amber-400 border border-amber-500/40 bg-amber-500/5'
: 'text-slate-200 border border-transparent hover:border-slate-600'
)}
title={isSaving ? 'Saving…' : 'Click to edit Outer L'}
>
{isSaving ? (
<span className="text-slate-500 italic truncate">saving</span>
) : (
<>
<span>{formatPlainValue(row[COLUMNS.OUTER_L])}</span>
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50 shrink-0" />
</>
)}
</button>
);
})()}
</td> </td>
<td className={cn("px-3 py-2.5 text-slate-400 font-mono text-xs truncate", isPinned('outerH') && "sticky bg-slate-800")} style={isPinned('outerH') ? { left: getStickyLeft('outerH') ?? 0, zIndex: getStickyRank('outerH') ?? 0 } : {}}> <td className={cn("px-3 py-2.5 text-slate-400 font-mono text-xs truncate", isPinned('outerH') && "sticky bg-slate-800")} style={isPinned('outerH') ? { left: getStickyLeft('outerH') ?? 0, zIndex: getStickyRank('outerH') ?? 0 } : {}}>
{row[COLUMNS.OUTER_H] ?? '-'} {(() => {
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === COLUMNS.OUTER_H;
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === COLUMNS.OUTER_H;
const value = getEditableCellValue(row[COLUMNS.OUTER_H]);
return isEditing ? (
<input
ref={inputRef}
type="text"
value={editingCell.value}
onChange={e => setEditingCell(prev => prev ? { ...prev, value: e.target.value } : null)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
className="w-full bg-slate-900 border border-blue-500 rounded px-2 py-1 text-sm text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
) : (
<button
onClick={() => startEdit(dataIndex, COLUMNS.OUTER_H, value)}
className={cn(
'group flex items-center justify-between gap-1 px-2 py-1 rounded text-xs transition-colors hover:bg-slate-700/60 w-full overflow-hidden',
!value || Number(value) === 0
? 'text-amber-400 border border-amber-500/40 bg-amber-500/5'
: 'text-slate-200 border border-transparent hover:border-slate-600'
)}
title={isSaving ? 'Saving…' : 'Click to edit Outer H'}
>
{isSaving ? (
<span className="text-slate-500 italic truncate">saving</span>
) : (
<>
<span>{formatPlainValue(row[COLUMNS.OUTER_H])}</span>
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50 shrink-0" />
</>
)}
</button>
);
})()}
</td> </td>
{/* Container units columns */} {/* Container units columns */}
{containerCols.map(col => { {containerCols.map(col => {
const colKey = `con_${col.index}`; const colKey = `con_${col.index}`;
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === col.index;
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === col.index;
const value = getEditableCellValue(row[col.index]);
return ( return (
<td key={col.index} className={cn("px-3 py-2.5 truncate", isPinned(colKey) && "sticky bg-slate-800")} style={isPinned(colKey) ? { left: getStickyLeft(colKey) ?? 0, zIndex: getStickyRank(colKey) ?? 0 } : {}}> <td key={col.index} className={cn("px-3 py-2.5 truncate", isPinned(colKey) && "sticky bg-slate-800")} style={isPinned(colKey) ? { left: getStickyLeft(colKey) ?? 0, zIndex: getStickyRank(colKey) ?? 0 } : {}}>
{isEditing ? (
<input
ref={inputRef}
type="text"
value={editingCell.value}
onChange={e => setEditingCell(prev => prev ? { ...prev, value: e.target.value } : null)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
className="w-full bg-slate-900 border border-blue-500 rounded px-2 py-1 text-sm text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
) : (
<button
onClick={() => startEdit(dataIndex, col.index, value)}
className={cn(
'group flex items-center justify-between gap-1 px-2 py-1 rounded text-xs transition-colors hover:bg-slate-700/60 w-full overflow-hidden',
!value || Number(value) === 0
? 'text-amber-400 border border-amber-500/40 bg-amber-500/5'
: 'text-slate-200 border border-transparent hover:border-slate-600'
)}
title={isSaving ? 'Saving…' : `Click to edit ${col.name}`}
>
{isSaving ? (
<span className="text-slate-500 italic truncate">saving</span>
) : (
<>
{unitBadge(row[col.index], col.name)} {unitBadge(row[col.index], col.name)}
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50 shrink-0" />
</>
)}
</button>
)}
</td> </td>
); );
})} })}
{/* MOQ cell */} {/* MOQ cell */}
<td className={cn("px-3 py-2.5 truncate", isPinned('moq') && "sticky bg-slate-800")} style={isPinned('moq') ? { left: getStickyLeft('moq') ?? 0, zIndex: getStickyRank('moq') ?? 0 } : {}}> <td className={cn("px-3 py-2.5 truncate", isPinned('moq') && "sticky bg-slate-800")} style={isPinned('moq') ? { left: getStickyLeft('moq') ?? 0, zIndex: getStickyRank('moq') ?? 0 } : {}}>
{row[COLUMNS.MOQ] !== undefined && row[COLUMNS.MOQ] !== null ? row[COLUMNS.MOQ] : '-'} {(() => {
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === COLUMNS.MOQ;
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === COLUMNS.MOQ;
const value = getEditableCellValue(row[COLUMNS.MOQ]);
return isEditing ? (
<input
ref={inputRef}
type="text"
value={editingCell.value}
onChange={e => setEditingCell(prev => prev ? { ...prev, value: e.target.value } : null)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
className="w-full bg-slate-900 border border-blue-500 rounded px-2 py-1 text-sm text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
) : (
<button
onClick={() => startEdit(dataIndex, COLUMNS.MOQ, value)}
className={cn(
'group flex items-center justify-between gap-1 px-2 py-1 rounded text-xs font-mono transition-colors hover:bg-slate-700/60 w-full overflow-hidden',
!value || Number(value) === 0
? 'text-amber-400 border border-amber-500/40 bg-amber-500/5'
: 'text-slate-200 border border-transparent hover:border-slate-600'
)}
title={isSaving ? 'Saving…' : 'Click to edit MOQ'}
>
{isSaving ? (
<span className="text-slate-500 italic truncate">saving</span>
) : (
<>
<span>{formatPlainValue(row[COLUMNS.MOQ])}</span>
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50 shrink-0" />
</>
)}
</button>
);
})()}
</td> </td>
{/* Issues column (weight only) */} {/* Issues column (weight only) */}
@@ -2334,7 +2547,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
<p className="text-xs text-slate-500 pb-1"> <p className="text-xs text-slate-500 pb-1">
Showing {paginatedRows.length} of {sortedRows.length} products {totalPages > 1 && `(${currentPage}/${totalPages})`} Showing {paginatedRows.length} of {sortedRows.length} products {totalPages > 1 && `(${currentPage}/${totalPages})`}
{pricingEditableCols.length > 0 && ( {pricingEditableCols.length > 0 && (
<> · Click any <span className="text-blue-400">price cell</span> to edit inline</> <> · Click editable <span className="text-blue-400">price / units / MOQ</span> cells to edit inline</>
)} )}
</p> </p>
{dragSelectedRows.size > 0 && ( {dragSelectedRows.size > 0 && (