UNPKG

@arudovwen/react-form-builder

Version:
248 lines (241 loc) 10.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = DataGrid; var _react = _interopRequireWildcard(require("react")); var _reactCurrencyInputField = _interopRequireDefault(require("react-currency-input-field")); var _axios = _interopRequireDefault(require("axios")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function DataGrid({ value = [], onChange, isReadOnly, columns = [], url }) { const [rows, setRows] = (0, _react.useState)(value); const [editingCell, setEditingCell] = (0, _react.useState)(null); // { rowIndex, field } const [validatingCells, setValidatingCells] = (0, _react.useState)(new Set()); // Track cells being validated const [validationResults, setValidationResults] = (0, _react.useState)({}); // Store validation results // Keep local rows in sync with prop (0, _react.useEffect)(() => { setRows(value); }, []); const updateRowsAndNotify = updated => { setRows(updated); if (onChange) { onChange(updated); } }; // Validation function - only runs for columns with validate: true const validateCell = async (val, rowIndex, field, column) => { // Double check that validation is explicitly true if (column.validate !== true || !url || !val) return; const cellKey = `${rowIndex}-${field}`; setValidatingCells(prev => new Set([...prev, cellKey])); try { const validationUrl = `${url}/${encodeURIComponent(val)}`; const { data, status } = await _axios.default.get(validationUrl); // Store validation result setValidationResults(prev => ({ ...prev, [cellKey]: { isValid: status === 200 && data, data: data.data?.description || data?.description, error: null } })); } catch (error) { // Store validation error setValidationResults(prev => ({ ...prev, [cellKey]: { isValid: false, data: null, error: error.response?.data?.message || error.message || 'Validation failed' } })); } finally { setValidatingCells(prev => { const newSet = new Set(prev); newSet.delete(cellKey); return newSet; }); } }; const handleCellChange = async (val, rowIndex, field) => { const updated = rows.map((row, i) => i === rowIndex ? { ...row, [field]: val } : row); updateRowsAndNotify(updated); // Find the column to check if validation is needed const column = columns.find(col => col.field === field); // Only validate if column has validate: true, url is provided, and value exists if (column && column.validate === true && url && val) { await validateCell(val, rowIndex, field, column); } }; const addRow = () => { const newId = rows.length > 0 ? Math.max(...rows.map(row => Number(row.id) || 0)) + 1 : 1; const newRow = Object.fromEntries(columns.map(col => [col.field, col.field === 'id' ? newId : ''])); updateRowsAndNotify([...rows, newRow]); }; const deleteRow = index => { // Clean up validation results for deleted row const updatedValidationResults = { ...validationResults }; Object.keys(updatedValidationResults).forEach(key => { if (key.startsWith(`${index}-`)) { delete updatedValidationResults[key]; } }); setValidationResults(updatedValidationResults); const updated = rows.filter((_, i) => i !== index); updateRowsAndNotify(updated); }; const isEditing = (rowIndex, field) => editingCell?.rowIndex === rowIndex && editingCell?.field === field; const getCellValidationStatus = (rowIndex, field) => { const cellKey = `${rowIndex}-${field}`; const isValidating = validatingCells.has(cellKey); const validationResult = validationResults[cellKey]; return { isValidating, validationResult }; }; // Renders editable inputs based on column type const renderEditableCell = (col, inputValue, rowIndex) => { const { isValidating, validationResult } = getCellValidationStatus(rowIndex, col.field); let validationClass = 'border-gray-300'; if (col.validate === true) { if (validationResult?.isValid === false) { validationClass = 'border-red-500 bg-red-50'; } else if (validationResult?.isValid === true) { validationClass = 'border-green-500 bg-green-50'; } } const inputClassName = `w-full px-2 py-1 border rounded outline-none ${validationClass}`; if (col.type === 'number') { return /*#__PURE__*/_react.default.createElement("div", { className: "relative" }, /*#__PURE__*/_react.default.createElement(_reactCurrencyInputField.default, { value: inputValue ?? '', onValueChange: val => handleCellChange(val, rowIndex, col.field), decimalsLimit: 6, allowNegativeValue: false, className: inputClassName, autoFocus: true }), isValidating && /*#__PURE__*/_react.default.createElement("div", { className: "absolute right-1 top-1" }, /*#__PURE__*/_react.default.createElement("div", { className: "w-4 h-4 border-2 border-blue-500 rounded-full border-t-transparent animate-spin" }))); } if (col.type === 'checkbox') { return /*#__PURE__*/_react.default.createElement("input", { type: "checkbox", checked: !!inputValue, onChange: e => handleCellChange(e.target.checked, rowIndex, col.field), onBlur: () => setEditingCell(null), className: "px-2 py-1 text-gray-600 border rounded outline-none", autoFocus: true }); } return /*#__PURE__*/_react.default.createElement("div", { className: "relative" }, /*#__PURE__*/_react.default.createElement("input", { type: "text", value: inputValue ?? '', onChange: e => handleCellChange(e.target.value, rowIndex, col.field), onBlur: () => setEditingCell(null), className: inputClassName, autoFocus: true }), isValidating && /*#__PURE__*/_react.default.createElement("div", { className: "absolute right-1 top-1" }, /*#__PURE__*/_react.default.createElement("div", { className: "w-4 h-4 border-2 border-blue-500 rounded-full border-t-transparent animate-spin" }))); }; // Renders read-only cell display const renderReadOnlyCell = (col, val, rowIndex) => { const { validationResult } = getCellValidationStatus(rowIndex, col.field); const cellContent = (() => { if (col.type === 'number') { return Number(val).toLocaleString(); } if (col.type === 'checkbox') { return val ? 'Yes' : 'No'; } return val; })(); if (col.validate === true && validationResult) { return /*#__PURE__*/_react.default.createElement("div", { className: "flex items-center gap-2" }, /*#__PURE__*/_react.default.createElement("span", null, cellContent, " ", typeof validationResult?.data === 'string' ? `[${validationResult?.data}]` : ''), validationResult.isValid ? /*#__PURE__*/_react.default.createElement("span", { className: "text-xs text-green-600" }, "\u2713") : /*#__PURE__*/_react.default.createElement("span", { className: "text-xs text-red-600 cursor-help", title: validationResult.error }, "\u2717")); } return cellContent; }; return /*#__PURE__*/_react.default.createElement("div", { className: "mt-3 rounded" }, /*#__PURE__*/_react.default.createElement("div", { className: "flex justify-end" }, columns.length > 0 && !isReadOnly && /*#__PURE__*/_react.default.createElement("button", { onClick: addRow, type: "button", className: "px-2 py-1 mb-3 text-xs text-white bg-gray-600 rounded hover:bg-gray-700" }, "Add Row")), /*#__PURE__*/_react.default.createElement("table", { className: "w-full text-sm border-collapse rounded table-auto bg-gray-50" }, /*#__PURE__*/_react.default.createElement("thead", null, /*#__PURE__*/_react.default.createElement("tr", { className: "bg-gray-100" }, columns.map((col, index) => /*#__PURE__*/_react.default.createElement("th", { key: `${col.field}- ${index}`, className: "px-3 py-2 text-xs font-semibold text-left text-gray-600 border" }, col.headerName || col.field, col.validate === true && /*#__PURE__*/_react.default.createElement("span", { className: "ml-1 text-blue-600", title: "This field will be validated" }, "*"))), !isReadOnly && /*#__PURE__*/_react.default.createElement("th", { className: "w-10 px-2 py-2 border" }))), /*#__PURE__*/_react.default.createElement("tbody", null, rows.length > 0 ? rows.map((row, rowIndex) => /*#__PURE__*/_react.default.createElement("tr", { key: `${row.id}- ${rowIndex}` }, columns.map(col => { const cellValue = row[col.field]; const editable = col.editable && !isReadOnly; const editing = isEditing(rowIndex, col.field); return /*#__PURE__*/_react.default.createElement("td", { key: col.field, className: "px-3 py-1 border", onDoubleClick: () => editable && setEditingCell({ rowIndex, field: col.field }) }, editable && editing ? renderEditableCell(col, cellValue, rowIndex) : /*#__PURE__*/_react.default.createElement("span", { className: "block py-1 text-gray-700 cursor-pointer" }, renderReadOnlyCell(col, cellValue, rowIndex))); }), !isReadOnly && /*#__PURE__*/_react.default.createElement("td", { className: "px-3 py-1 text-center border" }, /*#__PURE__*/_react.default.createElement("button", { onClick: () => deleteRow(rowIndex), className: "text-red-500 hover:text-red-700", "aria-label": "Delete row" }, "Remove")))) : /*#__PURE__*/_react.default.createElement("tr", null, /*#__PURE__*/_react.default.createElement("td", { colSpan: columns.length + (isReadOnly ? 0 : 1), className: "p-2 text-xs text-center text-gray-400" }, "No data available"))))); }