mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 17:25:24 +02:00
UI: Add 'Check Anna' column to Pricing Units view
Mirrors Check Ying with its own checkbox and note editor (pink theme). Stored in virtual columns ANNA_CHECK (105) and ANNA_NOTE (106). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
aca37c4a99
commit
f545776e15
@@ -592,6 +592,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
}, [headers, displayedIndices]);
|
}, [headers, displayedIndices]);
|
||||||
|
|
||||||
const [noteEditor, setNoteEditor] = useState<{ rowIndex: number; text: string } | null>(null);
|
const [noteEditor, setNoteEditor] = useState<{ rowIndex: number; text: string } | null>(null);
|
||||||
|
const [annaEditor, setAnnaEditor] = useState<{ rowIndex: number; text: string } | null>(null);
|
||||||
|
|
||||||
const handleToggleCheck = async (rowIndex: number, checked: boolean) => {
|
const handleToggleCheck = async (rowIndex: number, checked: boolean) => {
|
||||||
const original = data[rowIndex];
|
const original = data[rowIndex];
|
||||||
@@ -612,6 +613,25 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
await onSaveRow(rowIndex, newRow);
|
await onSaveRow(rowIndex, newRow);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleToggleAnnaCheck = async (rowIndex: number, checked: boolean) => {
|
||||||
|
const original = data[rowIndex];
|
||||||
|
const newRow = [...original];
|
||||||
|
newRow[COLUMNS.ANNA_CHECK] = checked;
|
||||||
|
onCaptureState(`${checked ? 'Anna checked' : 'Anna unchecked'} ${original[COLUMNS.ARTICLE_NO]}`);
|
||||||
|
await onSaveRow(rowIndex, newRow);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveAnnaNote = async () => {
|
||||||
|
if (!annaEditor) return;
|
||||||
|
const { rowIndex, text } = annaEditor;
|
||||||
|
const original = data[rowIndex];
|
||||||
|
const newRow = [...original];
|
||||||
|
newRow[COLUMNS.ANNA_NOTE] = text;
|
||||||
|
setAnnaEditor(null);
|
||||||
|
onCaptureState(`Updated Anna 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');
|
||||||
@@ -627,6 +647,39 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full gap-4 relative">
|
<div className="flex flex-col h-full gap-4 relative">
|
||||||
|
{/* ── Anna Note Editor Modal ── */}
|
||||||
|
{annaEditor && (
|
||||||
|
<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-pink-400" />
|
||||||
|
Anna Note
|
||||||
|
</h3>
|
||||||
|
<p className="text-[10px] text-slate-500 font-mono">{data[annaEditor.rowIndex][COLUMNS.ARTICLE_NO]}</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4">
|
||||||
|
<textarea
|
||||||
|
value={annaEditor.text}
|
||||||
|
onChange={e => setAnnaEditor(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-pink-500 resize-none"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-slate-900/50 rounded-b-xl flex justify-end gap-3">
|
||||||
|
<button onClick={() => setAnnaEditor(null)} className="px-4 py-2 text-sm text-slate-400 hover:text-white transition-colors">Cancel</button>
|
||||||
|
<button
|
||||||
|
onClick={saveAnnaNote}
|
||||||
|
className="px-4 py-2 bg-pink-600 hover:bg-pink-700 text-white text-sm font-bold rounded-lg shadow-lg active:scale-95 transition-all"
|
||||||
|
>
|
||||||
|
Save Note
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ── Note Editor Modal ── */}
|
{/* ── Note Editor Modal ── */}
|
||||||
{noteEditor && (
|
{noteEditor && (
|
||||||
<div className="fixed inset-0 bg-slate-950/50 backdrop-blur-sm z-[100] flex items-center justify-center p-4">
|
<div className="fixed inset-0 bg-slate-950/50 backdrop-blur-sm z-[100] flex items-center justify-center p-4">
|
||||||
@@ -1127,6 +1180,11 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'check', columnWidths.check); }} />
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'check', columnWidths.check); }} />
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
|
<th style={{ width: columnWidths.checkAnna ?? 100 }} className="text-left px-3 py-3 text-xs font-semibold text-pink-400 uppercase tracking-wider group cursor-pointer hover:bg-slate-800/50 transition-colors relative" onClick={() => handleSort(COLUMNS.ANNA_CHECK)}>
|
||||||
|
<span className="flex items-center gap-1">Check Anna <SortIcon current={sortConfig.key === COLUMNS.ANNA_CHECK ? sortConfig.direction : null} /></span>
|
||||||
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'checkAnna', columnWidths.checkAnna ?? 100); }} />
|
||||||
|
</th>
|
||||||
|
|
||||||
{matrixCols.map(col => {
|
{matrixCols.map(col => {
|
||||||
const colKey = `mat_${col.index}`;
|
const colKey = `mat_${col.index}`;
|
||||||
const width = columnWidths[colKey] ?? 120;
|
const width = columnWidths[colKey] ?? 120;
|
||||||
@@ -1350,7 +1408,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
})()}
|
})()}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Check Column (Validated + Note) */}
|
{/* Check Ying Column (Validated + Note) */}
|
||||||
<td className="px-3 py-2.5">
|
<td className="px-3 py-2.5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
@@ -1380,6 +1438,42 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{/* Check Anna Column */}
|
||||||
|
{(() => {
|
||||||
|
const annaChecked = !!row[COLUMNS.ANNA_CHECK];
|
||||||
|
const annaNote = String(row[COLUMNS.ANNA_NOTE] || '');
|
||||||
|
return (
|
||||||
|
<td className="px-3 py-2.5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={annaChecked}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleToggleAnnaCheck(dataIndex, e.target.checked)}
|
||||||
|
className="w-4 h-4 rounded border-slate-600 bg-slate-900 text-pink-600 focus:ring-pink-500 focus:ring-offset-slate-800 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<div className="relative group">
|
||||||
|
<button
|
||||||
|
onClick={() => setAnnaEditor({ rowIndex: dataIndex, text: annaNote })}
|
||||||
|
className={cn(
|
||||||
|
"p-1.5 rounded-md transition-all shrink-0",
|
||||||
|
annaNote
|
||||||
|
? "bg-pink-500/10 text-pink-400 border border-pink-500/20 hover:bg-pink-500/20"
|
||||||
|
: "text-slate-500 hover:text-slate-300 hover:bg-slate-700/50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<MessageSquare className={cn("w-3.5 h-3.5", annaNote && "fill-pink-400/20")} />
|
||||||
|
</button>
|
||||||
|
{annaNote && (
|
||||||
|
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-48 p-2 bg-pink-900/90 border border-pink-500/50 rounded text-xs text-white shadow-lg z-50 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
|
||||||
|
{annaNote}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* Matrix columns cells */}
|
{/* Matrix columns cells */}
|
||||||
{matrixCols.map(col => (
|
{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] || '')}>
|
<td key={col.index} className="px-3 py-2.5 text-slate-500 text-xs max-w-[150px] truncate" title={String(row[col.index] || '')}>
|
||||||
|
|||||||
+6
-2
@@ -41,7 +41,9 @@ export const COLUMNS = {
|
|||||||
VALIDATED_CHECK: 101,
|
VALIDATED_CHECK: 101,
|
||||||
VALIDATED_NOTE: 102,
|
VALIDATED_NOTE: 102,
|
||||||
PRODUCT_TYPE: 103,
|
PRODUCT_TYPE: 103,
|
||||||
ITEM_TO_LOGISTIC: 104
|
ITEM_TO_LOGISTIC: 104,
|
||||||
|
ANNA_CHECK: 105,
|
||||||
|
ANNA_NOTE: 106
|
||||||
};
|
};
|
||||||
|
|
||||||
// Search patterns for dynamic detection
|
// Search patterns for dynamic detection
|
||||||
@@ -77,7 +79,9 @@ export const COLUMN_PATTERNS: Record<keyof typeof COLUMNS, string[]> = {
|
|||||||
VALIDATED_CHECK: ['validated'],
|
VALIDATED_CHECK: ['validated'],
|
||||||
VALIDATED_NOTE: ['note'],
|
VALIDATED_NOTE: ['note'],
|
||||||
PRODUCT_TYPE: ['product', 'type'],
|
PRODUCT_TYPE: ['product', 'type'],
|
||||||
ITEM_TO_LOGISTIC: ['item', 'to', 'logistic']
|
ITEM_TO_LOGISTIC: ['item', 'to', 'logistic'],
|
||||||
|
ANNA_CHECK: ['anna', 'check'],
|
||||||
|
ANNA_NOTE: ['anna', 'note']
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resolveColumnIndices(headers: string[]): typeof COLUMNS {
|
export function resolveColumnIndices(headers: string[]): typeof COLUMNS {
|
||||||
|
|||||||
Reference in New Issue
Block a user