krishna_sales/src/components/forms/fields/SmartGridField.tsx

146 lines
6.0 KiB
TypeScript

import { Button } from '../../buttons/Button';
import { Select } from '../../reusable/Select';
import { Input } from '../../reusable/Input';
import type { FormScreenField } from '../../../api/types';
export function SmartGridField({
label,
columns,
value = [],
onChange,
}: {
label: string;
columns: FormScreenField[];
value: Record<string, unknown>[];
onChange: (val: Record<string, unknown>[]) => void;
}) {
const addRow = () => onChange([...value, {}]);
const removeRow = (idx: number) => {
const next = [...value];
next.splice(idx, 1);
onChange(next);
};
const updateRow = (idx: number, fieldId: string, val: unknown) => {
const next = [...value];
let row = { ...next[idx], [fieldId]: val };
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')) {
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) {
for (const key of Object.keys(option._raw)) {
// Match column ID with or without underscores (e.g., brcode <-> br_code)
const targetCol = columns.find(c => c.id === key || c.id.replace(/_/g, '') === key.replace(/_/g, ''));
if (targetCol && targetCol.id !== fieldId) {
row[targetCol.id] = option._raw[key];
}
}
}
}
// Auto-calculate row_kgs if sku and bags are present
const skuVal = Number(row.sku) || 0;
const bagsVal = Number(row.bags) || 0;
row.row_kgs = skuVal * bagsVal;
next[idx] = row;
onChange(next);
};
return (
<div className="flex flex-col gap-2 font-sans border border-border-default rounded-md p-4 bg-slate-50">
<span className="text-sm font-semibold text-strong mb-2">{label}</span>
{value.length === 0 ? (
<span className="text-sm text-faint italic">No rows added.</span>
) : (
<div className="flex flex-col gap-4">
{value.map((row, i) => (
<div key={i} className="flex flex-col gap-3 p-3 bg-white border border-border-subtle rounded relative shadow-sm">
<div className="absolute top-2 right-2">
<button type="button" onClick={() => removeRow(i)} className="text-xs text-ruby-600 font-medium hover:underline">
Remove
</button>
</div>
<span className="text-xs font-bold text-muted uppercase tracking-wider">Row {i + 1}</span>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{columns.map(col => {
const val = row[col.id];
if (col.data_type === 'select' || col.data_type === 'multiselect') {
// Cascade filter options
const allOpts = col.properties?.options || [];
let filteredOpts = allOpts;
// Specific logic for product_name depending on product_category
if (col.id === 'product_name' || col.name === 'Product Name') {
const catCol = columns.find(c => c.id === 'product_category' || c.name === 'Product Category');
if (catCol) {
const selectedCategory = row[catCol.id] as string;
if (selectedCategory) {
filteredOpts = allOpts.filter(opt => {
const labelStr = String(opt.label || opt.value || '');
return labelStr.startsWith(selectedCategory);
});
}
}
} else {
// Generic _raw cascading for other fields just in case
filteredOpts = allOpts.filter(opt => {
if (!opt._raw) return true;
for (const [rowKey, rowVal] of Object.entries(row)) {
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 (
<Select
key={col.id}
label={col.name}
required={col.mandatory}
value={(val as string) ?? ''}
onChange={(e) => updateRow(i, col.id, e.target.value)}
options={[{ value: '', label: 'Select...' }, ...filteredOpts.map((o: any) => ({ value: String(o.value), label: o.label }))]}
/>
);
}
return (
<Input
key={col.id}
label={col.name}
required={col.mandatory}
type={col.data_type === 'number' ? 'number' : col.data_type === 'email' ? 'email' : 'text'}
value={(val as string) ?? ''}
onChange={(e) => updateRow(i, col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)}
/>
);
})}
</div>
</div>
))}
</div>
)}
<Button type="button" variant="secondary" size="sm" onClick={addRow} className="mt-2 self-start">
+ Add Row
</Button>
</div>
);
}