@controleonline/ui-common 1.2.98 → 1.2.100

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.
@@ -24,7 +24,7 @@ import {
24
24
  parseConfigsObject,
25
25
  tt,
26
26
  } from './deviceListHelpers';
27
- import styles from '../../Devices.styles';
27
+ import legacyStyles from '../../Devices.styles';
28
28
 
29
29
  /**
30
30
  * Normalize DefaultTable card `item` into a device group shape.
@@ -65,6 +65,7 @@ const normalizeDeviceGroup = item => {
65
65
  export default function DeviceGroupCard({
66
66
  item,
67
67
  brandColors,
68
+ themedStyles = null,
68
69
  creatingPdv,
69
70
  goToDetail,
70
71
  handleCreateCurrentPdv,
@@ -74,6 +75,7 @@ export default function DeviceGroupCard({
74
75
  runtimeDeviceType,
75
76
  showCurrentPdvSetup,
76
77
  }) {
78
+ const styles = themedStyles || legacyStyles;
77
79
  const deviceGroup = normalizeDeviceGroup(item);
78
80
  if (!deviceGroup) {
79
81
  return null;
@@ -116,7 +118,7 @@ export default function DeviceGroupCard({
116
118
  deviceGroup.device?.id ||
117
119
  deviceGroup.key,
118
120
  );
119
- const accent = getDeviceTypeAccent(normalizedType);
121
+ const accent = brandColors.deviceAccent || getDeviceTypeAccent(normalizedType);
120
122
  const metaChips = [];
121
123
 
122
124
  if (isPdv && primaryConfig) {
@@ -196,7 +198,7 @@ export default function DeviceGroupCard({
196
198
  <View style={styles.deviceConfigRow}>
197
199
  {deviceConfigs.map(deviceConfig => {
198
200
  const configType = getDeviceConfigType(deviceConfig);
199
- const configAccent = getDeviceTypeAccent(configType);
201
+ const configAccent = brandColors.deviceAccent || getDeviceTypeAccent(configType);
200
202
  const isSessionConfig =
201
203
  isCurrentDevice &&
202
204
  runtimeDeviceType &&
@@ -297,5 +299,5 @@ export default function DeviceGroupCard({
297
299
  </View>
298
300
  </View>
299
301
  );
300
-
302
+
301
303
  }
@@ -36,7 +36,7 @@ import {
36
36
  prioritizeCurrentDeviceGroups,
37
37
  readStoredRuntimeDevice,
38
38
  } from '../currentDevice';
39
- import styles from '../../Devices.styles';
39
+ import legacyStyles, {createStyles} from '../../Devices.styles';
40
40
  import DeviceGroupCard from './DeviceGroupCard';
41
41
  import {useDeviceNetworkConnectivity} from './useDeviceNetworkConnectivity';
42
42
  import {
@@ -89,7 +89,7 @@ export const createDeviceTypeTab = ({
89
89
  clientFilter = null,
90
90
  offerCurrentPdvSetup = false,
91
91
  }) => {
92
- const DeviceTypeTab = () => {
92
+ const DeviceTypeTab = ({themePalette = null} = {}) => {
93
93
  const navigation = useNavigation();
94
94
  const peopleStore = useStore('people');
95
95
  const deviceStore = useStore('device');
@@ -101,13 +101,15 @@ export const createDeviceTypeTab = ({
101
101
 
102
102
  const brandColors = useMemo(
103
103
  () =>
104
- resolveThemePalette(
104
+ themePalette || resolveThemePalette(
105
105
  {...themeColors, ...(currentCompany?.theme?.colors || {})},
106
106
  colors,
107
107
  ),
108
- [themeColors, currentCompany?.id],
108
+ [themePalette, themeColors, currentCompany?.id],
109
109
  );
110
110
 
111
+ const styles = useMemo(() => themePalette ? createStyles(brandColors) : legacyStyles, [themePalette, brandColors]);
112
+
111
113
  const [deviceConfigs, setDeviceConfigs] = useState([]);
112
114
  const [loading, setLoading] = useState(false);
113
115
  const [refreshing, setRefreshing] = useState(false);
@@ -372,6 +374,7 @@ export const createDeviceTypeTab = ({
372
374
  <DeviceGroupCard
373
375
  {...cardProps}
374
376
  brandColors={brandColors}
377
+ themedStyles={themePalette ? styles : null}
375
378
  creatingPdv={creatingPdv}
376
379
  goToDetail={goToDetail}
377
380
  handleCreateCurrentPdv={handleCreateCurrentPdv}
@@ -384,6 +387,8 @@ export const createDeviceTypeTab = ({
384
387
  ),
385
388
  [
386
389
  brandColors,
390
+ styles,
391
+ themePalette,
387
392
  creatingPdv,
388
393
  goToDetail,
389
394
  handleCreateCurrentPdv,
@@ -1,183 +1,184 @@
1
- import React, {useEffect, useMemo, useState} from 'react';
2
- import {Text, TouchableOpacity, View} from 'react-native';
3
- import {SafeAreaView} from 'react-native-safe-area-context';
4
- import {useNavigation} from '@react-navigation/native';
5
- import {useStore} from '@store';
6
- import {resolveThemePalette} from '@controleonline/../../src/styles/branding';
7
- import {colors} from '@controleonline/../../src/styles/colors';
8
- import CompactFilterSelector from '@controleonline/ui-default/src/react/components/filters/CompactFilterSelector';
9
- import {PDV_DEVICE_TYPE} from '@controleonline/ui-common/src/react/utils/printerDevices';
10
- import Icon from 'react-native-vector-icons/Feather';
11
- import {
12
- ALL_DEVICE_FILTER,
13
- getDeviceCreationAction,
14
- getDeviceFilterAccent,
15
- getDeviceFilterDefinition,
16
- getDeviceFilterIcon,
17
- getDeviceFilterOptions,
18
- getPdvSubfilterOptions,
19
- hasRegisteredDeviceFilter,
20
- isPdvSubfilter,
21
- } from './Devices/deviceTypes';
22
- import { createStyles } from './Devices.styles';
23
-
24
- const tt = (type, key) => global.t?.t('configs', type, key);
25
-
26
- const Devices = () => {
27
- const navigation = useNavigation();
28
- const peopleStore = useStore('people');
29
- const themeStore = useStore('theme');
30
-
31
- const {currentCompany} = peopleStore.getters;
32
- const {colors: themeColors} = themeStore.getters;
33
-
34
- const [activeFilter, setActiveFilter] = useState(ALL_DEVICE_FILTER);
35
-
36
- const brandColors = useMemo(
37
- () =>
38
- resolveThemePalette(
39
- {...themeColors, ...(currentCompany?.theme?.colors || {})},
40
- colors,
41
- ),
42
- [themeColors, currentCompany?.id],
43
- );
44
- const styles = useMemo(() => createStyles(brandColors), [brandColors]);
45
- const accentPalette = useMemo(
46
- () => ({
47
- primary: brandColors.info,
48
- success: brandColors.success,
49
- danger: brandColors.error,
50
- warning: brandColors.warning,
51
- }),
52
- [brandColors.error, brandColors.info, brandColors.success, brandColors.warning],
53
- );
54
-
55
- const filterOptions = useMemo(() => getDeviceFilterOptions(), []);
56
- const pdvSubfilterOptions = useMemo(() => getPdvSubfilterOptions(), []);
57
- const activeFilterDefinition = useMemo(
58
- () => getDeviceFilterDefinition(activeFilter),
59
- [activeFilter],
60
- );
61
- const topLevelActiveFilter = useMemo(
62
- () => (isPdvSubfilter(activeFilter) ? PDV_DEVICE_TYPE : activeFilter),
63
- [activeFilter],
64
- );
65
- const activeFilterAction = useMemo(
66
- () => getDeviceCreationAction(topLevelActiveFilter),
67
- [topLevelActiveFilter],
68
- );
69
- const deviceTypeSelectorOptions = useMemo(
70
- () => filterOptions.map(option => ({
71
- key: option.key,
72
- label: option.label,
73
- })),
74
- [filterOptions],
75
- );
76
- const pdvGatewaySelectorOptions = useMemo(
77
- () => [
78
- {key: PDV_DEVICE_TYPE, label: tt('device_filter', 'all') || 'Todos'},
79
- ...pdvSubfilterOptions.map(option => ({
80
- key: option.key,
81
- label: option.label,
82
- })),
83
- ],
84
- [pdvSubfilterOptions],
85
- );
86
- const selectedDeviceTypeLabel = useMemo(
87
- () =>
88
- deviceTypeSelectorOptions.find(option => option.key === topLevelActiveFilter)?.label ||
89
- tt('device_filter', 'allTypes') ||
90
- 'Todos os tipos',
91
- [deviceTypeSelectorOptions, topLevelActiveFilter],
92
- );
93
- const selectedPdvGatewayLabel = useMemo(
94
- () =>
95
- pdvGatewaySelectorOptions.find(option => option.key === activeFilter)?.label ||
96
- tt('device_filter', 'all') ||
97
- 'Todos',
98
- [activeFilter, pdvGatewaySelectorOptions],
99
- );
100
- const ActiveTabComponent = activeFilterDefinition.TabComponent;
101
-
102
- useEffect(() => {
103
- if (!hasRegisteredDeviceFilter(activeFilter)) {
104
- setActiveFilter(ALL_DEVICE_FILTER);
105
- }
106
- }, [activeFilter]);
107
-
108
- return (
109
- <SafeAreaView
110
- style={[styles.container, {backgroundColor: brandColors.background}]}>
111
- <View style={styles.filtersBlock}>
112
- <Text style={styles.filtersLabel}>{tt('device_filter', 'filterByType') || 'Filtrar por tipo'}</Text>
113
- <CompactFilterSelector
114
- icon={getDeviceFilterIcon(topLevelActiveFilter)}
115
- label={selectedDeviceTypeLabel}
116
- title={tt('device_filter', 'filterByType') || 'Filtrar por tipo'}
117
- accentColor={getDeviceFilterAccent(topLevelActiveFilter, {
118
- brandColors,
119
- hex: accentPalette,
120
- })}
121
- active={topLevelActiveFilter !== ALL_DEVICE_FILTER}
122
- options={deviceTypeSelectorOptions}
123
- selectedKey={topLevelActiveFilter}
124
- onSelect={optionKey => {
125
- setActiveFilter(optionKey);
126
- return true;
127
- }}
128
- />
129
- </View>
130
-
131
- {topLevelActiveFilter === PDV_DEVICE_TYPE && pdvSubfilterOptions.length > 0 ? (
132
- <View style={styles.filtersBlock}>
133
- <Text style={styles.filtersLabel}>{tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}</Text>
134
- <CompactFilterSelector
135
- icon={getDeviceFilterIcon(activeFilter)}
136
- label={selectedPdvGatewayLabel}
137
- title={tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}
138
- accentColor={getDeviceFilterAccent(activeFilter, {
139
- brandColors,
140
- hex: accentPalette,
141
- })}
142
- active={activeFilter !== PDV_DEVICE_TYPE}
143
- options={pdvGatewaySelectorOptions}
144
- selectedKey={activeFilter}
145
- onSelect={optionKey => {
146
- setActiveFilter(optionKey);
147
- return true;
148
- }}
149
- />
150
- </View>
151
- ) : null}
152
-
153
- {activeFilterAction ? (
154
- <View style={styles.printerActionBlock}>
155
- <TouchableOpacity
156
- style={[
157
- styles.createPrinterBtn,
158
- {backgroundColor: brandColors.primary},
159
- ]}
160
- activeOpacity={0.86}
161
- onPress={() => navigation.navigate(activeFilterAction.routeName)}>
162
- <Icon name="plus-circle" size={16} color={brandColors.white} />
163
- <Text style={styles.createPrinterBtnText}>
164
- {activeFilterAction.label}
165
- </Text>
166
- </TouchableOpacity>
167
-
168
- <Text style={styles.helperText}>
169
- {activeFilterAction.helperText}
170
- </Text>
171
- </View>
172
- ) : null}
173
-
174
- {ActiveTabComponent ? (
175
- <ActiveTabComponent
176
- key={`${activeFilter}-${currentCompany?.id || 'company'}`}
177
- />
178
- ) : null}
179
- </SafeAreaView>
180
- );
181
- };
182
-
183
- export default Devices;
1
+ import React, {useEffect, useMemo, useState} from 'react';
2
+ import {Text, TouchableOpacity, View} from 'react-native';
3
+ import {SafeAreaView} from 'react-native-safe-area-context';
4
+ import {useNavigation} from '@react-navigation/native';
5
+ import {useStore} from '@store';
6
+ import {resolveThemePalette} from '@controleonline/../../src/styles/branding';
7
+ import {colors} from '@controleonline/../../src/styles/colors';
8
+ import CompactFilterSelector from '@controleonline/ui-default/src/react/components/filters/CompactFilterSelector';
9
+ import {PDV_DEVICE_TYPE} from '@controleonline/ui-common/src/react/utils/printerDevices';
10
+ import Icon from 'react-native-vector-icons/Feather';
11
+ import {
12
+ ALL_DEVICE_FILTER,
13
+ getDeviceCreationAction,
14
+ getDeviceFilterAccent,
15
+ getDeviceFilterDefinition,
16
+ getDeviceFilterIcon,
17
+ getDeviceFilterOptions,
18
+ getPdvSubfilterOptions,
19
+ hasRegisteredDeviceFilter,
20
+ isPdvSubfilter,
21
+ } from './Devices/deviceTypes';
22
+ import { createStyles } from './Devices.styles';
23
+
24
+ const tt = (type, key) => global.t?.t('configs', type, key);
25
+
26
+ const Devices = ({themePalette = null} = {}) => {
27
+ const navigation = useNavigation();
28
+ const peopleStore = useStore('people');
29
+ const themeStore = useStore('theme');
30
+
31
+ const {currentCompany} = peopleStore.getters;
32
+ const {colors: themeColors} = themeStore.getters;
33
+
34
+ const [activeFilter, setActiveFilter] = useState(ALL_DEVICE_FILTER);
35
+
36
+ const brandColors = useMemo(
37
+ () =>
38
+ themePalette || resolveThemePalette(
39
+ {...themeColors, ...(currentCompany?.theme?.colors || {})},
40
+ colors,
41
+ ),
42
+ [themePalette, themeColors, currentCompany?.id],
43
+ );
44
+ const styles = useMemo(() => createStyles(brandColors), [brandColors]);
45
+ const accentPalette = useMemo(
46
+ () => ({
47
+ primary: brandColors.info,
48
+ success: brandColors.success,
49
+ danger: brandColors.error,
50
+ warning: brandColors.warning,
51
+ }),
52
+ [brandColors.error, brandColors.info, brandColors.success, brandColors.warning],
53
+ );
54
+
55
+ const filterOptions = useMemo(() => getDeviceFilterOptions(), []);
56
+ const pdvSubfilterOptions = useMemo(() => getPdvSubfilterOptions(), []);
57
+ const activeFilterDefinition = useMemo(
58
+ () => getDeviceFilterDefinition(activeFilter),
59
+ [activeFilter],
60
+ );
61
+ const topLevelActiveFilter = useMemo(
62
+ () => (isPdvSubfilter(activeFilter) ? PDV_DEVICE_TYPE : activeFilter),
63
+ [activeFilter],
64
+ );
65
+ const activeFilterAction = useMemo(
66
+ () => getDeviceCreationAction(topLevelActiveFilter),
67
+ [topLevelActiveFilter],
68
+ );
69
+ const deviceTypeSelectorOptions = useMemo(
70
+ () => filterOptions.map(option => ({
71
+ key: option.key,
72
+ label: option.label,
73
+ })),
74
+ [filterOptions],
75
+ );
76
+ const pdvGatewaySelectorOptions = useMemo(
77
+ () => [
78
+ {key: PDV_DEVICE_TYPE, label: tt('device_filter', 'all') || 'Todos'},
79
+ ...pdvSubfilterOptions.map(option => ({
80
+ key: option.key,
81
+ label: option.label,
82
+ })),
83
+ ],
84
+ [pdvSubfilterOptions],
85
+ );
86
+ const selectedDeviceTypeLabel = useMemo(
87
+ () =>
88
+ deviceTypeSelectorOptions.find(option => option.key === topLevelActiveFilter)?.label ||
89
+ tt('device_filter', 'allTypes') ||
90
+ 'Todos os tipos',
91
+ [deviceTypeSelectorOptions, topLevelActiveFilter],
92
+ );
93
+ const selectedPdvGatewayLabel = useMemo(
94
+ () =>
95
+ pdvGatewaySelectorOptions.find(option => option.key === activeFilter)?.label ||
96
+ tt('device_filter', 'all') ||
97
+ 'Todos',
98
+ [activeFilter, pdvGatewaySelectorOptions],
99
+ );
100
+ const ActiveTabComponent = activeFilterDefinition.TabComponent;
101
+
102
+ useEffect(() => {
103
+ if (!hasRegisteredDeviceFilter(activeFilter)) {
104
+ setActiveFilter(ALL_DEVICE_FILTER);
105
+ }
106
+ }, [activeFilter]);
107
+
108
+ return (
109
+ <SafeAreaView
110
+ style={[styles.container, {backgroundColor: brandColors.background}]}>
111
+ <View style={styles.filtersBlock}>
112
+ <Text style={styles.filtersLabel}>{tt('device_filter', 'filterByType') || 'Filtrar por tipo'}</Text>
113
+ <CompactFilterSelector
114
+ icon={getDeviceFilterIcon(topLevelActiveFilter)}
115
+ label={selectedDeviceTypeLabel}
116
+ title={tt('device_filter', 'filterByType') || 'Filtrar por tipo'}
117
+ accentColor={getDeviceFilterAccent(topLevelActiveFilter, {
118
+ brandColors,
119
+ hex: accentPalette,
120
+ })}
121
+ active={topLevelActiveFilter !== ALL_DEVICE_FILTER}
122
+ options={deviceTypeSelectorOptions}
123
+ selectedKey={topLevelActiveFilter}
124
+ onSelect={optionKey => {
125
+ setActiveFilter(optionKey);
126
+ return true;
127
+ }}
128
+ />
129
+ </View>
130
+
131
+ {topLevelActiveFilter === PDV_DEVICE_TYPE && pdvSubfilterOptions.length > 0 ? (
132
+ <View style={styles.filtersBlock}>
133
+ <Text style={styles.filtersLabel}>{tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}</Text>
134
+ <CompactFilterSelector
135
+ icon={getDeviceFilterIcon(activeFilter)}
136
+ label={selectedPdvGatewayLabel}
137
+ title={tt('device_filter', 'pdvGateway') || 'Gateway do PDV'}
138
+ accentColor={getDeviceFilterAccent(activeFilter, {
139
+ brandColors,
140
+ hex: accentPalette,
141
+ })}
142
+ active={activeFilter !== PDV_DEVICE_TYPE}
143
+ options={pdvGatewaySelectorOptions}
144
+ selectedKey={activeFilter}
145
+ onSelect={optionKey => {
146
+ setActiveFilter(optionKey);
147
+ return true;
148
+ }}
149
+ />
150
+ </View>
151
+ ) : null}
152
+
153
+ {activeFilterAction ? (
154
+ <View style={styles.printerActionBlock}>
155
+ <TouchableOpacity
156
+ style={[
157
+ styles.createPrinterBtn,
158
+ {backgroundColor: brandColors.primary},
159
+ ]}
160
+ activeOpacity={0.86}
161
+ onPress={() => navigation.navigate(activeFilterAction.routeName)}>
162
+ <Icon name="plus-circle" size={16} color={brandColors.white} />
163
+ <Text style={styles.createPrinterBtnText}>
164
+ {activeFilterAction.label}
165
+ </Text>
166
+ </TouchableOpacity>
167
+
168
+ <Text style={styles.helperText}>
169
+ {activeFilterAction.helperText}
170
+ </Text>
171
+ </View>
172
+ ) : null}
173
+
174
+ {ActiveTabComponent ? (
175
+ <ActiveTabComponent
176
+ themePalette={themePalette}
177
+ key={`${activeFilter}-${currentCompany?.id || 'company'}`}
178
+ />
179
+ ) : null}
180
+ </SafeAreaView>
181
+ );
182
+ };
183
+
184
+ export default Devices;
@@ -14,6 +14,7 @@ import { fetchPeopleConfigs } from './fetchPeopleConfigs';
14
14
  import IntegrationConfigFields from './IntegrationConfigFields';
15
15
  import {
16
16
  ROUTE_PROVIDER_MAP,
17
+ expandBundledConfig,
17
18
  extractAuthorizationUrl,
18
19
  formatApiError,
19
20
  formatUberOAuthError,
@@ -112,6 +113,8 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
112
113
  const allFieldKeysSig = useMemo(() => allFieldKeys.join('|'), [allFieldKeys]);
113
114
  // Pure config providers (e.g. receita-federal) do not use marketplace/integrations.
114
115
  const needsMarketplaceIntegration = Boolean(providerConfig?.oauthConnect);
116
+ const bundleKey = normalizeTextValue(providerConfig?.bundleKey);
117
+ const configVisibility = normalizeTextValue(providerConfig?.visibility) || 'public';
115
118
 
116
119
  useEffect(() => {
117
120
  setConfigValues({});
@@ -135,14 +138,28 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
135
138
  }
136
139
  if (showLoading) setLoading(true);
137
140
  try {
138
- const keys = allFieldKeys.length
139
- ? allFieldKeys
140
- : visibleFields.map(field => field.key).filter(Boolean);
141
-
142
- if (keys.length) {
143
- const configItems = await fetchPeopleConfigs({ peopleIri: providerIri, configKeys: keys });
141
+ if (bundleKey) {
142
+ const configItems = await fetchPeopleConfigs({
143
+ peopleIri: providerIri,
144
+ configKeys: [bundleKey],
145
+ });
146
+ const bundled = configItems.find(
147
+ item => String(item?.configKey || '').trim() === bundleKey,
148
+ );
144
149
  const mergeFields = allConfigFields.length ? allConfigFields : visibleFields;
145
- setConfigValues(current => mergeTabValues(mergeFields, configItems, current));
150
+ setConfigValues(current =>
151
+ expandBundledConfig(mergeFields, bundled?.configValue, current),
152
+ );
153
+ } else {
154
+ const keys = allFieldKeys.length
155
+ ? allFieldKeys
156
+ : visibleFields.map(field => field.key).filter(Boolean);
157
+
158
+ if (keys.length) {
159
+ const configItems = await fetchPeopleConfigs({ peopleIri: providerIri, configKeys: keys });
160
+ const mergeFields = allConfigFields.length ? allConfigFields : visibleFields;
161
+ setConfigValues(current => mergeTabValues(mergeFields, configItems, current));
162
+ }
146
163
  }
147
164
 
148
165
  if (needsMarketplaceIntegration) {
@@ -170,6 +187,7 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
170
187
  allConfigFields,
171
188
  allFieldKeys,
172
189
  allFieldKeysSig,
190
+ bundleKey,
173
191
  needsMarketplaceIntegration,
174
192
  providerConfig,
175
193
  providerIri,
@@ -246,16 +264,44 @@ export default function IntegrationConfigPage({ route, embedded = false, fiscalT
246
264
  showError('Nao foi possivel identificar a integracao selecionada.');
247
265
  return;
248
266
  }
249
- const configs = visibleFields.map(field => ({
250
- configKey: field.key,
251
- configValue: toConfigRequestValue(normalizeTextValue(configValues[field.key])),
252
- }));
267
+
268
+ let configs;
269
+ if (bundleKey) {
270
+ const payloadObject = {};
271
+ (visibleFields || []).forEach(field => {
272
+ payloadObject[field.key] = normalizeTextValue(configValues[field.key]);
273
+ });
274
+ configs = [
275
+ {
276
+ configKey: bundleKey,
277
+ configValue: toConfigRequestValue(payloadObject),
278
+ },
279
+ ];
280
+ } else {
281
+ configs = visibleFields.map(field => ({
282
+ configKey: field.key,
283
+ configValue: toConfigRequestValue(normalizeTextValue(configValues[field.key])),
284
+ }));
285
+ }
286
+
253
287
  try {
254
- await persistConfigs({ configs, people: providerIri, module: 4, visibility: 'public' });
288
+ await persistConfigs({ configs, people: providerIri, module: 4, visibility: configVisibility });
255
289
  showSuccess(`${activeTabDef?.label || providerConfig.label} salvo com sucesso.`);
256
290
  await loadPageData({ showLoading: false });
257
291
  } catch (error) { showError(error?.message || 'Nao foi possivel salvar a integracao.'); }
258
- }, [activeTabDef, configValues, loadPageData, persistConfigs, providerConfig, providerIri, showError, showSuccess, visibleFields]);
292
+ }, [
293
+ activeTabDef,
294
+ bundleKey,
295
+ configValues,
296
+ configVisibility,
297
+ loadPageData,
298
+ persistConfigs,
299
+ providerConfig,
300
+ providerIri,
301
+ showError,
302
+ showSuccess,
303
+ visibleFields,
304
+ ]);
259
305
 
260
306
  if (!providerConfig) return <CenterState icon="alert-triangle" iconColor="#e67e22" title="Integracao indisponivel" text="A tela solicitada nao possui configuracao cadastrada." />;
261
307
  if (!providerId) return <CenterState icon="building" iconColor="#94A3B8" title="Empresa nao identificada" text="Abra a ficha da empresa para gravar as configuracoes fiscais nela." />;
@@ -5,6 +5,7 @@ export const ROUTE_PROVIDER_MAP = {
5
5
  AsaasIntegrationPage: 'asaas',
6
6
  ClickSignIntegrationPage: 'clicksign',
7
7
  ReceitaFederalIntegrationPage: 'receita-federal',
8
+ SemPararIntegrationPage: 'sem-parar',
8
9
  };
9
10
 
10
11
  export const routeNameToPath = routeName =>
@@ -53,6 +54,22 @@ export const mergeTabValues = (fields, source, current = {}) => {
53
54
  return next;
54
55
  };
55
56
 
57
+ /** Expand a single JSON config (bundleKey) into flat field values. */
58
+ export const expandBundledConfig = (fields, rawValue, current = {}) => {
59
+ let parsed = rawValue;
60
+ if (typeof rawValue === 'string') {
61
+ try {
62
+ parsed = JSON.parse(rawValue);
63
+ } catch {
64
+ parsed = {};
65
+ }
66
+ }
67
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
68
+ parsed = {};
69
+ }
70
+ return mergeTabValues(fields, parsed, current);
71
+ };
72
+
56
73
  export const buildFieldValues = (providerConfig, source) =>
57
74
  mergeTabValues(getConfigFields(providerConfig), source, {});
58
75