import { useMemo, useCallback } from 'react'; import type { FormScreenField } from '../../../api/types'; import { Select } from '../../reusable/Select'; import { Input } from '../../reusable/Input'; import { isCategoryColumn, isProductColumn, findColumn } from './gridUtils'; export interface OrderGridProps { label: string; columns: FormScreenField[]; value: Record[]; onChange: (val: Record[]) => void; totalBagsValue?: number; totalKgsValue?: number; showErrors?: boolean; } export function OrderGrid({ label, columns, value = [], onChange, totalBagsValue, totalKgsValue, showErrors = false, }: OrderGridProps) { const rows = value.length > 0 ? value : [{}]; const catCol = useMemo(() => columns.find(c => isCategoryColumn(c.id, c.name)) || findColumn(columns, 'product_category', 'category'), [columns]); const prodNameCol = useMemo(() => columns.find(c => isProductColumn(c.id, c.name)), [columns]); const bagsCol = useMemo(() => findColumn(columns, 'bags', 'quantity', 'actual_potential', 'store_potential'), [columns]); const visibleColumns = useMemo(() => { const baseCols = (() => { if (columns.length === 0) { return [ { id: 'product_category', name: 'Category', data_type: 'select' } as FormScreenField, { id: 'product_name', name: 'Product name', data_type: 'select' } as FormScreenField, { id: 'bags', name: 'Bags', data_type: 'number' } as FormScreenField, ]; } const filtered = columns.filter((c) => { return isCategoryColumn(c.id, c.name) || isProductColumn(c.id, c.name) || c.id.includes('bags') || (c.name || '').toLowerCase().includes('bags'); }); if (filtered.length > 0) return filtered; return columns.filter((c) => { const idL = c.id.toLowerCase(); const nL = (c.name || '').toLowerCase(); return !idL.includes('sku') && !nL.includes('sku') && !idL.includes('br_code') && !nL.includes('br code') && !idL.includes('row_kgs'); }); })(); return [ ...baseCols, { id: '_row_total_kgs', name: 'Total (kgs)', data_type: 'readonly' } as FormScreenField, ]; }, [columns]); const gridTemplateColumns = useMemo(() => { const numCols = visibleColumns.length; const actionCol = ' auto'; if (numCols === 4) return `1.3fr 1.5fr 0.7fr 0.8fr${actionCol}`; if (numCols === 3) return `1.4fr 1.6fr 0.7fr${actionCol}`; if (numCols === 2) return `1.5fr 1.5fr${actionCol}`; return `repeat(${numCols}, minmax(0, 1fr))${actionCol}`; }, [visibleColumns]); const consolidateRows = useCallback((rowsList: Record[]) => { const merged: Record[] = []; const indexMap = new Map(); for (const r of rowsList) { const cat = String(r.category || r.product_category || (catCol ? r[catCol.id] : '') || '').trim().toLowerCase(); const prod = String(r.productName || r.product_name || (prodNameCol ? r[prodNameCol.id] : '') || '').trim().toLowerCase(); const bagsColId = bagsCol ? bagsCol.id : 'bags'; const currentBags = Number(r[bagsColId] ?? r.bags ?? r.quantity ?? 0); if (cat && prod) { const key = `${cat}::${prod}`; if (indexMap.has(key) && currentBags > 0) { const targetIdx = indexMap.get(key)!; const existingRow = merged[targetIdx]; const existingBags = Number(existingRow[bagsColId] ?? existingRow.bags ?? existingRow.quantity ?? 0); const addedBags = currentBags; const newBags = existingBags + addedBags; const skuVal = Number(existingRow.sku || r.sku || 0); const updatedRow: Record = { ...existingRow, bags: newBags, quantity: newBags, row_kgs: skuVal * newBags, }; if (bagsCol) updatedRow[bagsCol.id] = newBags; merged[targetIdx] = updatedRow; continue; } else if (!indexMap.has(key)) { indexMap.set(key, merged.length); } } merged.push(r); } return merged.length > 0 ? merged : [{}]; }, [catCol, prodNameCol, bagsCol]); const isAnyRowIncomplete = useMemo(() => { return rows.some((r) => { const cat = String(r.category || r.product_category || (catCol ? r[catCol.id] : '') || '').trim(); if (!cat) return true; if (prodNameCol) { const prod = String(r.productName || r.product_name || r[prodNameCol.id] || '').trim(); if (!prod) return true; } if (bagsCol) { const bags = Number(r.bags || r.quantity || r[bagsCol.id] || 0); if (bags <= 0) return true; } return false; }); }, [rows, catCol, prodNameCol, bagsCol]); const addRow = useCallback(() => { if (isAnyRowIncomplete) return; const consolidated = consolidateRows(rows); const cleanRows = consolidated.map((r) => { const clean: Record = {}; Object.keys(r).forEach((k) => { if (k !== '_row_total_kgs' && k !== 'row_kgs' && k !== 'row_total') { clean[k] = r[k]; } }); return clean; }); onChange([...cleanRows, {}]); }, [rows, isAnyRowIncomplete, consolidateRows, onChange]); const removeRow = useCallback((idx: number) => { if (rows.length > 1) { const next = [...rows]; next.splice(idx, 1); const cleanRows = next.map(r => { const clean: Record = {}; Object.keys(r).forEach(k => { if (k !== '_row_total_kgs' && k !== 'row_kgs' && k !== 'row_total') { clean[k] = r[k]; } }); return clean; }); onChange(cleanRows); } }, [rows, onChange]); const categoriesList = useMemo(() => { if (catCol?.properties?.options && catCol.properties.options.length > 0) { return catCol.properties.options.map((o: any) => ({ value: String(o.value ?? o.label), label: String(o.label ?? o.value), })); } return []; }, [catCol]); const getProductsForCategory = useCallback((selectedCat?: string) => { let allOpts: { value: string; label: string; rawCat?: string; raw?: any }[] = []; if (prodNameCol?.properties?.options && prodNameCol.properties.options.length > 0) { allOpts = prodNameCol.properties.options.map((o: any) => { const val = String(o.value ?? o.label ?? ''); const label = String(o.label ?? o.value ?? ''); const rawCat = o._raw?.product_category || o._raw?.cat || o._raw?.category; return { value: val, label, rawCat, raw: o._raw }; }); if (selectedCat) { allOpts = allOpts.filter((o) => { if (o.rawCat) { return String(o.rawCat).trim().toLowerCase() === String(selectedCat).trim().toLowerCase(); } return o.label.toLowerCase().startsWith(String(selectedCat).toLowerCase()); }); } } return allOpts; }, [prodNameCol]); const updateRowField = useCallback((rowIdx: number, fieldId: string, val: unknown) => { const next = rows.map((r) => ({ ...r })); let row = { ...next[rowIdx] }; const colDef = columns.find((c) => c.id === fieldId) || visibleColumns.find((c) => c.id === fieldId); const colName = colDef?.name || ''; const isCatField = isCategoryColumn(fieldId, colName); const isProdField = isProductColumn(fieldId, colName); const isBagsField = colDef?.data_type === 'number' || fieldId.includes('bags') || fieldId.includes('quantity') || colName.toLowerCase().includes('bags'); if (isCatField) { const catVal = String(val ?? ''); row[fieldId] = catVal; row['category'] = catVal; row['product_category'] = catVal; const pColId = prodNameCol?.id || 'product_name'; row[pColId] = ''; row['product_name'] = ''; row['productName'] = ''; row['sku'] = ''; row['br_code'] = ''; row['br'] = ''; row['sku_code'] = ''; row['skucode'] = ''; row['bags'] = ''; row['quantity'] = ''; if (bagsCol) row[bagsCol.id] = ''; row['row_kgs'] = 0; } else if (isProdField) { const currentCat = String(row['category'] || row['product_category'] || (catCol ? row[catCol.id] : '') || ''); const prodOptions = getProductsForCategory(currentCat); const matchedOpt = prodOptions.find( (o) => o.value === String(val) || o.label === String(val) || o.label.toLowerCase() === String(val).toLowerCase() ); const selectedName = matchedOpt?.label || String(val); const sku = matchedOpt?.raw?.sku || ''; const br = matchedOpt?.raw?.br_code || matchedOpt?.raw?.br || matchedOpt?.raw?.brcode || matchedOpt?.raw?.brand_code || ''; const skucode = matchedOpt?.raw?.sku_code || matchedOpt?.raw?.skucode || ''; const cat = matchedOpt?.rawCat || matchedOpt?.raw?.product_category || currentCat; const setVal = matchedOpt?.value || String(val); row[fieldId] = setVal; row['product_name'] = selectedName; row['productName'] = selectedName; if (prodNameCol) row[prodNameCol.id] = setVal; row['sku'] = sku; const skuCol = findColumn(columns, 'sku'); if (skuCol) row[skuCol.id] = sku; row['br'] = br; row['br_code'] = br; const brCol = findColumn(columns, 'br_code', 'br'); if (brCol) row[brCol.id] = br; row['skucode'] = skucode; row['sku_code'] = skucode; const skuCodeCol = findColumn(columns, 'sku_code', 'skucode'); if (skuCodeCol) row[skuCodeCol.id] = skucode; if (cat) { row['category'] = cat; row['product_category'] = cat; if (catCol) row[catCol.id] = cat; } const bagsVal = Number(row.bags ?? row.quantity ?? (bagsCol ? row[bagsCol.id] : 0)) || 0; row['row_kgs'] = Number(sku || 0) * bagsVal; } else if (isBagsField) { const parsedVal = val !== '' && val !== null && val !== undefined ? val : ''; const numericVal = Number(val) || 0; row[fieldId] = parsedVal; row['bags'] = parsedVal; row['quantity'] = parsedVal; if (bagsCol) row[bagsCol.id] = parsedVal; const skuVal = Number(row.sku || 0); row['row_kgs'] = skuVal * numericVal; } else { row[fieldId] = val; } next[rowIdx] = row; const cleanRows = next.map((r) => { const clean: Record = {}; Object.keys(r).forEach((k) => { if (k !== '_row_total_kgs' && k !== 'row_kgs' && k !== 'row_total') { clean[k] = r[k]; } }); return clean; }); onChange(cleanRows); }, [rows, columns, visibleColumns, prodNameCol, catCol, bagsCol, getProductsForCategory, onChange]); const { calculatedBags, calculatedKgs } = useMemo(() => { let bagsAcc = 0; let kgsAcc = 0; for (const r of rows) { const b = Number(r.bags ?? r.quantity ?? (bagsCol ? r[bagsCol.id] : 0)) || 0; const sku = Number(r.sku) || 0; bagsAcc += b; kgsAcc += b * sku; } return { calculatedBags: Math.round(bagsAcc), calculatedKgs: Math.round(kgsAcc) }; }, [rows, bagsCol]); const displayTotalBags = totalBagsValue !== undefined ? totalBagsValue : calculatedBags; const displayTotalKgs = totalKgsValue !== undefined ? totalKgsValue : calculatedKgs; return (

{label || 'Order details'}

{visibleColumns.map((col) => ( {col.name} ))}
{rows.map((row, rowIdx) => { const currentCatVal = String(row.category ?? row.product_category ?? (catCol ? row[catCol.id] : '') ?? ''); const currentProdVal = String(row[prodNameCol?.id || 'product_name'] ?? row.product_name ?? row.productName ?? ''); const currentBagsVal = row.bags ?? row.quantity ?? (bagsCol ? row[bagsCol.id] : '') ?? ''; const prodOptions = getProductsForCategory(currentCatVal); const isCategorySelected = Boolean(currentCatVal.trim()); const skuNum = Number(row.sku) || 0; const bagsNum = Number(currentBagsVal) || 0; const computedRowKgs = skuNum * bagsNum; const isMissingProd = isCategorySelected && !currentProdVal.trim(); const prodError = showErrors && isMissingProd ? 'Required' : undefined; const isMissingBags = isCategorySelected && (!currentBagsVal || Number(currentBagsVal) <= 0); const bagsError = showErrors && isMissingBags ? 'Required' : undefined; return (
{visibleColumns.map((col) => { const fieldId = col.id; if (fieldId === '_row_total_kgs') { return ( 0 ? `${computedRowKgs.toLocaleString()} kgs` : '0 kgs'} className="w-full font-semibold text-slate-700 bg-slate-50 border-slate-200" /> ); } const isCat = isCategoryColumn(fieldId, col.name); const isProd = isProductColumn(fieldId, col.name); const isBagsOrNum = col.data_type === 'number' || fieldId.includes('bags') || fieldId.includes('quantity') || col.name?.toLowerCase().includes('bags'); if (isCat) { return ( updateRowField(rowIdx, fieldId, e.target.value)} disabled={!isCategorySelected && categoriesList.length > 0} options={[ { value: '', label: isCategorySelected || categoriesList.length === 0 ? 'Select product' : 'Select category first' }, ...prodOptions ]} error={prodError} className="w-full" /> ); } if (isBagsOrNum) { const isMainBagsCol = bagsCol?.id === fieldId || fieldId === 'bags' || fieldId === 'quantity'; const numVal = isMainBagsCol ? currentBagsVal : row[fieldId]; return ( updateRowField(rowIdx, fieldId, e.target.value)} placeholder={isMainBagsCol && isCategorySelected ? 'Bags' : '0'} error={isMainBagsCol ? bagsError : undefined} className="w-full" /> ); } if (col.data_type === 'select' || col.data_type === 'multiselect') { const opts = col.properties?.options || []; return ( updateRowField(rowIdx, fieldId, e.target.value)} className="w-full" /> ); })}
); })}
{bagsCol && (
{visibleColumns.map((col, idx) => { const fieldId = col.id; const isBagsOrNum = col.data_type === 'number' || fieldId.includes('bags') || fieldId.includes('quantity') || col.name?.toLowerCase().includes('bags'); if (isBagsOrNum) { return ( ); } if (fieldId === '_row_total_kgs') { return ( ); } if (idx === 0) { return
Total
; } return
; })}
)}
); }