@oondemand/oon-core-front 0.3.48 → 0.3.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2069,7 +2069,7 @@ function DynamicForm({ title, eyebrow = "Cadastro", fields: rawFields, initialVa
2069
2069
  }
2070
2070
 
2071
2071
  // src/components/internal/CoreDetailModal.tsx
2072
- import { useEffect as useEffect6, useMemo as useMemo7, useState as useState9 } from "react";
2072
+ import { useEffect as useEffect6, useMemo as useMemo7, useRef as useRef2, useState as useState9 } from "react";
2073
2073
  import { Box as Box9, Button as Button8, Flex as Flex7, Heading as Heading3, IconButton, Text as Text10 } from "@chakra-ui/react";
2074
2074
  import { useQuery as useQuery6, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
2075
2075
 
@@ -2098,6 +2098,14 @@ function fieldsForTab(fields, selected) {
2098
2098
  function relationFrom(input, relations) {
2099
2099
  return typeof input === "string" ? relations?.[input] : input;
2100
2100
  }
2101
+ function resolveInitialDetailTab(config, recordId, initialTab) {
2102
+ if (initialTab) return initialTab;
2103
+ const configuredId = config.defaultTab ?? config.tabs[0]?.id ?? "dados";
2104
+ const configuredTab = config.tabs.find((tab) => tab.id === configuredId);
2105
+ if (recordId) return configuredTab?.id ?? config.tabs[0]?.id ?? "dados";
2106
+ if (configuredTab?.type === "form") return configuredTab.id;
2107
+ return config.tabs.find((tab) => tab.type === "form")?.id ?? configuredTab?.id ?? config.tabs[0]?.id ?? "dados";
2108
+ }
2101
2109
 
2102
2110
  // src/components/internal/CoreSummaryCards.tsx
2103
2111
  import { Fragment, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -2898,29 +2906,52 @@ function GroupSection({
2898
2906
  )) })
2899
2907
  ] });
2900
2908
  }
2909
+ function formValuesEqual(fields, left, right) {
2910
+ return fields.every((field) => (left[field.field] ?? "") === (right[field.field] ?? ""));
2911
+ }
2912
+ function fieldsForFormTabs(fields, tabs) {
2913
+ const formTabs = tabs.filter((tab) => tab.type === "form");
2914
+ if (!formTabs.length) return [];
2915
+ if (formTabs.some((tab) => !tab.fields?.length && !tab.groups?.length)) return fields;
2916
+ const selected = new Set(
2917
+ formTabs.flatMap((tab) => tab.fields ?? tab.groups?.flatMap((group) => group.fields) ?? [])
2918
+ );
2919
+ return fields.filter((field) => selected.has(field.field));
2920
+ }
2901
2921
  function InlineParentForm({
2902
2922
  fields: rawFields,
2923
+ visibleFields: rawVisibleFields,
2903
2924
  groups,
2904
2925
  parent,
2905
2926
  resource,
2906
2927
  onSaved,
2928
+ onDirtyChange,
2929
+ onSaveFailed,
2907
2930
  formId,
2908
2931
  externalSubmit = false
2909
2932
  }) {
2910
2933
  const fields = rawFields;
2911
- const initial = initialReactiveValues2(fields, parent);
2934
+ const visibleFields = rawVisibleFields ?? rawFields;
2935
+ const initial = useMemo6(() => initialReactiveValues2(fields, parent), [rawFields, parent]);
2912
2936
  const [values, setValues] = useState8(initial.values);
2913
2937
  const [formulaErrors, setFormulaErrors] = useState8(initial.errors);
2914
2938
  const [saving, setSaving] = useState8(false);
2915
2939
  const [error, setError] = useState8(null);
2916
2940
  const [fieldErrors, setFieldErrors] = useState8({});
2917
2941
  useEffect5(() => {
2918
- const next = initialReactiveValues2(fields, parent);
2919
- setValues(next.values);
2920
- setFormulaErrors(next.errors);
2942
+ setValues(initial.values);
2943
+ setFormulaErrors(initial.errors);
2921
2944
  setError(null);
2922
2945
  setFieldErrors({});
2923
- }, [parent, rawFields]);
2946
+ onDirtyChange?.(false);
2947
+ }, [initial, onDirtyChange]);
2948
+ const dirty = useMemo6(
2949
+ () => !formValuesEqual(fields, values, initial.values),
2950
+ [fields, values, initial.values]
2951
+ );
2952
+ useEffect5(() => {
2953
+ onDirtyChange?.(dirty);
2954
+ }, [dirty, onDirtyChange]);
2924
2955
  const fieldsByName = useMemo6(() => new Map(fields.map((field) => [field.field, field])), [rawFields]);
2925
2956
  const change = (fieldName, value) => {
2926
2957
  const selected = fieldsByName.get(fieldName);
@@ -2962,7 +2993,7 @@ function InlineParentForm({
2962
2993
  `${group.label}-${group.fields.join("-")}`
2963
2994
  )) });
2964
2995
  }
2965
- return /* @__PURE__ */ jsx15(SimpleGrid3, { columns: { base: 1, md: 2 }, gap: "14px", children: fields.map((field) => /* @__PURE__ */ jsx15(
2996
+ return /* @__PURE__ */ jsx15(SimpleGrid3, { columns: { base: 1, md: 2 }, gap: "14px", children: visibleFields.map((field) => /* @__PURE__ */ jsx15(
2966
2997
  FieldControl,
2967
2998
  {
2968
2999
  field,
@@ -2986,6 +3017,7 @@ function InlineParentForm({
2986
3017
  if (Object.keys(validation).length) {
2987
3018
  setFieldErrors(validation);
2988
3019
  setError({ message: "Preencha os campos obrigat\xF3rios destacados." });
3020
+ onSaveFailed?.();
2989
3021
  return;
2990
3022
  }
2991
3023
  setSaving(true);
@@ -2994,10 +3026,12 @@ function InlineParentForm({
2994
3026
  try {
2995
3027
  const payload = buildDomainMutationPayload(fields, values);
2996
3028
  const saved = parent._id ? await resource.update(String(parent._id), payload) : await resource.create(payload);
3029
+ onDirtyChange?.(false);
2997
3030
  onSaved(saved);
2998
3031
  } catch (requestError) {
2999
3032
  setError(requestError);
3000
3033
  setFieldErrors(fieldErrorsOf(requestError));
3034
+ onSaveFailed?.();
3001
3035
  } finally {
3002
3036
  setSaving(false);
3003
3037
  }
@@ -3021,10 +3055,16 @@ function CoreTabbedDetail({
3021
3055
  refreshTokens,
3022
3056
  onRefresh,
3023
3057
  onParentSaved,
3058
+ onDirtyChange,
3059
+ onSaveFailed,
3024
3060
  formId,
3025
3061
  externalSubmit
3026
3062
  }) {
3027
3063
  const active = tabs.find((tab) => tab.id === activeTab) ?? tabs[0];
3064
+ const activeForm = active?.type === "form" ? active : void 0;
3065
+ const draftFields = useMemo6(() => fieldsForFormTabs(formFields, tabs), [formFields, tabs]);
3066
+ const formParent = useMemo6(() => parent ?? {}, [parent]);
3067
+ const hasFormTab = draftFields.length > 0;
3028
3068
  return /* @__PURE__ */ jsxs10(Stack3, { gap: 4, children: [
3029
3069
  /* @__PURE__ */ jsx15(Flex6, { gap: 2, borderBottomWidth: "1px", borderColor: "#E5E9ED", overflowX: "auto", children: tabs.map((tab) => {
3030
3070
  const disabled = !parent?._id && (tab.type === "relatedGrid" || tab.type === "readonlyGrid" || tab.type === "summary");
@@ -3041,56 +3081,52 @@ function CoreTabbedDetail({
3041
3081
  tab.id
3042
3082
  );
3043
3083
  }) }),
3044
- /* @__PURE__ */ jsx15(Box8, { children: !active ? null : active.type === "summary" && parent ? /* @__PURE__ */ jsx15(CoreSummaryCards, { cards: active.cards, parent, relations }) : active.type === "form" ? /* @__PURE__ */ jsx15(
3045
- InlineParentForm,
3046
- {
3047
- fields: fieldsForTab(formFields, active.fields ?? active.groups?.flatMap((group) => group.fields)),
3048
- groups: active.groups,
3049
- parent: parent ?? {},
3050
- resource,
3051
- onSaved: onParentSaved,
3052
- formId,
3053
- externalSubmit
3054
- }
3055
- ) : (active.type === "relatedGrid" || active.type === "readonlyGrid") && parent ? /* @__PURE__ */ jsx15(
3056
- CoreRelatedGrid,
3057
- {
3058
- tab: active,
3059
- parent,
3060
- relations,
3061
- refreshToken: refreshTokens[active.id],
3062
- onRefresh
3063
- }
3064
- ) : active.type === "customComponent" ? /* @__PURE__ */ jsxs10(Text9, { color: "#667085", children: [
3065
- "Componente customizado: ",
3066
- active.component
3067
- ] }) : /* @__PURE__ */ jsx15(Text9, { color: "#667085", children: "Salve o registro principal para liberar esta aba." }) })
3084
+ /* @__PURE__ */ jsxs10(Box8, { children: [
3085
+ hasFormTab ? /* @__PURE__ */ jsx15(Box8, { display: activeForm ? "block" : "none", children: /* @__PURE__ */ jsx15(
3086
+ InlineParentForm,
3087
+ {
3088
+ fields: draftFields,
3089
+ visibleFields: activeForm ? fieldsForTab(draftFields, activeForm.fields ?? activeForm.groups?.flatMap((group) => group.fields)) : [],
3090
+ groups: activeForm?.groups,
3091
+ parent: formParent,
3092
+ resource,
3093
+ onSaved: onParentSaved,
3094
+ onDirtyChange,
3095
+ onSaveFailed,
3096
+ formId,
3097
+ externalSubmit
3098
+ }
3099
+ ) }) : null,
3100
+ !active || active.type === "form" ? null : active.type === "summary" && parent ? /* @__PURE__ */ jsx15(CoreSummaryCards, { cards: active.cards, parent, relations }) : (active.type === "relatedGrid" || active.type === "readonlyGrid") && parent ? /* @__PURE__ */ jsx15(
3101
+ CoreRelatedGrid,
3102
+ {
3103
+ tab: active,
3104
+ parent,
3105
+ relations,
3106
+ refreshToken: refreshTokens[active.id],
3107
+ onRefresh
3108
+ }
3109
+ ) : active.type === "customComponent" ? /* @__PURE__ */ jsxs10(Text9, { color: "#667085", children: [
3110
+ "Componente customizado: ",
3111
+ active.component
3112
+ ] }) : /* @__PURE__ */ jsx15(Text9, { color: "#667085", children: "Salve o registro principal para liberar esta aba." })
3113
+ ] })
3068
3114
  ] });
3069
3115
  }
3070
3116
 
3071
3117
  // src/components/internal/CoreDetailModal.tsx
3072
3118
  import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
3073
- function CoreDetailModal({
3074
- model,
3075
- title,
3076
- config,
3077
- formFields,
3078
- recordId,
3079
- initialRecord,
3080
- initialTab,
3081
- relations,
3082
- topActions,
3083
- bottomActions,
3084
- onClose,
3085
- onSaved
3086
- }) {
3119
+ function CoreDetailModal({ model, title, config, formFields, recordId, initialRecord, initialTab, relations, topActions, bottomActions, onClose, onSaved }) {
3087
3120
  const schemaQuery = useModelSchema(model);
3088
3121
  const schema = schemaQuery.data;
3089
3122
  const resource = useOonResource(schema?.basePath ?? `/${model.toLowerCase()}s`);
3090
3123
  const queryClient = useQueryClient2();
3091
3124
  const [parent, setParent] = useState9(initialRecord);
3092
- const [activeTab, setActiveTab] = useState9(initialTab ?? config.defaultTab ?? config.tabs[0]?.id ?? "dados");
3125
+ const [activeTab, setActiveTab] = useState9(resolveInitialDetailTab(config, recordId, initialTab));
3093
3126
  const [refreshTokens, setRefreshTokens] = useState9({});
3127
+ const [dirty, setDirty] = useState9(false);
3128
+ const [confirmingClose, setConfirmingClose] = useState9(false);
3129
+ const closeAfterSaveRef = useRef2(false);
3094
3130
  const parentQuery = useQuery6({
3095
3131
  queryKey: ["oon", "detail", model, recordId],
3096
3132
  enabled: !!recordId,
@@ -3126,78 +3162,83 @@ function CoreDetailModal({
3126
3162
  const width = config.size === "full" ? "98vw" : config.size === "md" ? "760px" : config.size === "lg" ? "980px" : "1180px";
3127
3163
  const displayTitle = parent?.[config.titleField ?? ""] ? String(parent[config.titleField ?? ""]) : title;
3128
3164
  const activeIsForm = (config.tabs.find((tab) => tab.id === activeTab) ?? config.tabs[0])?.type === "form";
3165
+ const hasFormTab = config.tabs.some((tab) => tab.type === "form");
3129
3166
  const formId = `oon-detail-form-${model.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
3130
- return /* @__PURE__ */ jsx16(DetailRecordProvider, { record: parent, setRecord: setParent, children: /* @__PURE__ */ jsx16(Flex7, { position: "fixed", inset: 0, zIndex: 1e3, align: "center", justify: "center", p: 4, bg: "rgba(15,23,42,.48)", children: /* @__PURE__ */ jsxs11(Box9, { role: "dialog", "aria-modal": "true", display: "flex", flexDirection: "column", w: `min(${width}, 98vw)`, h: "94vh", maxH: "94vh", overflow: "hidden", bg: "white", borderRadius: "14px", boxShadow: "0 24px 70px rgba(15,23,42,.24)", children: [
3131
- /* @__PURE__ */ jsxs11(Flex7, { minH: "72px", px: "20px", py: "10px", align: "center", justify: "space-between", gap: 4, borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
3132
- /* @__PURE__ */ jsxs11(Box9, { minW: "160px", children: [
3133
- /* @__PURE__ */ jsx16(Text10, { color: "#0474AF", fontSize: "10px", fontWeight: "700", textTransform: "uppercase", children: "Detalhe" }),
3134
- /* @__PURE__ */ jsx16(Heading3, { size: "md", children: recordId ? displayTitle : `Novo ${title}` })
3167
+ const requestClose = () => {
3168
+ if (dirty) setConfirmingClose(true);
3169
+ else onClose();
3170
+ };
3171
+ const saveAndClose = () => {
3172
+ const form = document.getElementById(formId);
3173
+ if (!form) return;
3174
+ closeAfterSaveRef.current = true;
3175
+ setConfirmingClose(false);
3176
+ form.requestSubmit();
3177
+ };
3178
+ return /* @__PURE__ */ jsx16(DetailRecordProvider, { record: parent, setRecord: setParent, children: /* @__PURE__ */ jsxs11(Flex7, { position: "fixed", inset: 0, zIndex: 1e3, align: "center", justify: "center", p: 4, bg: "rgba(15,23,42,.48)", children: [
3179
+ /* @__PURE__ */ jsxs11(Box9, { role: "dialog", "aria-modal": "true", display: "flex", flexDirection: "column", w: `min(${width}, 98vw)`, h: "94vh", maxH: "94vh", overflow: "hidden", bg: "white", borderRadius: "14px", boxShadow: "0 24px 70px rgba(15,23,42,.24)", children: [
3180
+ /* @__PURE__ */ jsxs11(Flex7, { minH: "72px", px: "20px", py: "10px", align: "center", justify: "space-between", gap: 4, borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
3181
+ /* @__PURE__ */ jsxs11(Box9, { minW: "160px", children: [
3182
+ /* @__PURE__ */ jsx16(Text10, { color: "#0474AF", fontSize: "10px", fontWeight: "700", textTransform: "uppercase", children: "Detalhe" }),
3183
+ /* @__PURE__ */ jsx16(Heading3, { size: "md", children: recordId ? displayTitle : `Novo ${title}` })
3184
+ ] }),
3185
+ /* @__PURE__ */ jsxs11(Flex7, { flex: "1", align: "center", justify: "flex-end", gap: "8px", wrap: "wrap", children: [
3186
+ recordId ? topActions : null,
3187
+ /* @__PURE__ */ jsx16(IconButton, { "aria-label": "Fechar", title: "Fechar", size: "sm", minW: "32px", h: "32px", borderRadius: "full", variant: "ghost", color: "#475569", fontSize: "21px", fontWeight: "400", lineHeight: "1", onClick: requestClose, _hover: { bg: "#F1F5F9", color: "#0F172A" }, children: "\xD7" })
3188
+ ] })
3135
3189
  ] }),
3136
- /* @__PURE__ */ jsxs11(Flex7, { flex: "1", align: "center", justify: "flex-end", gap: "8px", wrap: "wrap", children: [
3137
- recordId ? topActions : null,
3138
- /* @__PURE__ */ jsx16(
3139
- IconButton,
3140
- {
3141
- "aria-label": "Fechar",
3142
- title: "Fechar",
3143
- size: "sm",
3144
- minW: "32px",
3145
- h: "32px",
3146
- borderRadius: "full",
3147
- variant: "ghost",
3148
- color: "#475569",
3149
- fontSize: "21px",
3150
- fontWeight: "400",
3151
- lineHeight: "1",
3152
- onClick: onClose,
3153
- _hover: { bg: "#F1F5F9", color: "#0F172A" },
3154
- children: "\xD7"
3190
+ /* @__PURE__ */ jsx16(Box9, { flex: "1", minH: 0, p: "20px", overflowY: "auto", children: schemaQuery.isLoading || parentQuery.isLoading ? /* @__PURE__ */ jsx16(Text10, { children: "Carregando..." }) : /* @__PURE__ */ jsx16(
3191
+ CoreTabbedDetail,
3192
+ {
3193
+ tabs: config.tabs,
3194
+ activeTab,
3195
+ onTabChange: setActiveTab,
3196
+ parent,
3197
+ formFields: resolvedFormFields,
3198
+ resource,
3199
+ relations,
3200
+ refreshTokens,
3201
+ onRefresh: refresh,
3202
+ formId,
3203
+ externalSubmit: true,
3204
+ onDirtyChange: setDirty,
3205
+ onSaveFailed: () => {
3206
+ closeAfterSaveRef.current = false;
3207
+ },
3208
+ onParentSaved: (saved) => {
3209
+ setParent(saved);
3210
+ setDirty(false);
3211
+ onSaved?.(saved);
3212
+ refresh(["parent"]);
3213
+ if (closeAfterSaveRef.current) {
3214
+ closeAfterSaveRef.current = false;
3215
+ onClose();
3216
+ }
3155
3217
  }
3156
- )
3218
+ }
3219
+ ) }),
3220
+ /* @__PURE__ */ jsxs11(Flex7, { flex: "0 0 auto", minH: "64px", px: "20px", py: "10px", align: "center", justify: "space-between", gap: "12px", bg: "white", borderTopWidth: "1px", borderColor: "#E5E9ED", boxShadow: "0 -8px 24px rgba(15,23,42,.06)", children: [
3221
+ /* @__PURE__ */ jsx16(Box9, { children: recordId ? bottomActions : null }),
3222
+ /* @__PURE__ */ jsx16(Button8, { type: "submit", form: formId, colorPalette: "brand", disabled: !hasFormTab || !activeIsForm && !dirty || schemaQuery.isLoading || parentQuery.isLoading, children: "Salvar" })
3157
3223
  ] })
3158
3224
  ] }),
3159
- /* @__PURE__ */ jsx16(Box9, { flex: "1", minH: 0, p: "20px", overflowY: "auto", children: schemaQuery.isLoading || parentQuery.isLoading ? /* @__PURE__ */ jsx16(Text10, { children: "Carregando..." }) : /* @__PURE__ */ jsx16(
3160
- CoreTabbedDetail,
3161
- {
3162
- tabs: config.tabs,
3163
- activeTab,
3164
- onTabChange: setActiveTab,
3165
- parent,
3166
- formFields: resolvedFormFields,
3167
- resource,
3168
- relations,
3169
- refreshTokens,
3170
- onRefresh: refresh,
3171
- formId,
3172
- externalSubmit: true,
3173
- onParentSaved: (saved) => {
3174
- setParent(saved);
3175
- onSaved?.(saved);
3176
- refresh(["parent"]);
3177
- }
3178
- }
3179
- ) }),
3180
- /* @__PURE__ */ jsxs11(
3181
- Flex7,
3182
- {
3183
- flex: "0 0 auto",
3184
- minH: "64px",
3185
- px: "20px",
3186
- py: "10px",
3187
- align: "center",
3188
- justify: "space-between",
3189
- gap: "12px",
3190
- bg: "white",
3191
- borderTopWidth: "1px",
3192
- borderColor: "#E5E9ED",
3193
- boxShadow: "0 -8px 24px rgba(15,23,42,.06)",
3194
- children: [
3195
- /* @__PURE__ */ jsx16(Box9, { children: recordId ? bottomActions : null }),
3196
- /* @__PURE__ */ jsx16(Button8, { type: "submit", form: formId, colorPalette: "brand", disabled: !activeIsForm || schemaQuery.isLoading || parentQuery.isLoading, children: "Salvar" })
3197
- ]
3198
- }
3199
- )
3200
- ] }) }) });
3225
+ confirmingClose ? /* @__PURE__ */ jsx16(Flex7, { position: "fixed", inset: 0, zIndex: 1100, align: "center", justify: "center", p: 4, bg: "rgba(15,23,42,.58)", children: /* @__PURE__ */ jsxs11(Box9, { role: "alertdialog", "aria-modal": "true", "aria-labelledby": "oon-unsaved-title", w: "min(520px, 94vw)", p: "22px", bg: "white", borderRadius: "12px", boxShadow: "0 24px 70px rgba(15,23,42,.28)", children: [
3226
+ /* @__PURE__ */ jsx16(Heading3, { id: "oon-unsaved-title", size: "md", children: "Altera\xE7\xF5es n\xE3o salvas" }),
3227
+ /* @__PURE__ */ jsx16(Text10, { mt: "8px", color: "#667085", fontSize: "13px", children: "As informa\xE7\xF5es preenchidas ainda n\xE3o foram salvas. Voc\xEA pode continuar editando, sair sem salvar ou salvar antes de fechar." }),
3228
+ /* @__PURE__ */ jsxs11(Flex7, { mt: "22px", justify: "flex-end", gap: "10px", wrap: "wrap", children: [
3229
+ /* @__PURE__ */ jsx16(Button8, { variant: "ghost", onClick: () => {
3230
+ closeAfterSaveRef.current = false;
3231
+ setConfirmingClose(false);
3232
+ }, children: "Continuar editando" }),
3233
+ /* @__PURE__ */ jsx16(Button8, { variant: "outline", colorPalette: "red", onClick: () => {
3234
+ closeAfterSaveRef.current = false;
3235
+ setConfirmingClose(false);
3236
+ onClose();
3237
+ }, children: "Sair sem salvar" }),
3238
+ /* @__PURE__ */ jsx16(Button8, { colorPalette: "brand", onClick: saveAndClose, children: "Salvar e sair" })
3239
+ ] })
3240
+ ] }) }) : null
3241
+ ] }) });
3201
3242
  }
3202
3243
 
3203
3244
  // src/components/CoreCollection.tsx
@@ -3341,7 +3382,7 @@ function CoreDocument({ model, label, endpoint, columns, form }) {
3341
3382
  }
3342
3383
 
3343
3384
  // src/components/CorePipeline.tsx
3344
- import { useEffect as useEffect7, useRef as useRef2, useState as useState11 } from "react";
3385
+ import { useEffect as useEffect7, useRef as useRef3, useState as useState11 } from "react";
3345
3386
  import { Badge as Badge4, Box as Box11, Button as Button9, Flex as Flex8, Heading as Heading5, Input as Input6, NativeSelect as NativeSelect5, Stack as Stack5, Text as Text12 } from "@chakra-ui/react";
3346
3387
  import { useMutation as useMutation3, useQuery as useQuery7, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
3347
3388
 
@@ -3585,7 +3626,7 @@ function CorePipeline({
3585
3626
  const [selectedTicket, setSelectedTicket] = useState11(null);
3586
3627
  const [creating, setCreating] = useState11(false);
3587
3628
  const [formError, setFormError] = useState11(null);
3588
- const boardRef = useRef2(null);
3629
+ const boardRef = useRef3(null);
3589
3630
  const [boardScrollState, setBoardScrollState] = useState11({ canScrollLeft: false, canScrollRight: false });
3590
3631
  const effectiveFilters = Object.fromEntries(
3591
3632
  Object.entries(filterValues).filter(([, value]) => value !== "" && value != null)