mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 14:35:23 +02:00
feat: redesign PricingView with manual validation check and notes, and add Matrix columns
This commit is contained in:
+3
-1
@@ -122,7 +122,9 @@ export default function App() {
|
|||||||
COLUMNS.INNER_L, COLUMNS.INNER_W, COLUMNS.INNER_H,
|
COLUMNS.INNER_L, COLUMNS.INNER_W, COLUMNS.INNER_H,
|
||||||
COLUMNS.OUTER_L, COLUMNS.OUTER_W, COLUMNS.OUTER_H,
|
COLUMNS.OUTER_L, COLUMNS.OUTER_W, COLUMNS.OUTER_H,
|
||||||
COLUMNS.UNITS_OUTER, COLUMNS.MOQ,
|
COLUMNS.UNITS_OUTER, COLUMNS.MOQ,
|
||||||
COLUMNS.VERIFIED_DIMS
|
COLUMNS.VERIFIED_DIMS,
|
||||||
|
COLUMNS.VALIDATED_CHECK,
|
||||||
|
COLUMNS.VALIDATED_NOTE
|
||||||
]);
|
]);
|
||||||
|
|
||||||
headers.forEach((h: any, i: number) => {
|
headers.forEach((h: any, i: number) => {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function isEmptyOrEpoch(val: any): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TabType = 'missingLaunch';
|
type TabType = 'missingLaunch' | 'launchInconsistent' | 'upcomingLaunch';
|
||||||
|
|
||||||
interface EditingState {
|
interface EditingState {
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
|
|||||||
+135
-75
@@ -13,6 +13,7 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
Check,
|
Check,
|
||||||
Search,
|
Search,
|
||||||
|
MessageSquare,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||||
@@ -311,6 +312,52 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Matrix columns (the rest) ─────────────────────────────────────────────
|
||||||
|
const displayedIndices = useMemo(() => {
|
||||||
|
const set = new Set<number>([
|
||||||
|
COLUMNS.ARTICLE_NO,
|
||||||
|
COLUMNS.ARTICLE_NAME,
|
||||||
|
COLUMNS.LINE,
|
||||||
|
COLUMNS.CLASSIFICATION,
|
||||||
|
COLUMNS.UNITS_OUTER,
|
||||||
|
COLUMNS.OUTER_W,
|
||||||
|
COLUMNS.OUTER_L,
|
||||||
|
COLUMNS.OUTER_H,
|
||||||
|
]);
|
||||||
|
if (uvpIdx >= 0) set.add(uvpIdx);
|
||||||
|
srpCols.forEach(c => set.add(c.index));
|
||||||
|
containerCols.forEach(c => set.add(c.index));
|
||||||
|
return set;
|
||||||
|
}, [uvpIdx, srpCols, containerCols]);
|
||||||
|
|
||||||
|
const matrixCols = useMemo(() => {
|
||||||
|
return headers
|
||||||
|
.map((h, i) => ({ index: i, name: h || '' }))
|
||||||
|
.filter(({ index }) => !displayedIndices.has(index))
|
||||||
|
.filter(({ name }) => name.trim() !== ''); // Skip empty headers
|
||||||
|
}, [headers, displayedIndices]);
|
||||||
|
|
||||||
|
const [noteEditor, setNoteEditor] = useState<{ rowIndex: number; text: string } | null>(null);
|
||||||
|
|
||||||
|
const handleToggleCheck = async (rowIndex: number, checked: boolean) => {
|
||||||
|
const original = data[rowIndex];
|
||||||
|
const newRow = [...original];
|
||||||
|
newRow[COLUMNS.VALIDATED_CHECK] = checked;
|
||||||
|
onCaptureState(`${checked ? 'Checked' : 'Unchecked'} ${original[COLUMNS.ARTICLE_NO]}`);
|
||||||
|
await onSaveRow(rowIndex, newRow);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveNote = async () => {
|
||||||
|
if (!noteEditor) return;
|
||||||
|
const { rowIndex, text } = noteEditor;
|
||||||
|
const original = data[rowIndex];
|
||||||
|
const newRow = [...original];
|
||||||
|
newRow[COLUMNS.VALIDATED_NOTE] = text;
|
||||||
|
setNoteEditor(null);
|
||||||
|
onCaptureState(`Updated note for ${original[COLUMNS.ARTICLE_NO]}`);
|
||||||
|
await onSaveRow(rowIndex, newRow);
|
||||||
|
};
|
||||||
|
|
||||||
// ── Column detection notice ───────────────────────────────────────────────
|
// ── Column detection notice ───────────────────────────────────────────────
|
||||||
const missingCols: string[] = [];
|
const missingCols: string[] = [];
|
||||||
if (uvpIdx < 0) missingCols.push('UVP');
|
if (uvpIdx < 0) missingCols.push('UVP');
|
||||||
@@ -325,7 +372,40 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full gap-4">
|
<div className="flex flex-col h-full gap-4 relative">
|
||||||
|
{/* ── Note Editor Modal ── */}
|
||||||
|
{noteEditor && (
|
||||||
|
<div className="fixed inset-0 bg-slate-950/50 backdrop-blur-sm z-[100] flex items-center justify-center p-4">
|
||||||
|
<div className="bg-slate-800 border border-slate-700 rounded-xl shadow-2xl w-full max-w-md animate-in zoom-in-95 duration-200">
|
||||||
|
<div className="p-4 border-b border-slate-700 flex items-center justify-between">
|
||||||
|
<h3 className="font-semibold text-white flex items-center gap-2">
|
||||||
|
<MessageSquare className="w-4 h-4 text-blue-400" />
|
||||||
|
Validation Note
|
||||||
|
</h3>
|
||||||
|
<p className="text-[10px] text-slate-500 font-mono">{data[noteEditor.rowIndex][COLUMNS.ARTICLE_NO]}</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4">
|
||||||
|
<textarea
|
||||||
|
value={noteEditor.text}
|
||||||
|
onChange={e => setNoteEditor(prev => prev ? { ...prev, text: e.target.value } : null)}
|
||||||
|
placeholder="Write a note about this product..."
|
||||||
|
className="w-full h-32 bg-slate-900 border border-slate-700 rounded-lg p-3 text-sm text-white focus:outline-none focus:border-blue-500 resize-none"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-slate-900/50 rounded-b-xl flex justify-end gap-3">
|
||||||
|
<button onClick={() => setNoteEditor(null)} className="px-4 py-2 text-sm text-slate-400 hover:text-white transition-colors">Cancel</button>
|
||||||
|
<button
|
||||||
|
onClick={saveNote}
|
||||||
|
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-bold rounded-lg shadow-lg active:scale-95 transition-all"
|
||||||
|
>
|
||||||
|
Save Note
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
{/* ── Search bar ── */}
|
{/* ── Search bar ── */}
|
||||||
<div className="flex-1 max-w-md relative">
|
<div className="flex-1 max-w-md relative">
|
||||||
@@ -411,7 +491,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
|
||||||
Art. No.
|
Art. No.
|
||||||
</th>
|
</th>
|
||||||
{/* Article Name with text filter */}
|
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 group relative">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Article Name</span>
|
<span>Article Name</span>
|
||||||
@@ -433,7 +512,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
{/* Line with multi-select filter */}
|
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Line</span>
|
<span>Line</span>
|
||||||
@@ -458,7 +536,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
{/* Classification with multi-select filter */}
|
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Classification</span>
|
<span>Classification</span>
|
||||||
@@ -484,7 +561,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
{/* Editable pricing columns */}
|
|
||||||
{pricingEditableCols.map(col => (
|
{pricingEditableCols.map(col => (
|
||||||
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-blue-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-blue-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -520,7 +596,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Units/Outer */}
|
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Units/Outer</span>
|
<span>Units/Outer</span>
|
||||||
@@ -546,7 +621,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
{/* Outer W */}
|
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Outer W</span>
|
<span>Outer W</span>
|
||||||
@@ -572,7 +646,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
{/* Outer L */}
|
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Outer L</span>
|
<span>Outer L</span>
|
||||||
@@ -598,7 +671,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
{/* Outer H */}
|
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Outer H</span>
|
<span>Outer H</span>
|
||||||
@@ -624,7 +696,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
{/* Container columns */}
|
|
||||||
{containerCols.map(col => (
|
{containerCols.map(col => (
|
||||||
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-orange-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-orange-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -660,19 +731,28 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Status */}
|
{/* Validation Check Column */}
|
||||||
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700">
|
<th className="text-left px-3 py-3 text-xs font-semibold text-blue-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
|
||||||
Status
|
Check
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
|
{/* All remaining Matrix columns */}
|
||||||
|
{matrixCols.map(col => (
|
||||||
|
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap overflow-hidden max-w-[150px] truncate" title={col.name}>
|
||||||
|
{col.name}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<th className="px-3 py-3 border-b border-slate-700 w-10" />
|
<th className="px-3 py-3 border-b border-slate-700 w-10 sticky right-0 bg-slate-900 shadow-[-4px_0_8px_rgba(0,0,0,0.2)]" />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredRows.map(({ row, dataIndex, pricingErrors, unitErrors, isCritical }) => {
|
{filteredRows.map(({ row, dataIndex, pricingErrors, unitErrors, isCritical }) => {
|
||||||
const hasAnyError = pricingErrors.length > 0 || unitErrors.length > 0;
|
|
||||||
const saveStatus = rowStatuses[String(row[COLUMNS.ARTICLE_NO])];
|
const saveStatus = rowStatuses[String(row[COLUMNS.ARTICLE_NO])];
|
||||||
|
const isValidated = !!row[COLUMNS.VALIDATED_CHECK];
|
||||||
|
const note = String(row[COLUMNS.VALIDATED_NOTE] || '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={dataIndex}
|
key={dataIndex}
|
||||||
@@ -689,7 +769,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
: ''
|
: ''
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Article No */}
|
{/* Art. No. */}
|
||||||
<td className="px-3 py-2.5 font-mono text-xs text-slate-300 whitespace-nowrap">
|
<td className="px-3 py-2.5 font-mono text-xs text-slate-300 whitespace-nowrap">
|
||||||
{row[COLUMNS.ARTICLE_NO]}
|
{row[COLUMNS.ARTICLE_NO]}
|
||||||
</td>
|
</td>
|
||||||
@@ -720,7 +800,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Editable pricing cells */}
|
{/* Pricing editable cells */}
|
||||||
{pricingEditableCols.map(col => {
|
{pricingEditableCols.map(col => {
|
||||||
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === col.index;
|
const isEditing = editingCell?.rowIndex === dataIndex && editingCell?.colIndex === col.index;
|
||||||
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === col.index;
|
const isSaving = savingCell?.rowIndex === dataIndex && savingCell?.colIndex === col.index;
|
||||||
@@ -743,9 +823,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<button onClick={commitEdit} className="text-emerald-400 hover:text-emerald-300 p-0.5">
|
<button onClick={commitEdit} className="text-emerald-400 hover:text-emerald-300 p-0.5">
|
||||||
<Save className="w-3.5 h-3.5" />
|
<Save className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={cancelEdit} className="text-slate-500 hover:text-slate-300 p-0.5">
|
|
||||||
<X className="w-3.5 h-3.5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
@@ -761,15 +838,9 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
{isSaving ? (
|
{isSaving ? (
|
||||||
<span className="text-slate-500 italic">saving…</span>
|
<span className="text-slate-500 italic">saving…</span>
|
||||||
) : isEmpty ? (
|
) : isEmpty ? (
|
||||||
<>
|
<><AlertTriangle className="w-3 h-3" /><span>Missing</span></>
|
||||||
<AlertTriangle className="w-3 h-3" />
|
|
||||||
<span>Missing</span>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>{val}<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50" /></>
|
||||||
<span>{val}</span>
|
|
||||||
<Edit2 className="w-2.5 h-2.5 opacity-0 group-hover:opacity-50" />
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -777,66 +848,55 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Units/Outer */}
|
{/* Units columns */}
|
||||||
<td className="px-3 py-2.5">
|
<td className="px-3 py-2.5">{unitOuterBadge(row[COLUMNS.UNITS_OUTER])}</td>
|
||||||
{unitOuterBadge(row[COLUMNS.UNITS_OUTER])}
|
<td className="px-3 py-2.5 text-slate-400 font-mono text-xs">{row[COLUMNS.OUTER_W] ?? '-'}</td>
|
||||||
</td>
|
<td className="px-3 py-2.5 text-slate-400 font-mono text-xs">{row[COLUMNS.OUTER_L] ?? '-'}</td>
|
||||||
|
<td className="px-3 py-2.5 text-slate-400 font-mono text-xs">{row[COLUMNS.OUTER_H] ?? '-'}</td>
|
||||||
|
|
||||||
{/* Outer W */}
|
{/* Container units columns */}
|
||||||
<td className="px-3 py-2.5 text-slate-400 font-mono text-xs">
|
|
||||||
{row[COLUMNS.OUTER_W] ?? '-'}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{/* Outer L */}
|
|
||||||
<td className="px-3 py-2.5 text-slate-400 font-mono text-xs">
|
|
||||||
{row[COLUMNS.OUTER_L] ?? '-'}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{/* Outer H */}
|
|
||||||
<td className="px-3 py-2.5 text-slate-400 font-mono text-xs">
|
|
||||||
{row[COLUMNS.OUTER_H] ?? '-'}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{/* Container unit columns */}
|
|
||||||
{containerCols.map(col => (
|
{containerCols.map(col => (
|
||||||
<td key={col.index} className="px-3 py-2.5">
|
<td key={col.index} className="px-3 py-2.5">
|
||||||
{unitBadge(row[col.index], col.name)}
|
{unitBadge(row[col.index], col.name)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Status badge */}
|
{/* Check Column (Validated + Note) */}
|
||||||
<td className="px-3 py-2.5">
|
<td className="px-3 py-2.5">
|
||||||
{!hasAnyError ? (
|
<div className="flex items-center gap-2">
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
|
<input
|
||||||
<CheckCircle2 className="w-3 h-3" /> OK
|
type="checkbox"
|
||||||
</span>
|
checked={isValidated}
|
||||||
) : isCritical ? (
|
onChange={(e) => handleToggleCheck(dataIndex, e.target.checked)}
|
||||||
<div className="flex flex-col gap-1">
|
className="w-4 h-4 rounded border-slate-600 bg-slate-900 text-blue-600 focus:ring-blue-500 focus:ring-offset-slate-800 cursor-pointer"
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-bold bg-red-500/15 text-red-400 border border-red-500/30">
|
/>
|
||||||
<AlertCircle className="w-3 h-3" /> Critical
|
<button
|
||||||
</span>
|
onClick={() => setNoteEditor({ rowIndex: dataIndex, text: note })}
|
||||||
{unitErrors.map((e, i) => (
|
className={cn(
|
||||||
<span key={i} className="text-[10px] text-red-400/70 pl-1">{e}</span>
|
"p-1.5 rounded-md transition-all shrink-0",
|
||||||
))}
|
note
|
||||||
</div>
|
? "bg-amber-500/10 text-amber-400 border border-amber-500/20 hover:bg-amber-500/20"
|
||||||
) : (
|
: "text-slate-500 hover:text-slate-300 hover:bg-slate-700/50"
|
||||||
<div className="flex flex-col gap-1">
|
)}
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-amber-500/10 text-amber-400 border border-amber-500/20">
|
title={note || "Add note"}
|
||||||
<AlertTriangle className="w-3 h-3" /> Incomplete
|
>
|
||||||
</span>
|
<MessageSquare className={cn("w-3.5 h-3.5", note && "fill-amber-400/20")} />
|
||||||
{pricingErrors.map((e, i) => (
|
</button>
|
||||||
<span key={i} className="text-[10px] text-amber-400/70 pl-1">{e}</span>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Edit button */}
|
{/* Matrix columns cells */}
|
||||||
<td className="px-2 py-2.5">
|
{matrixCols.map(col => (
|
||||||
|
<td key={col.index} className="px-3 py-2.5 text-slate-500 text-xs max-w-[150px] truncate" title={String(row[col.index] || '')}>
|
||||||
|
{row[col.index] ?? '—'}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Actions sticky */}
|
||||||
|
<td className="px-2 py-2.5 sticky right-0 bg-slate-800 shadow-[-4px_0_8px_rgba(0,0,0,0.1)] group-hover:bg-slate-700/40">
|
||||||
<button
|
<button
|
||||||
onClick={() => onEdit(dataIndex)}
|
onClick={() => onEdit(dataIndex)}
|
||||||
className="p-1.5 text-slate-500 hover:text-slate-200 hover:bg-slate-700 rounded transition-colors"
|
className="p-1.5 text-slate-500 hover:text-slate-200 hover:bg-slate-700 rounded transition-colors"
|
||||||
title="Open full editor"
|
|
||||||
>
|
>
|
||||||
<Edit2 className="w-3.5 h-3.5" />
|
<Edit2 className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+3
-1
@@ -36,5 +36,7 @@ export const COLUMNS = {
|
|||||||
OUTER_W: 47,
|
OUTER_W: 47,
|
||||||
OUTER_L: 48,
|
OUTER_L: 48,
|
||||||
OUTER_H: 49,
|
OUTER_H: 49,
|
||||||
VERIFIED_DIMS: 100
|
VERIFIED_DIMS: 100,
|
||||||
|
VALIDATED_CHECK: 101,
|
||||||
|
VALIDATED_NOTE: 102
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user