import { useState, useRef } from 'react'; import { Button } from '../../buttons/Button'; import { Select } from '../../reusable/Select'; import { Input } from '../../reusable/Input'; import { Trash2, Pencil } from 'lucide-react'; import type { FormScreenField } from '../../../api/types'; export function SmartGridField({ label, columns, value = [], onChange, }: { label: string; columns: FormScreenField[]; value: Record[]; onChange: (val: Record[]) => void; }) { const isPotentialMining = label.toLowerCase().includes('potential'); const formRef = useRef(null); const [newRow, setNewRow] = useState>({}); const [editingIdx, setEditingIdx] = useState(null); const [errors, setErrors] = useState>({}); const visibleColumns = columns.filter(c => { const isOrderDetails = label.toLowerCase().includes('order'); if (isOrderDetails) { const n = (c.name || '').toLowerCase(); const id = (c.id || '').toLowerCase(); // Show only Category, Name, and Bags return n.includes('category') || n.includes('name') || n.includes('bags') || id.includes('category') || id.includes('name') || id.includes('bags'); } return true; }); const removeRow = (idx: number) => { const next = [...value]; next.splice(idx, 1); onChange(next); }; const startEdit = (idx: number) => { setEditingIdx(idx); setNewRow({ ...value[idx] }); }; const updateNewRowField = (fieldId: string, val: unknown) => { let row = { ...newRow, [fieldId]: val }; setErrors(prev => ({ ...prev, [fieldId]: '' })); const colDef = columns.find(c => c.id === fieldId); // If product category changes, clear out the rest of the row's data if (colDef && (colDef.id === 'product_category' || colDef.name === 'Product Category' || colDef.mapped_workflow_field === 'product_category')) { Object.keys(row).forEach(k => { if (k !== fieldId) { row[k] = ''; } }); } // Auto-fill dataset keys from _raw if (colDef && (colDef.data_type === 'select' || colDef.data_type === 'multiselect')) { const option = colDef.properties?.options?.find(o => String(o.value) === String(val)); if (option && option._raw) { const getBaseId = (id: string) => id.replace(/_\d+$/, ''); for (const key of Object.keys(option._raw)) { const targetCol = columns.find(c => c.id === key || c.mapped_workflow_field === key || getBaseId(c.id) === key || c.name.toLowerCase().replace(/\s+/g, '') === key.toLowerCase().replace(/_/g, '')); if (targetCol && targetCol.id !== fieldId) { row[targetCol.id] = option._raw[key]; } else if (!targetCol && key !== fieldId) { row[key] = option._raw[key]; } } } } // Auto-calculate row_kgs if sku and bags are present const getBaseId = (id: string) => id.replace(/_\d+$/, ''); const getVal = (key: string) => { const c = columns.find(c => c.id === key || c.mapped_workflow_field === key || getBaseId(c.id) === key || c.name.toLowerCase().replace(/\s+/g, '') === key.toLowerCase().replace(/_/g, '')); return c ? row[c.id] : row[key]; }; const skuVal = Number(getVal('sku')) || 0; const bagsVal = Number(getVal('bags')) || 0; const rowKgsCol = columns.find(c => c.id === 'row_kgs' || c.mapped_workflow_field === 'row_kgs' || getBaseId(c.id) === 'row_kgs' || getBaseId(c.id) === 'rowkgs'); if (rowKgsCol) { row[rowKgsCol.id] = skuVal * bagsVal; } else { row['row_kgs'] = skuVal * bagsVal; } setNewRow(row); }; const submitNewRow = () => { const newErrors: Record = {}; const getBaseId = (id: string) => id.replace(/_\d+$/, ''); const catColId = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category' || getBaseId(c.id) === 'product_category')?.id; const isCatSelected = catColId ? !!newRow[catColId] : false; const isOrderDetails = label.toLowerCase().includes('order'); const hasAnyValue = visibleColumns.some(col => newRow[col.id] !== undefined && newRow[col.id] !== null && String(newRow[col.id]).trim() !== ''); for (const col of visibleColumns) { if (isPotentialMining) { const isReasonOrRemark = col.id.toLowerCase().includes('reason') || col.name.toLowerCase().includes('reason') || col.id.toLowerCase().includes('remark') || col.name.toLowerCase().includes('remark'); if (!isReasonOrRemark) continue; } const isCategory = col.id === 'product_category' || col.name === 'Product Category' || col.mapped_workflow_field === 'product_category' || getBaseId(col.id) === 'product_category'; const isProductName = col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name' || getBaseId(col.id) === 'product_name'; const isBags = col.id === 'bags' || col.name === 'Bags' || col.mapped_workflow_field === 'bags' || getBaseId(col.id) === 'bags' || col.id.toLowerCase().includes('quantity') || col.name.toLowerCase().includes('quantity') || col.mapped_workflow_field?.toLowerCase().includes('quantity'); let isRequired = col.mandatory || (isCatSelected && (isProductName || isBags)); if (isOrderDetails && (isCategory || isProductName || isBags)) { isRequired = true; } if (!hasAnyValue) { isRequired = true; // if completely empty, require fields to prevent empty row addition } if (isRequired && (!newRow[col.id] || String(newRow[col.id]).trim() === '')) { newErrors[col.id] = 'This field is required'; } } if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } if (editingIdx !== null) { const next = [...value]; next[editingIdx] = newRow; onChange(next); } else { const getBaseId = (id: string) => id.replace(/_\d+$/, ''); const prodNameCol = columns.find(c => c.id === 'product_name' || getBaseId(c.id) === 'product_name' || c.mapped_workflow_field === 'product_name'); let foundIdx = -1; if (prodNameCol && newRow[prodNameCol.id]) { foundIdx = value.findIndex(r => r[prodNameCol.id] === newRow[prodNameCol.id]); } if (foundIdx !== -1) { const next = [...value]; const existingRow = { ...next[foundIdx] }; const bagsCol = columns.find(c => c.id === 'bags' || getBaseId(c.id) === 'bags' || c.mapped_workflow_field === 'bags' || c.name.toLowerCase() === 'bags'); const rowKgsCol = columns.find(c => c.id === 'row_kgs' || getBaseId(c.id) === 'row_kgs' || c.mapped_workflow_field === 'row_kgs' || getBaseId(c.id) === 'rowkgs'); const skuCol = columns.find(c => c.id === 'sku' || getBaseId(c.id) === 'sku' || c.mapped_workflow_field === 'sku'); const bagsId = bagsCol ? bagsCol.id : 'bags'; const rowKgsId = rowKgsCol ? rowKgsCol.id : 'row_kgs'; const skuId = skuCol ? skuCol.id : 'sku'; const existingBags = Number(existingRow[bagsId]) || 0; const addedBags = Number(newRow[bagsId]) || 0; existingRow[bagsId] = existingBags + addedBags; const skuVal = Number(existingRow[skuId]) || 0; existingRow[rowKgsId] = (existingBags + addedBags) * skuVal; next[foundIdx] = existingRow; onChange(next); } else { onChange([...value, newRow]); } } // Only close form if we were editing an existing row. For new rows, stay open. if (editingIdx !== null) { // Nothing needed here anymore since we removed isModalOpen } setNewRow({}); setEditingIdx(null); }; const renderFormFields = () => { return visibleColumns.map(col => { const val = newRow[col.id]; const getBaseId = (id: string) => id.replace(/_\d+$/, ''); if (isPotentialMining) { const isReasonOrRemark = col.id.toLowerCase().includes('reason') || col.name.toLowerCase().includes('reason') || col.id.toLowerCase().includes('remark') || col.name.toLowerCase().includes('remark'); if (!isReasonOrRemark) return null; } const catColId = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category' || getBaseId(c.id) === 'product_category')?.id; const isCatSelected = catColId ? !!newRow[catColId] : false; const isProductName = col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name' || getBaseId(col.id) === 'product_name'; const isBags = col.id === 'bags' || col.name === 'Bags' || col.mapped_workflow_field === 'bags' || getBaseId(col.id) === 'bags' || col.id.toLowerCase().includes('quantity') || col.name.toLowerCase().includes('quantity') || col.mapped_workflow_field?.toLowerCase().includes('quantity'); const isRequired = col.mandatory || (isCatSelected && (isProductName || isBags)); if (col.data_type === 'select' || col.data_type === 'multiselect') { const allOpts = col.properties?.options || []; let filteredOpts = allOpts; if (col.id === 'product_name' || col.name === 'Product Name' || col.mapped_workflow_field === 'product_name') { const catCol = columns.find(c => c.id === 'product_category' || c.name === 'Product Category' || c.mapped_workflow_field === 'product_category'); if (catCol) { const selectedCategory = newRow[catCol.id] as string; if (selectedCategory) { filteredOpts = allOpts.filter(opt => { const labelStr = String(opt.label || opt.value || ''); return labelStr.startsWith(selectedCategory); }); } } } else { filteredOpts = allOpts.filter(opt => { if (!opt._raw) return true; for (const [rowKey, rowVal] of Object.entries(newRow)) { if (rowKey === col.id || rowVal == null || rowVal === '') continue; const rawKey = Object.keys(opt._raw).find(rk => rk === rowKey || rk.replace(/_/g, '') === rowKey.replace(/_/g, '')); if (rawKey && String(opt._raw[rawKey]) !== String(rowVal)) { return false; } } return true; }); } return ( updateNewRowField(col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)} /> ); }); }; return (
{label} {value.length === 0 ? ( No rows added. ) : (
{value.map((row, i) => { let productName = 'Unknown Product'; let bags = '0'; let hasBagsCol = false; let diffVal: number | null = null; let reasonStr = ''; visibleColumns.forEach(col => { const isName = col.id.toLowerCase().includes('name') || col.name.toLowerCase().includes('name') || (col.id.toLowerCase().includes('category') && productName === 'Unknown Product'); const isBags = col.id.toLowerCase().includes('bags') || col.name.toLowerCase().includes('bags') || col.id.toLowerCase().includes('quantity'); const isDiff = col.id.toLowerCase().includes('diff') || col.name.toLowerCase().includes('diff'); const isReason = col.id.toLowerCase().includes('reason') || col.name.toLowerCase().includes('reason'); const val = row[col.id]; let displayVal = String(val ?? '-'); if (col.data_type === 'select' || col.data_type === 'multiselect') { const opt = col.properties?.options?.find(o => String(o.value) === String(val)); if (opt) displayVal = opt.label; } if (isName && (productName === 'Unknown Product' || col.id.toLowerCase().includes('name'))) { productName = displayVal; } if (isBags) { bags = displayVal; hasBagsCol = true; } if (isPotentialMining && isDiff && val != null && val !== '') { diffVal = Number(val); } if (isPotentialMining && isReason && val && displayVal !== 'Select...' && displayVal !== '-') { reasonStr = displayVal; } }); const bagsNum = Number(bags) || 0; const getBaseId = (id: string) => id.replace(/_\d+$/, ''); const skuCol = columns.find(c => c.id === 'sku' || c.mapped_workflow_field === 'sku' || getBaseId(c.id) === 'sku'); const rowKgsCol = columns.find(c => c.id === 'row_kgs' || c.mapped_workflow_field === 'row_kgs' || getBaseId(c.id) === 'row_kgs' || getBaseId(c.id) === 'rowkgs'); const skuVal = skuCol ? Number(row[skuCol.id]) : Number(row['sku'] || 0); const rowKgsVal = rowKgsCol ? Number(row[rowKgsCol.id]) : Number(row['row_kgs'] || 0); let kgsNum = 0; if (skuVal > 0 && bagsNum > 0) { kgsNum = skuVal * bagsNum; } else if (rowKgsVal > 0) { kgsNum = rowKgsVal; } const kgsStr = kgsNum > 0 ? ` (${kgsNum} kg)` : ''; if (editingIdx === i) { return (

Edit Row

{productName}
{(!isPotentialMining || hasBagsCol) && ( {bags} {isPotentialMining ? 'Kgs' : 'Bags'}{!isPotentialMining ? kgsStr : ''} )} {isPotentialMining && diffVal !== null && !isNaN(diffVal) && diffVal !== 0 && ( 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`}> {Math.abs(diffVal)} {diffVal > 0 ? 'Surplus' : 'Less'} )}
{renderFormFields()}
); } return (
{productName}
{(!isPotentialMining || hasBagsCol) && ( {bags} {isPotentialMining ? 'Kgs' : 'Bags'}{!isPotentialMining ? kgsStr : ''} )} {isPotentialMining && diffVal !== null && !isNaN(diffVal) && diffVal !== 0 && ( 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`}> {Math.abs(diffVal)} {diffVal > 0 ? 'Surplus' : 'Less'} )} {isPotentialMining && reasonStr && ( {reasonStr} )}
{!isPotentialMining && ( )}
); })}
)} {!isPotentialMining && (

Add New Item

{editingIdx === null && renderFormFields()}
)}
); }