189 lines
7.4 KiB
TypeScript
189 lines
7.4 KiB
TypeScript
import { useMemo, useCallback } from 'react';
|
|
import type { FormScreenField } from '../../../api/types';
|
|
import { Select } from '../../reusable/Select';
|
|
import { Input } from '../../reusable/Input';
|
|
import { isCategoryColumn } from './gridUtils';
|
|
|
|
export interface PotentialMiningGridProps {
|
|
label: string;
|
|
columns: FormScreenField[];
|
|
value: Record<string, unknown>[];
|
|
onChange: (val: Record<string, unknown>[]) => void;
|
|
readOnlyCols?: string[];
|
|
}
|
|
|
|
export function PotentialMiningGrid({
|
|
label,
|
|
columns,
|
|
value = [],
|
|
onChange,
|
|
readOnlyCols = [],
|
|
}: PotentialMiningGridProps) {
|
|
const rows = value.length > 0 ? value : [{}];
|
|
|
|
const catCol = useMemo(() => columns.find(c => isCategoryColumn(c.id, c.name)), [columns]);
|
|
|
|
const visibleColumns = useMemo(() => {
|
|
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') && !idL.includes('brcode') && !idL.includes('brand_code') &&
|
|
!nL.includes('br code') && !idL.includes('row_kgs');
|
|
});
|
|
}, [columns]);
|
|
|
|
const gridTemplateColumns = useMemo(() => {
|
|
const numCols = visibleColumns.length;
|
|
if (numCols === 4) return `1.3fr 1.5fr 0.7fr 0.8fr`;
|
|
if (numCols === 3) return `1.4fr 1.6fr 0.7fr`;
|
|
if (numCols === 2) return `1.5fr 1.5fr`;
|
|
return `repeat(${numCols}, minmax(0, 1fr))`;
|
|
}, [visibleColumns]);
|
|
|
|
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 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);
|
|
const colName = colDef?.name || '';
|
|
const isCatField = isCategoryColumn(fieldId, colName);
|
|
|
|
if (isCatField) {
|
|
const catVal = String(val ?? '');
|
|
row[fieldId] = catVal;
|
|
row['category'] = catVal;
|
|
row['product_category'] = catVal;
|
|
} else {
|
|
row[fieldId] = val;
|
|
}
|
|
|
|
next[rowIdx] = row;
|
|
onChange(next);
|
|
}, [rows, columns, onChange]);
|
|
|
|
return (
|
|
<div className="max-w-full bg-[var(--tiles-card-bg)] rounded-xl border border-border-default p-6 space-y-6 shadow-sm font-sans">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-sm font-bold text-slate-700 uppercase tracking-wide">
|
|
{label || 'Potential Mining'}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="border-t border-border-subtle pt-4">
|
|
<div className="grid gap-3 px-1 pb-2 text-xs font-bold text-muted uppercase tracking-wider" style={{ gridTemplateColumns }}>
|
|
{visibleColumns.map((col) => (
|
|
<span key={col.id}>{col.name}</span>
|
|
))}
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
{rows.map((row, rowIdx) => {
|
|
const currentCatVal = String(row.category ?? row.product_category ?? (catCol ? row[catCol.id] : '') ?? '');
|
|
|
|
return (
|
|
<div key={rowIdx}>
|
|
<div className="grid gap-3 items-center" style={{ gridTemplateColumns }}>
|
|
{visibleColumns.map((col) => {
|
|
const fieldId = col.id;
|
|
const isReadOnly = readOnlyCols.some(c => c === fieldId || c.toLowerCase() === col.name?.toLowerCase());
|
|
|
|
if (fieldId === 'difference' || col.name?.toLowerCase() === 'difference') {
|
|
const diffVal = Number(row[fieldId]) || 0;
|
|
let displayStr = '0 kgs';
|
|
let textColor = 'text-slate-600';
|
|
let bgColor = 'bg-slate-50 border-slate-200';
|
|
|
|
if (diffVal < 0) {
|
|
displayStr = `${Math.abs(diffVal)} kgs shortage`;
|
|
textColor = 'text-rose-600';
|
|
bgColor = 'bg-rose-50 border-rose-200';
|
|
} else if (diffVal > 0) {
|
|
displayStr = `${diffVal} kgs surplus`;
|
|
textColor = 'text-emerald-600';
|
|
bgColor = 'bg-emerald-50 border-emerald-200';
|
|
}
|
|
|
|
return (
|
|
<div key={fieldId} className={`flex items-center justify-center h-[42px] px-3 rounded-md border text-xs font-bold w-full ${bgColor} ${textColor}`}>
|
|
{displayStr}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const isCat = isCategoryColumn(fieldId, col.name);
|
|
const isBagsOrNum = col.data_type === 'number' || fieldId.includes('bags') || fieldId.includes('quantity') || col.name?.toLowerCase().includes('bags');
|
|
|
|
if (isCat) {
|
|
return (
|
|
<Select
|
|
key={fieldId}
|
|
value={currentCatVal}
|
|
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
|
|
disabled={isReadOnly}
|
|
options={[{ value: '', label: 'Category' }, ...categoriesList]}
|
|
className="w-full"
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (isBagsOrNum) {
|
|
return (
|
|
<Input
|
|
key={fieldId}
|
|
type="number"
|
|
disabled={isReadOnly}
|
|
value={row[fieldId] !== undefined && row[fieldId] !== null ? String(row[fieldId]) : ''}
|
|
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
|
|
placeholder="0"
|
|
className="w-full"
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (col.data_type === 'select' || col.data_type === 'multiselect') {
|
|
const opts = col.properties?.options || [];
|
|
return (
|
|
<Select
|
|
key={fieldId}
|
|
value={String(row[fieldId] ?? '')}
|
|
disabled={isReadOnly}
|
|
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
|
|
options={[{ value: '', label: 'Select...' }, ...opts.map((o: any) => ({ value: String(o.value), label: o.label }))]}
|
|
className="w-full"
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Input
|
|
key={fieldId}
|
|
type="text"
|
|
disabled={isReadOnly}
|
|
value={String(row[fieldId] ?? '')}
|
|
onChange={(e) => updateRowField(rowIdx, fieldId, e.target.value)}
|
|
placeholder={col.name ? `Enter ${col.name.toLowerCase()}...` : 'Enter details...'}
|
|
className="w-full"
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|