import { Form, Space } from "antd"; import { useTranslation } from "react-i18next"; import AlertComponent from "../alert/alert.component"; import "./form-fields-changed.styles.scss"; import Prompt from "../../utils/prompt"; export default function FormsFieldChanged({ form, skipPrompt, onErrorNavigate, onReset, onDirtyChange }) { const { t } = useTranslation(); const normalizeNamePath = (namePath) => (Array.isArray(namePath) ? namePath.filter((part) => part !== undefined) : [namePath]); const getFieldIdCandidates = (namePath) => { const normalizedNamePath = normalizeNamePath(namePath).map((part) => String(part)); const underscoreId = normalizedNamePath.join("_"); const dashId = normalizedNamePath.join("-"); const dotName = normalizedNamePath.join("."); return [underscoreId, dashId, dotName].filter(Boolean); }; const clearFormMeta = () => { const fieldMeta = form.getFieldsError().map(({ name }) => ({ name, touched: false, validating: false, errors: [], warnings: [] })); if (fieldMeta.length > 0) { form.setFields(fieldMeta); } onDirtyChange?.(false); }; const handleReset = () => { if (onReset) { onReset(); } else { form.resetFields(); } window.requestAnimationFrame(() => { clearFormMeta(); }); }; const getFieldDomNode = (namePath) => { const fieldInstance = form.getFieldInstance?.(namePath); const fieldIdCandidates = getFieldIdCandidates(namePath); const domCandidates = [ fieldInstance?.nativeElement, fieldInstance?.input, fieldInstance?.resizableTextArea?.textArea, fieldInstance ]; fieldIdCandidates.forEach((fieldId) => { const escapedFieldId = CSS.escape(fieldId); const directNode = document.getElementById(fieldId) || document.querySelector(`#${escapedFieldId}`); const labelNode = document.querySelector(`label[for="${escapedFieldId}"]`); const namedNode = document.querySelector(`[name="${escapedFieldId}"]`); const formItemNode = directNode?.closest?.(".ant-form-item") || labelNode?.closest?.(".ant-form-item") || namedNode?.closest?.(".ant-form-item"); domCandidates.push(directNode); domCandidates.push(namedNode); domCandidates.push(formItemNode); domCandidates.push(formItemNode?.querySelector?.("input, textarea, select, .ant-select-selector")); }); return domCandidates.find((candidate) => candidate instanceof HTMLElement) ?? null; }; const waitForAnimationFrames = (frameCount = 1) => new Promise((resolve) => { let remainingFrames = frameCount; const nextFrame = () => { if (remainingFrames <= 0) { resolve(); return; } remainingFrames -= 1; window.requestAnimationFrame(nextFrame); }; window.requestAnimationFrame(nextFrame); }); const getFieldOwningTabMeta = (namePath) => { const fieldDomNode = getFieldDomNode(namePath); const owningTabPane = fieldDomNode?.closest?.(".ant-tabs-tabpane"); const paneId = owningTabPane?.getAttribute?.("id") || null; const owningTabButton = paneId ? document.querySelector(`[role="tab"][aria-controls="${paneId.replace(/"/g, '\\"')}"]`) : null; const tabLabel = owningTabButton?.textContent?.trim() || null; return { owningTabPane, owningTabButton, tabLabel }; }; const openFieldOwningTab = async (namePath) => { const { owningTabPane, owningTabButton } = getFieldOwningTabMeta(namePath); if (!owningTabPane || owningTabPane.classList.contains("ant-tabs-tabpane-active")) return false; if (!(owningTabButton instanceof HTMLElement)) return false; owningTabButton.click(); for (let index = 0; index < 24; index += 1) { await waitForAnimationFrames(); if (owningTabPane.classList.contains("ant-tabs-tabpane-active")) return true; } return owningTabPane.classList.contains("ant-tabs-tabpane-active"); }; const scrollToErrorField = (namePath) => { const normalizedNamePath = normalizeNamePath(namePath); if (!normalizedNamePath.length) return; try { form.scrollToField(normalizedNamePath, { behavior: "smooth", block: "center", focus: true }); window.requestAnimationFrame(() => { const fallbackNode = getFieldDomNode(normalizedNamePath); fallbackNode?.focus?.(); }); return; } catch { const fallbackTarget = document.getElementById(normalizedNamePath[0]?.toString?.() ?? ""); fallbackTarget?.scrollIntoView({ behavior: "smooth", block: "center" }); } }; const handleErrorClick = async (namePath) => { const normalizedNamePath = normalizeNamePath(namePath); if (!normalizedNamePath.length) return; const switchedTab = await openFieldOwningTab(normalizedNamePath); if (!switchedTab) { const navigationDelayMs = onErrorNavigate?.(normalizedNamePath) ?? 0; if (navigationDelayMs > 0) { window.setTimeout(() => { window.requestAnimationFrame(() => { scrollToErrorField(normalizedNamePath); }); }, navigationDelayMs); return; } } await waitForAnimationFrames(switchedTab ? 2 : 1); scrollToErrorField(normalizedNamePath); }; //if (!form.isFieldsTouched()) return <>; return ( {() => { const errors = form .getFieldsError() .filter((fieldError) => fieldError.errors.length > 0) .flatMap((fieldError) => { const tabMeta = getFieldOwningTabMeta(fieldError.name); return fieldError.errors.map((errorMessage, errorIndex) => ({ key: `${(fieldError.name || []).join(".")}-${errorIndex}-${errorMessage}`, message: errorMessage, namePath: fieldError.name, tabLabel: tabMeta.tabLabel })); }); const groupedErrors = errors.reduce((groups, error) => { const groupKey = error.tabLabel || "__ungrouped__"; if (!groups[groupKey]) { groups[groupKey] = { key: groupKey, label: error.tabLabel, errors: [] }; } groups[groupKey].errors.push(error); return groups; }, {}); const errorGroups = Object.values(groupedErrors); const hasTabbedErrorGroups = errorGroups.some((group) => Boolean(group.label)); if (form.isFieldsTouched()) return ( {t("general.messages.unsavedchanges")} {t("general.actions.reset")} } /> {errors.length > 0 && ( {errorGroups.map((group) => (
{hasTabbedErrorGroups && group.label ? (
{group.label}
) : null}
    {group.errors.map((error) => (
  • {Array.isArray(error.namePath) && error.namePath.length > 0 ? ( ) : ( error.message )}
  • ))}
))} } showIcon /> )}
); return
; }}
); }