@oondemand/oon-core-front 0.3.49 → 0.3.51

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
 
@@ -2906,29 +2906,52 @@ function GroupSection({
2906
2906
  )) })
2907
2907
  ] });
2908
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
+ }
2909
2921
  function InlineParentForm({
2910
2922
  fields: rawFields,
2923
+ visibleFields: rawVisibleFields,
2911
2924
  groups,
2912
2925
  parent,
2913
2926
  resource,
2914
2927
  onSaved,
2928
+ onDirtyChange,
2929
+ onSaveFailed,
2915
2930
  formId,
2916
2931
  externalSubmit = false
2917
2932
  }) {
2918
2933
  const fields = rawFields;
2919
- const initial = initialReactiveValues2(fields, parent);
2934
+ const visibleFields = rawVisibleFields ?? rawFields;
2935
+ const initial = useMemo6(() => initialReactiveValues2(fields, parent), [rawFields, parent]);
2920
2936
  const [values, setValues] = useState8(initial.values);
2921
2937
  const [formulaErrors, setFormulaErrors] = useState8(initial.errors);
2922
2938
  const [saving, setSaving] = useState8(false);
2923
2939
  const [error, setError] = useState8(null);
2924
2940
  const [fieldErrors, setFieldErrors] = useState8({});
2925
2941
  useEffect5(() => {
2926
- const next = initialReactiveValues2(fields, parent);
2927
- setValues(next.values);
2928
- setFormulaErrors(next.errors);
2942
+ setValues(initial.values);
2943
+ setFormulaErrors(initial.errors);
2929
2944
  setError(null);
2930
2945
  setFieldErrors({});
2931
- }, [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]);
2932
2955
  const fieldsByName = useMemo6(() => new Map(fields.map((field) => [field.field, field])), [rawFields]);
2933
2956
  const change = (fieldName, value) => {
2934
2957
  const selected = fieldsByName.get(fieldName);
@@ -2970,7 +2993,7 @@ function InlineParentForm({
2970
2993
  `${group.label}-${group.fields.join("-")}`
2971
2994
  )) });
2972
2995
  }
2973
- 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(
2974
2997
  FieldControl,
2975
2998
  {
2976
2999
  field,
@@ -2994,6 +3017,7 @@ function InlineParentForm({
2994
3017
  if (Object.keys(validation).length) {
2995
3018
  setFieldErrors(validation);
2996
3019
  setError({ message: "Preencha os campos obrigat\xF3rios destacados." });
3020
+ onSaveFailed?.();
2997
3021
  return;
2998
3022
  }
2999
3023
  setSaving(true);
@@ -3002,10 +3026,12 @@ function InlineParentForm({
3002
3026
  try {
3003
3027
  const payload = buildDomainMutationPayload(fields, values);
3004
3028
  const saved = parent._id ? await resource.update(String(parent._id), payload) : await resource.create(payload);
3029
+ onDirtyChange?.(false);
3005
3030
  onSaved(saved);
3006
3031
  } catch (requestError) {
3007
3032
  setError(requestError);
3008
3033
  setFieldErrors(fieldErrorsOf(requestError));
3034
+ onSaveFailed?.();
3009
3035
  } finally {
3010
3036
  setSaving(false);
3011
3037
  }
@@ -3029,10 +3055,16 @@ function CoreTabbedDetail({
3029
3055
  refreshTokens,
3030
3056
  onRefresh,
3031
3057
  onParentSaved,
3058
+ onDirtyChange,
3059
+ onSaveFailed,
3032
3060
  formId,
3033
3061
  externalSubmit
3034
3062
  }) {
3035
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;
3036
3068
  return /* @__PURE__ */ jsxs10(Stack3, { gap: 4, children: [
3037
3069
  /* @__PURE__ */ jsx15(Flex6, { gap: 2, borderBottomWidth: "1px", borderColor: "#E5E9ED", overflowX: "auto", children: tabs.map((tab) => {
3038
3070
  const disabled = !parent?._id && (tab.type === "relatedGrid" || tab.type === "readonlyGrid" || tab.type === "summary");
@@ -3049,58 +3081,52 @@ function CoreTabbedDetail({
3049
3081
  tab.id
3050
3082
  );
3051
3083
  }) }),
3052
- /* @__PURE__ */ jsx15(Box8, { children: !active ? null : active.type === "summary" && parent ? /* @__PURE__ */ jsx15(CoreSummaryCards, { cards: active.cards, parent, relations }) : active.type === "form" ? /* @__PURE__ */ jsx15(
3053
- InlineParentForm,
3054
- {
3055
- fields: fieldsForTab(formFields, active.fields ?? active.groups?.flatMap((group) => group.fields)),
3056
- groups: active.groups,
3057
- parent: parent ?? {},
3058
- resource,
3059
- onSaved: onParentSaved,
3060
- formId,
3061
- externalSubmit
3062
- }
3063
- ) : (active.type === "relatedGrid" || active.type === "readonlyGrid") && parent ? /* @__PURE__ */ jsx15(
3064
- CoreRelatedGrid,
3065
- {
3066
- tab: active,
3067
- parent,
3068
- relations,
3069
- refreshToken: refreshTokens[active.id],
3070
- onRefresh
3071
- }
3072
- ) : active.type === "customComponent" ? /* @__PURE__ */ jsxs10(Text9, { color: "#667085", children: [
3073
- "Componente customizado: ",
3074
- active.component
3075
- ] }) : /* @__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
+ ] })
3076
3114
  ] });
3077
3115
  }
3078
3116
 
3079
3117
  // src/components/internal/CoreDetailModal.tsx
3080
3118
  import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
3081
- function CoreDetailModal({
3082
- model,
3083
- title,
3084
- config,
3085
- formFields,
3086
- recordId,
3087
- initialRecord,
3088
- initialTab,
3089
- relations,
3090
- topActions,
3091
- bottomActions,
3092
- onClose,
3093
- onSaved
3094
- }) {
3119
+ function CoreDetailModal({ model, title, config, formFields, recordId, initialRecord, initialTab, relations, topActions, bottomActions, onClose, onSaved }) {
3095
3120
  const schemaQuery = useModelSchema(model);
3096
3121
  const schema = schemaQuery.data;
3097
3122
  const resource = useOonResource(schema?.basePath ?? `/${model.toLowerCase()}s`);
3098
3123
  const queryClient = useQueryClient2();
3099
3124
  const [parent, setParent] = useState9(initialRecord);
3100
- const [activeTab, setActiveTab] = useState9(
3101
- resolveInitialDetailTab(config, recordId, initialTab)
3102
- );
3125
+ const [activeTab, setActiveTab] = useState9(resolveInitialDetailTab(config, recordId, initialTab));
3103
3126
  const [refreshTokens, setRefreshTokens] = useState9({});
3127
+ const [dirty, setDirty] = useState9(false);
3128
+ const [confirmingClose, setConfirmingClose] = useState9(false);
3129
+ const closeAfterSaveRef = useRef2(false);
3104
3130
  const parentQuery = useQuery6({
3105
3131
  queryKey: ["oon", "detail", model, recordId],
3106
3132
  enabled: !!recordId,
@@ -3136,78 +3162,83 @@ function CoreDetailModal({
3136
3162
  const width = config.size === "full" ? "98vw" : config.size === "md" ? "760px" : config.size === "lg" ? "980px" : "1180px";
3137
3163
  const displayTitle = parent?.[config.titleField ?? ""] ? String(parent[config.titleField ?? ""]) : title;
3138
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");
3139
3166
  const formId = `oon-detail-form-${model.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
3140
- 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: [
3141
- /* @__PURE__ */ jsxs11(Flex7, { minH: "72px", px: "20px", py: "10px", align: "center", justify: "space-between", gap: 4, borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
3142
- /* @__PURE__ */ jsxs11(Box9, { minW: "160px", children: [
3143
- /* @__PURE__ */ jsx16(Text10, { color: "#0474AF", fontSize: "10px", fontWeight: "700", textTransform: "uppercase", children: "Detalhe" }),
3144
- /* @__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
+ ] })
3145
3189
  ] }),
3146
- /* @__PURE__ */ jsxs11(Flex7, { flex: "1", align: "center", justify: "flex-end", gap: "8px", wrap: "wrap", children: [
3147
- recordId ? topActions : null,
3148
- /* @__PURE__ */ jsx16(
3149
- IconButton,
3150
- {
3151
- "aria-label": "Fechar",
3152
- title: "Fechar",
3153
- size: "sm",
3154
- minW: "32px",
3155
- h: "32px",
3156
- borderRadius: "full",
3157
- variant: "ghost",
3158
- color: "#475569",
3159
- fontSize: "21px",
3160
- fontWeight: "400",
3161
- lineHeight: "1",
3162
- onClick: onClose,
3163
- _hover: { bg: "#F1F5F9", color: "#0F172A" },
3164
- 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
+ }
3165
3217
  }
3166
- )
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" })
3167
3223
  ] })
3168
3224
  ] }),
3169
- /* @__PURE__ */ jsx16(Box9, { flex: "1", minH: 0, p: "20px", overflowY: "auto", children: schemaQuery.isLoading || parentQuery.isLoading ? /* @__PURE__ */ jsx16(Text10, { children: "Carregando..." }) : /* @__PURE__ */ jsx16(
3170
- CoreTabbedDetail,
3171
- {
3172
- tabs: config.tabs,
3173
- activeTab,
3174
- onTabChange: setActiveTab,
3175
- parent,
3176
- formFields: resolvedFormFields,
3177
- resource,
3178
- relations,
3179
- refreshTokens,
3180
- onRefresh: refresh,
3181
- formId,
3182
- externalSubmit: true,
3183
- onParentSaved: (saved) => {
3184
- setParent(saved);
3185
- onSaved?.(saved);
3186
- refresh(["parent"]);
3187
- }
3188
- }
3189
- ) }),
3190
- /* @__PURE__ */ jsxs11(
3191
- Flex7,
3192
- {
3193
- flex: "0 0 auto",
3194
- minH: "64px",
3195
- px: "20px",
3196
- py: "10px",
3197
- align: "center",
3198
- justify: "space-between",
3199
- gap: "12px",
3200
- bg: "white",
3201
- borderTopWidth: "1px",
3202
- borderColor: "#E5E9ED",
3203
- boxShadow: "0 -8px 24px rgba(15,23,42,.06)",
3204
- children: [
3205
- /* @__PURE__ */ jsx16(Box9, { children: recordId ? bottomActions : null }),
3206
- /* @__PURE__ */ jsx16(Button8, { type: "submit", form: formId, colorPalette: "brand", disabled: !activeIsForm || schemaQuery.isLoading || parentQuery.isLoading, children: "Salvar" })
3207
- ]
3208
- }
3209
- )
3210
- ] }) }) });
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
+ ] }) });
3211
3242
  }
3212
3243
 
3213
3244
  // src/components/CoreCollection.tsx
@@ -3351,7 +3382,7 @@ function CoreDocument({ model, label, endpoint, columns, form }) {
3351
3382
  }
3352
3383
 
3353
3384
  // src/components/CorePipeline.tsx
3354
- 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";
3355
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";
3356
3387
  import { useMutation as useMutation3, useQuery as useQuery7, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
3357
3388
 
@@ -3595,7 +3626,7 @@ function CorePipeline({
3595
3626
  const [selectedTicket, setSelectedTicket] = useState11(null);
3596
3627
  const [creating, setCreating] = useState11(false);
3597
3628
  const [formError, setFormError] = useState11(null);
3598
- const boardRef = useRef2(null);
3629
+ const boardRef = useRef3(null);
3599
3630
  const [boardScrollState, setBoardScrollState] = useState11({ canScrollLeft: false, canScrollRight: false });
3600
3631
  const effectiveFilters = Object.fromEntries(
3601
3632
  Object.entries(filterValues).filter(([, value]) => value !== "" && value != null)