@controleonline/ui-default 1.0.263 → 1.0.264

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.
Files changed (29) hide show
  1. package/AGENTS.md +48 -47
  2. package/package.json +3 -6
  3. package/src/react/components/errors/DefaultErrors.js +360 -360
  4. package/src/react/components/files/DefaultFile.js +46 -46
  5. package/src/react/components/filters/CompactFilterSelector.js +22 -3
  6. package/src/react/components/filters/DefaultColumnFilter.js +15 -13
  7. package/src/react/components/filters/DefaultExternalFilters.js +44 -39
  8. package/src/react/components/filters/DefaultSearch.js +140 -103
  9. package/src/react/components/filters/DefaultSearch.styles.js +65 -70
  10. package/src/react/components/form/DefaultForm.js +200 -198
  11. package/src/react/components/help/DefaultTooltip.js +87 -87
  12. package/src/react/components/help/DefaultTooltip.styles.js +61 -61
  13. package/src/react/components/inputs/DefaultInput.js +162 -160
  14. package/src/react/components/inputs/DefaultSelect.js +203 -192
  15. package/src/react/components/map/DefaultGoogleMap.styles.js +115 -9
  16. package/src/react/components/map/DefaultGoogleMap.web.js +560 -446
  17. package/src/react/components/map/DefaultMap.native.js +73 -71
  18. package/src/react/components/map/DefaultMap.styles.js +16 -16
  19. package/src/react/components/map/DefaultMap.web.js +68 -66
  20. package/src/react/components/map/DefaultNativeMap.native.js +307 -183
  21. package/src/react/components/map/DefaultNativeMap.shared.js +439 -340
  22. package/src/react/components/map/DefaultNativeMap.styles.js +156 -122
  23. package/src/react/components/table/DefaultTable.js +529 -355
  24. package/src/react/components/table/DefaultTable.styles.js +30 -39
  25. package/src/react/index.js +10 -10
  26. package/src/tests/react/components/errors/DefaultErrors.test.js +162 -162
  27. package/src/tests/react/components/files/DefaultFile.test.js +80 -80
  28. package/src/tests/react/components/inputs/DefaultSelect.test.js +93 -0
  29. package/src/tests/react/components/table/DefaultTable.test.js +334 -0
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-unused-vars */
1
2
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
3
  import {
3
4
  FlatList,
@@ -16,7 +17,7 @@ import Formatter from '@controleonline/ui-common/src/utils/formatter.js';
16
17
  import StateStore from '@controleonline/ui-layout/src/react/components/StateStore';
17
18
  import { getDateRange } from '@controleonline/ui-common/src/react/utils/dateRangeFilter';
18
19
  import { formatStoreColumnLabel } from '@controleonline/ui-common/src/react/utils/storeColumns';
19
- import { resolveThemePalette, withOpacity } from '@controleonline/../../src/styles/branding';
20
+ import { resolveThemePalette } from '@controleonline/../../src/styles/branding';
20
21
  import { colors } from '@controleonline/../../src/styles/colors';
21
22
  import DefaultColumnFilter from '../filters/DefaultColumnFilter';
22
23
  import DefaultSearch from '../filters/DefaultSearch';
@@ -73,20 +74,29 @@ const resolveListLoadParams = ({currentCompanyId, listStoreName}) => {
73
74
  return {};
74
75
  }
75
76
 
76
- return {people: currentCompanyId};
77
+ return listStoreName === 'categories'
78
+ ? {company: currentCompanyId}
79
+ : {people: currentCompanyId};
77
80
  };
78
81
 
79
- const resolveColumnListLoadParams = ({column, currentCompanyId}) => {
82
+ export const resolveColumnListLoadParams = ({
83
+ column,
84
+ currentCompanyId,
85
+ requestParams = {},
86
+ }) => {
80
87
  const listStoreName = resolveStoreNameFromList(column?.list);
81
88
  const companyScopedParams = resolveListLoadParams({
82
89
  currentCompanyId,
83
90
  listStoreName,
84
91
  });
92
+ const resolvedCustomParams = typeof column?.listRequestParams === 'function'
93
+ ? column.listRequestParams({currentCompanyId, requestParams})
94
+ : column?.listRequestParams;
85
95
  const customParams =
86
- column?.listRequestParams &&
87
- typeof column.listRequestParams === 'object' &&
88
- !Array.isArray(column.listRequestParams)
89
- ? column.listRequestParams
96
+ resolvedCustomParams &&
97
+ typeof resolvedCustomParams === 'object' &&
98
+ !Array.isArray(resolvedCustomParams)
99
+ ? resolvedCustomParams
90
100
  : {};
91
101
 
92
102
  return {
@@ -492,33 +502,35 @@ const DefaultTable = ({
492
502
  searchProps = null,
493
503
  toolbarActions = [],
494
504
  showColumnFiltersButton = true,
495
- showTotalItemsInFooter = true,
496
- showTotalItemsInCompactToolbar = false,
497
- showRowActions = true,
498
- sort = null,
499
- storeName = '',
500
- summary = null,
501
- summaryLabels = null,
502
- totalItems = null,
503
- totalItemsLabel = null,
504
- totalItemsText = null,
505
- visibleColumnsPreferenceKey = '',
505
+ showTotalItemsInFooter = true,
506
+ showTotalItemsInCompactToolbar = false,
507
+ showRowActions = true,
508
+ rowStyle = null,
509
+ sort = null,
510
+ storeName = '',
511
+ summary = null,
512
+ summaryLabels = null,
513
+ totalItems = null,
514
+ totalItemsLabel = null,
515
+ totalItemsText = null,
516
+ footerComponent = null,
517
+ visibleColumnsPreferenceKey = '',
506
518
  }) => {
507
519
  const { width } = useWindowDimensions();
508
520
  const store = useStore(storeName);
521
+ const visibleColumnsStorageKey = visibleColumnsPreferenceKey || storeName;
509
522
  const peopleStore = useStore('people');
510
523
  const themeStore = useStore('theme');
511
524
  const [editingCell, setEditingCell] = useState(null);
512
- const [editingRow, setEditingRow] = useState(null);
525
+ const [editingRow, setEditingRow] = useState(null);
513
526
  const [formMode, setFormMode] = useState('edit');
514
527
  const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
515
- const [, setListOptionsVersion] = useState(0);
528
+ const [listOptionsByColumn, setListOptionsByColumn] = useState({});
516
529
  const [savingCell, setSavingCell] = useState(null);
517
530
  const [showColumnFilters, setShowColumnFilters] = useState(false);
518
531
  const [tableContainerWidth, setTableContainerWidth] = useState(0);
519
532
  const endReachedLockRef = useRef(false);
520
533
  const loadedListStoresRef = useRef(new Set());
521
- const listOptionsWasFocusedRef = useRef(false);
522
534
  const previousPaginationStateRef = useRef({
523
535
  dataLength: Array.isArray(data) ? data.length : 0,
524
536
  filtersKey: JSON.stringify(filters || {}),
@@ -532,10 +544,10 @@ const DefaultTable = ({
532
544
  ? store.getters.columns
533
545
  : columns,
534
546
  visibleColumns: resolveStoredVisibleColumnsPreference(
535
- visibleColumnsPreferenceKey,
547
+ visibleColumnsStorageKey,
536
548
  ),
537
549
  }),
538
- [columns, store?.getters?.columns, visibleColumnsPreferenceKey],
550
+ [columns, store?.getters?.columns, visibleColumnsStorageKey],
539
551
  );
540
552
  const viewModeSeed = useMemo(
541
553
  () =>
@@ -544,11 +556,12 @@ const DefaultTable = ({
544
556
  initialViewMode,
545
557
  ),
546
558
  [initialViewMode, visibleColumnsPreferenceKey],
547
- );
548
- const [visibleColumns, setVisibleColumns] = useState(() => visibleColumnsSeed);
549
- const [viewMode, setViewMode] = useState(() => viewModeSeed);
550
- const { currentCompany } = peopleStore.getters || {};
551
- const { colors: themeColors } = themeStore.getters || {};
559
+ );
560
+ const [visibleColumns, setVisibleColumns] = useState(() => visibleColumnsSeed);
561
+ const [viewMode, setViewMode] = useState(() => viewModeSeed);
562
+ const [compactViewMode, setCompactViewMode] = useState(null);
563
+ const { currentCompany } = peopleStore.getters || {};
564
+ const { colors: themeColors } = themeStore.getters;
552
565
  const themeTokens = useMemo(
553
566
  () => ({...themeColors, ...(currentCompany?.theme?.colors || {})}),
554
567
  [currentCompany?.theme?.colors, themeColors],
@@ -565,22 +578,47 @@ const DefaultTable = ({
565
578
  const tableSurfaceColor = palette.background;
566
579
  const tableTextColor = palette.text;
567
580
  const tableMutedColor = palette.textSecondary;
568
- const tableOnAccentColor = palette.secondary || palette.text;
581
+ const panelBackgroundColor = themeColors.panelBackground;
582
+ const panelBorderColor = themeColors.panelBorder;
583
+ const modalBackgroundColor = themeColors.modalBackground;
584
+ const modalBorderColor = themeColors.modalBorder;
585
+ const modalCloseIconColor = themeColors.modalCloseIcon;
586
+ const modalHeaderTextColor = themeColors.modalHeaderText;
587
+ const modalOverlayColor = themeColors.modalOverlay;
588
+ const modalTextColor = themeColors.modalText;
589
+ const checkboxBorderColor = themeColors.checkboxBorder;
590
+ const checkboxSelectedMarkColor = themeColors.checkboxSelectedMark;
591
+ const toolbarBackgroundColor = themeColors.toolbarBackground;
592
+ const toolbarBorderColor = themeColors.toolbarBorder;
593
+ const toolbarCountBackgroundColor = themeColors.badgeBackground;
594
+ const toolbarCountTextColor = themeColors.badgeText;
595
+ const tableActionBackgroundColor = themeColors.tableActionBackground;
596
+ const tableActionBorderColor = themeColors.tableActionBorder;
597
+ const tableActionTextColor = themeColors.tableActionIcon;
598
+ const tableButtonBackgroundColor = themeColors.buttonBackground;
599
+ const tableButtonBorderColor = themeColors.buttonBorder;
600
+ const tableButtonIconColor = themeColors.buttonIcon;
601
+ const tableButtonPressedBackgroundColor = themeColors.buttonPressedBackground;
602
+ const tableButtonPressedBorderColor = themeColors.buttonPressedBorder;
603
+ const tableButtonPressedIconColor = themeColors.buttonPressedIcon;
604
+ const tableButtonTextColor = themeColors.buttonText;
605
+ const tableFilterBackgroundColor = themeColors.tableFilterBackground;
606
+ const tableFilterBorderColor = themeColors.tableFilterBorder;
607
+ const tableFilterTextColor = themeColors.tableFilterText;
569
608
  const isFocused = useIsFocused();
570
609
  const {showError} = useMessage() || {};
571
610
  const autoMode = data === undefined && normalizeText(storeName) !== '';
572
611
  const searchKey = searchProps?.searchKey || 'search';
573
612
  const storeActions = store?.actions || {};
613
+ const resolvedActions = useMemo(
614
+ () => ({
615
+ ...storeActions,
616
+ ...(actions || {}),
617
+ }),
618
+ [actions, storeActions],
619
+ );
574
620
  const storeColumns = Array.isArray(store?.getters?.columns) ? store.getters.columns : [];
575
621
  const columnsForTable = storeColumns.length > 0 ? storeColumns : columns;
576
- const listColumnsSignature = useMemo(
577
- () =>
578
- columnsForTable
579
- .filter(column => normalizeText(column?.list))
580
- .map(column => `${getColumnKey(column)}:${normalizeText(column.list)}`)
581
- .join('|'),
582
- [columnsForTable],
583
- );
584
622
  const storeFilters = isObject(store?.getters?.filters) ? store.getters.filters : {};
585
623
  const requestParamsSeed = isObject(requestParams) ? requestParams : {};
586
624
  const initialFiltersSeed = autoMode
@@ -613,80 +651,119 @@ const DefaultTable = ({
613
651
  const resolvedSort = autoMode ? autoSort : sort;
614
652
  const resolvedFilters = autoMode ? autoFilters : (filters || {});
615
653
  const resolvedSearchValue = normalizeText(resolvedFilters?.[searchKey]);
654
+ const resolvedGetOptionsForColumn = useCallback(
655
+ column => {
656
+ const explicitOptions = getOptionsForColumn?.(column);
657
+ if (Array.isArray(explicitOptions)) return explicitOptions;
658
+
659
+ const columnKey = getColumnKey(column);
660
+ return Array.isArray(listOptionsByColumn[columnKey])
661
+ ? listOptionsByColumn[columnKey]
662
+ : undefined;
663
+ },
664
+ [getOptionsForColumn, listOptionsByColumn],
665
+ );
616
666
 
617
667
  useEffect(() => {
618
- const shouldRefreshCompanyScopedLists =
619
- isFocused && !listOptionsWasFocusedRef.current;
668
+ setListOptionsByColumn({});
669
+ loadedListStoresRef.current.clear();
670
+ }, [currentCompany?.id]);
671
+
672
+ const loadListOptionsForColumns = useCallback(
673
+ (targetColumns = []) => {
674
+ const columnsToLoad = (Array.isArray(targetColumns) ? targetColumns : []).filter(
675
+ column => normalizeText(column?.list),
676
+ );
677
+
678
+ if (columnsToLoad.length === 0) {
679
+ return Promise.resolve([]);
680
+ }
620
681
 
621
- listOptionsWasFocusedRef.current = isFocused;
682
+ const stores = getAllStores?.() || {};
683
+ const loadPromises = [];
622
684
 
623
- if (!isFocused || !listColumnsSignature) return;
685
+ columnsToLoad.forEach(column => {
686
+ const explicitOptions = getOptionsForColumn?.(column);
687
+ if (Array.isArray(explicitOptions)) {
688
+ return;
689
+ }
624
690
 
625
- const stores = getAllStores?.() || {};
626
- const loadPromises = [];
691
+ const listStoreName = resolveStoreNameFromList(column.list);
692
+ const actionName = resolveListActionName(column.list);
693
+ const listStore = stores?.[listStoreName];
694
+ const listAction = listStore?.actions?.[actionName];
627
695
 
628
- columnsForTable.forEach(column => {
629
- if (!column?.list) return;
696
+ const listLoadParams = resolveColumnListLoadParams({
697
+ column,
698
+ currentCompanyId: currentCompany?.id,
699
+ requestParams: requestParamsSeed,
700
+ });
701
+ const isCompanyScopedList = Object.keys(listLoadParams).length > 0;
630
702
 
631
- const explicitOptions = getOptionsForColumn?.(column);
632
- if (Array.isArray(explicitOptions) && explicitOptions.length > 0) return;
703
+ if (!listStoreName || typeof listAction !== 'function') {
704
+ return;
705
+ }
633
706
 
634
- const listStoreName = resolveStoreNameFromList(column.list);
635
- const actionName = resolveListActionName(column.list);
636
- const listStore = stores?.[listStoreName];
637
- const listAction = listStore?.actions?.[actionName];
707
+ if (
708
+ !isCompanyScopedList &&
709
+ Array.isArray(listStore?.getters?.items) &&
710
+ listStore.getters.items.length > 0
711
+ ) {
712
+ const columnKey = getColumnKey(column);
713
+ setListOptionsByColumn(current => ({
714
+ ...current,
715
+ [columnKey]: normalizeCollectionItems(listStore.getters.items),
716
+ }));
717
+ return;
718
+ }
638
719
 
639
- const listLoadParams = resolveColumnListLoadParams({
640
- column,
641
- currentCompanyId: currentCompany?.id,
720
+ const columnKey = getColumnKey(column);
721
+ const loadKey = `${columnKey}:${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
722
+ if (loadedListStoresRef.current.has(loadKey)) {
723
+ return;
724
+ }
725
+
726
+ loadedListStoresRef.current.add(loadKey);
727
+ loadPromises.push(
728
+ Promise.resolve(
729
+ listAction({
730
+ ...listLoadParams,
731
+ itemsPerPage: LIST_OPTIONS_PAGE_SIZE,
732
+ __storeMeta: {
733
+ dedupeKey: `default-table-list-options:${loadKey}`,
734
+ skipSystemError: true,
735
+ },
736
+ }),
737
+ )
738
+ .then(response => ({columnKey, items: normalizeCollectionItems(response)}))
739
+ .catch(() => {
740
+ loadedListStoresRef.current.delete(loadKey);
741
+ return {columnKey, items: []};
742
+ }),
743
+ );
642
744
  });
643
- const isCompanyScopedList = Object.keys(listLoadParams).length > 0;
644
-
645
- if (!listStoreName || typeof listAction !== 'function') return;
646
- if (
647
- !isCompanyScopedList &&
648
- Array.isArray(listStore?.getters?.items) &&
649
- listStore.getters.items.length > 0
650
- ) {
651
- return;
652
- }
653
745
 
654
- const loadKey = `${listStoreName}:${actionName}:${stableSerialize(listLoadParams)}`;
655
- if (shouldRefreshCompanyScopedLists && isCompanyScopedList) {
656
- loadedListStoresRef.current.delete(loadKey);
746
+ if (loadPromises.length === 0) {
747
+ return Promise.resolve([]);
657
748
  }
658
- if (loadedListStoresRef.current.has(loadKey)) return;
659
-
660
- loadedListStoresRef.current.add(loadKey);
661
- loadPromises.push(
662
- Promise.resolve(
663
- listAction({
664
- ...listLoadParams,
665
- itemsPerPage: LIST_OPTIONS_PAGE_SIZE,
666
- __storeMeta: {
667
- dedupeKey: `default-table-list-options:${loadKey}`,
668
- skipSystemError: true,
669
- },
670
- }),
671
- ).catch(() => {
672
- loadedListStoresRef.current.delete(loadKey);
673
- }),
674
- );
675
- });
676
749
 
677
- if (loadPromises.length === 0) return;
750
+ return Promise.all(loadPromises).then(results => {
751
+ const nextOptionsByColumn = Object.fromEntries(
752
+ results.map(result => [result.columnKey, result.items]),
753
+ );
678
754
 
679
- let cancelled = false;
680
- Promise.allSettled(loadPromises).then(() => {
681
- if (!cancelled) {
682
- setListOptionsVersion(version => version + 1);
683
- }
684
- });
755
+ if (Object.keys(nextOptionsByColumn).length > 0) {
756
+ setListOptionsByColumn(current => ({
757
+ ...current,
758
+ ...nextOptionsByColumn,
759
+ }));
760
+ }
685
761
 
686
- return () => {
687
- cancelled = true;
688
- };
689
- }, [columnsForTable, currentCompany?.id, getOptionsForColumn, isFocused, listColumnsSignature]);
762
+ return results;
763
+ });
764
+ },
765
+ [currentCompany?.id, getOptionsForColumn, requestParamsSeed],
766
+ );
690
767
 
691
768
  const buildRequestQuery = useCallback(
692
769
  (page, append = false) => {
@@ -762,7 +839,7 @@ const DefaultTable = ({
762
839
 
763
840
  const loadAutoPage = useCallback(
764
841
  (page, { append = false } = {}) => {
765
- if (!autoMode || typeof storeActions.getItems !== 'function') {
842
+ if (!autoMode || typeof resolvedActions.getItems !== 'function') {
766
843
  return Promise.resolve([]);
767
844
  }
768
845
 
@@ -780,7 +857,7 @@ const DefaultTable = ({
780
857
 
781
858
  const query = buildRequestQuery(page, append);
782
859
 
783
- return Promise.resolve(storeActions.getItems(query))
860
+ return Promise.resolve(resolvedActions.getItems(query))
784
861
  .then(response => {
785
862
  if (autoRequestIdRef.current !== requestId) {
786
863
  return response;
@@ -810,7 +887,7 @@ const DefaultTable = ({
810
887
  }
811
888
  });
812
889
  },
813
- [autoMode, autoQuerySignature, buildRequestQuery, showError, storeActions],
890
+ [autoMode, autoQuerySignature, buildRequestQuery, resolvedActions, showError],
814
891
  );
815
892
 
816
893
  const availableColumns = useMemo(
@@ -854,7 +931,7 @@ const DefaultTable = ({
854
931
 
855
932
  useEffect(() => {
856
933
  if (!autoMode || !isFocused) return;
857
- if (typeof storeActions.getItems !== 'function') return;
934
+ if (typeof resolvedActions.getItems !== 'function') return;
858
935
  if (
859
936
  autoLoading ||
860
937
  autoLoadingMore ||
@@ -873,7 +950,7 @@ const DefaultTable = ({
873
950
  autoQuerySignature,
874
951
  isFocused,
875
952
  loadAutoPage,
876
- storeActions,
953
+ resolvedActions,
877
954
  ]);
878
955
 
879
956
  const tableColumns = useMemo(
@@ -898,7 +975,7 @@ const DefaultTable = ({
898
975
  const hasAddInstruction = addConfig === true || (isObject(addConfig) && addConfig.enabled !== false);
899
976
  const shouldRenderAddButton =
900
977
  hasAddInstruction &&
901
- (typeof onAdd === 'function' || typeof actions.save === 'function');
978
+ (typeof onAdd === 'function' || typeof resolvedActions.save === 'function');
902
979
  const resolvedIsLoading = autoMode ? (autoLoading || autoLoadingMore) : Boolean(isLoading);
903
980
  const resolvedData = autoMode
904
981
  ? (autoHasLoaded && Array.isArray(store?.getters?.items) ? store.getters.items : [])
@@ -912,13 +989,29 @@ const DefaultTable = ({
912
989
  [hasRowActions, tableColumns],
913
990
  );
914
991
  const tableWidth = Math.max(tableContainerWidth, tableMinimumWidth);
915
- const tableLayoutStyle = useMemo(
916
- () => (tableWidth > 0 ? { minWidth: tableWidth, width: tableWidth } : null),
917
- [tableWidth],
918
- );
992
+ const tableLayoutStyle = useMemo(
993
+ () => (tableWidth > 0 ? { minWidth: tableWidth, width: tableWidth } : null),
994
+ [tableWidth],
995
+ );
919
996
  const isCompactView = width > 0 && width <= compactBreakpoint;
920
997
  const shouldForceCardsOnCompact = forceCardsOnCompact !== false;
921
- const effectiveViewMode = isCompactView && shouldForceCardsOnCompact ? 'cards' : viewMode;
998
+ const effectiveViewMode =
999
+ isCompactView && shouldForceCardsOnCompact
1000
+ ? (compactViewMode || 'cards')
1001
+ : viewMode;
1002
+ useEffect(() => {
1003
+ if (!shouldForceCardsOnCompact) {
1004
+ setCompactViewMode(null);
1005
+ return;
1006
+ }
1007
+
1008
+ if (isCompactView) {
1009
+ setCompactViewMode('cards');
1010
+ return;
1011
+ }
1012
+
1013
+ setCompactViewMode(null);
1014
+ }, [isCompactView, shouldForceCardsOnCompact]);
922
1015
  const emptyStateLabel = resolvedIsLoading
923
1016
  ? global.t?.t(storeName, 'label', 'loading') || 'Carregando...'
924
1017
  : 'Nenhum registro encontrado';
@@ -994,10 +1087,26 @@ const DefaultTable = ({
994
1087
  normalizeText(entry.value) !== '',
995
1088
  );
996
1089
  }, [columnsForTable, resolvedSummary, shouldReadSummary, storeName, summaryLabels, tableColumns]);
997
- const shouldRenderFooterBar =
998
- (shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems) ||
999
- summaryEntries.length > 0;
1000
-
1090
+ const shouldRenderFooterBar =
1091
+ (shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems) ||
1092
+ summaryEntries.length > 0;
1093
+ const footerProps = {
1094
+ columns: columnsForTable,
1095
+ resolvedAccentColor,
1096
+ resolvedTotalItemsText,
1097
+ shouldRenderCompactToolbarTotalItems,
1098
+ shouldRenderFooterTotalItems,
1099
+ storeName,
1100
+ summaryEntries,
1101
+ summaryLabels,
1102
+ tableBorderColor,
1103
+ tableMutedColor,
1104
+ tableSurfaceColor,
1105
+ tableTextColor,
1106
+ toolbarCountBackgroundColor,
1107
+ toolbarCountTextColor,
1108
+ };
1109
+
1001
1110
  const sortedData = useMemo(() => {
1002
1111
  const items = Array.isArray(resolvedData) ? [...resolvedData] : [];
1003
1112
  const sortField = resolvedSort?.field;
@@ -1086,12 +1195,12 @@ const DefaultTable = ({
1086
1195
  setEditingCell(null);
1087
1196
  }, []);
1088
1197
 
1089
- const saveCell = useCallback((row, column, nextValue) => {
1090
- const fieldName = getColumnKey(column);
1091
- if (!fieldName || typeof actions.save !== 'function') {
1092
- clearEdit();
1093
- return Promise.resolve(null);
1094
- }
1198
+ const saveCell = useCallback((row, column, nextValue) => {
1199
+ const fieldName = getColumnKey(column);
1200
+ if (!fieldName || typeof resolvedActions.save !== 'function') {
1201
+ clearEdit();
1202
+ return Promise.resolve(null);
1203
+ }
1095
1204
 
1096
1205
  const currentValue = resolveEditValue(column, row);
1097
1206
  const normalizedNextValue = nextValue && typeof nextValue === 'object'
@@ -1111,10 +1220,10 @@ const DefaultTable = ({
1111
1220
 
1112
1221
  setSavingCell(cellKey);
1113
1222
 
1114
- return actions.save(payload)
1115
- .then(savedItem => {
1116
- onSaved?.(savedItem || { ...row, [fieldName]: nextValue }, row);
1117
- return savedItem;
1223
+ return resolvedActions.save(payload)
1224
+ .then(savedItem => {
1225
+ onSaved?.(savedItem || { ...row, [fieldName]: nextValue }, row);
1226
+ return savedItem;
1118
1227
  })
1119
1228
  .catch(error => {
1120
1229
  console.error(error);
@@ -1124,7 +1233,7 @@ const DefaultTable = ({
1124
1233
  setSavingCell(null);
1125
1234
  clearEdit();
1126
1235
  });
1127
- }, [actions, clearEdit, onSaved]);
1236
+ }, [clearEdit, onSaved, resolvedActions]);
1128
1237
 
1129
1238
  const requestSort = useCallback(column => {
1130
1239
  if (!isSortableColumn(column)) return;
@@ -1160,27 +1269,27 @@ const DefaultTable = ({
1160
1269
  visibleColumnsPreferenceKey,
1161
1270
  ]);
1162
1271
 
1163
- const openEditModal = useCallback(row => {
1164
- if (typeof onEditRow === 'function') {
1165
- onEditRow(row);
1166
- return;
1167
- }
1168
-
1169
- setFormMode('edit');
1170
- setEditingRow(row);
1171
- }, [onEditRow]);
1172
-
1272
+ const openEditModal = useCallback(row => {
1273
+ if (typeof onEditRow === 'function') {
1274
+ onEditRow(row);
1275
+ return;
1276
+ }
1277
+
1278
+ setFormMode('edit');
1279
+ setEditingRow(row);
1280
+ }, [onEditRow]);
1281
+
1173
1282
  const openAddForm = useCallback(() => {
1174
1283
  if (typeof onAdd === 'function') {
1175
1284
  onAdd();
1176
1285
  return;
1177
1286
  }
1178
-
1179
- if (typeof actions.save !== 'function') return;
1180
-
1181
- setFormMode('create');
1182
- setEditingRow({});
1183
- }, [actions, onAdd]);
1287
+
1288
+ if (typeof resolvedActions.save !== 'function') return;
1289
+
1290
+ setFormMode('create');
1291
+ setEditingRow({});
1292
+ }, [onAdd, resolvedActions]);
1184
1293
 
1185
1294
  const closeEditModal = useCallback(() => {
1186
1295
  setEditingRow(null);
@@ -1192,12 +1301,12 @@ const DefaultTable = ({
1192
1301
 
1193
1302
  if (autoMode) {
1194
1303
  setAutoFilters(resolvedNextFilters);
1195
- storeActions.setFilters?.(resolvedNextFilters);
1304
+ resolvedActions.setFilters?.(resolvedNextFilters);
1196
1305
  return;
1197
1306
  }
1198
1307
 
1199
1308
  onFilterChange?.(resolvedNextFilters);
1200
- }, [autoMode, onFilterChange, storeActions]);
1309
+ }, [autoMode, onFilterChange, resolvedActions]);
1201
1310
 
1202
1311
  const updateFilter = useCallback((fieldName, value) => {
1203
1312
  const nextFilters = { ...(resolvedFilters || {}) };
@@ -1226,39 +1335,49 @@ const DefaultTable = ({
1226
1335
  [fieldName]: prev[fieldName] === false,
1227
1336
  };
1228
1337
 
1229
- actions?.setVisibleColumns?.(next);
1230
- if (visibleColumnsPreferenceKey) {
1338
+ resolvedActions?.setVisibleColumns?.(next);
1339
+ if (visibleColumnsStorageKey) {
1231
1340
  persistVisibleColumnsPreference(
1232
- visibleColumnsPreferenceKey,
1341
+ visibleColumnsStorageKey,
1233
1342
  next,
1234
1343
  );
1235
1344
  }
1236
1345
  return next;
1237
1346
  });
1238
1347
  }, [
1239
- actions,
1348
+ resolvedActions,
1240
1349
  columnsForTable,
1241
- visibleColumnsPreferenceKey,
1350
+ visibleColumnsStorageKey,
1242
1351
  ]);
1243
1352
 
1244
- const toggleViewMode = useCallback(() => {
1245
- setViewMode(prev => {
1246
- const nextViewMode = prev === 'table' ? 'cards' : 'table';
1247
-
1248
- if (!visibleColumnsPreferenceKey) {
1249
- return nextViewMode;
1250
- }
1251
-
1252
- persistTableViewModePreference(
1253
- visibleColumnsPreferenceKey,
1254
- nextViewMode,
1255
- );
1256
-
1257
- return nextViewMode;
1258
- });
1259
- }, [
1260
- visibleColumnsPreferenceKey,
1261
- ]);
1353
+ const toggleViewMode = useCallback(() => {
1354
+ const currentViewMode =
1355
+ isCompactView && shouldForceCardsOnCompact
1356
+ ? (compactViewMode || 'cards')
1357
+ : viewMode;
1358
+ const nextViewMode = currentViewMode === 'table' ? 'cards' : 'table';
1359
+
1360
+ if (isCompactView && shouldForceCardsOnCompact) {
1361
+ setCompactViewMode(nextViewMode);
1362
+ }
1363
+
1364
+ setViewMode(nextViewMode);
1365
+
1366
+ if (!visibleColumnsPreferenceKey) {
1367
+ return;
1368
+ }
1369
+
1370
+ persistTableViewModePreference(
1371
+ visibleColumnsPreferenceKey,
1372
+ nextViewMode,
1373
+ );
1374
+ }, [
1375
+ compactViewMode,
1376
+ isCompactView,
1377
+ shouldForceCardsOnCompact,
1378
+ viewMode,
1379
+ visibleColumnsPreferenceKey,
1380
+ ]);
1262
1381
 
1263
1382
  const handleEndReached = useCallback(() => {
1264
1383
  if (
@@ -1306,7 +1425,8 @@ const DefaultTable = ({
1306
1425
  column={column}
1307
1426
  columns={columnsForTable}
1308
1427
  editing={isEditing}
1309
- getOptionsForColumn={getOptionsForColumn}
1428
+ getOptionsForColumn={resolvedGetOptionsForColumn}
1429
+ onBeforeOpen={() => loadListOptionsForColumns([column])}
1310
1430
  onCancelEditing={clearEdit}
1311
1431
  onSave={value => saveCell(row, column, value)}
1312
1432
  onStartEditing={() => beginEdit(row, column)}
@@ -1325,7 +1445,8 @@ const DefaultTable = ({
1325
1445
  accentColor={resolvedAccentColor}
1326
1446
  column={column}
1327
1447
  filters={resolvedFilters}
1328
- getOptionsForColumn={getOptionsForColumn}
1448
+ onBeforeOpen={column?.list ? () => loadListOptionsForColumns([column]) : null}
1449
+ getOptionsForColumn={resolvedGetOptionsForColumn}
1329
1450
  onChange={updateFilter}
1330
1451
  storeName={storeName}
1331
1452
  style={getColumnStyle(column)}
@@ -1333,23 +1454,23 @@ const DefaultTable = ({
1333
1454
  );
1334
1455
  };
1335
1456
 
1336
- const renderToolbarAction = action => {
1337
- if (!action || action.hidden) return null;
1338
-
1339
- const isActive = action.active === true;
1340
- const hasLabel = normalizeText(action.label) !== '';
1341
- const actionColor = action.color || (isActive ? resolvedAccentColor : tableMutedColor);
1342
-
1343
- return (
1344
- <TouchableOpacity
1345
- key={action.key || action.icon || action.label}
1346
- style={[
1347
- styles.toolbarButton,
1348
- isActive
1349
- ? { backgroundColor: withOpacity(resolvedAccentColor, 0.12), borderColor: resolvedAccentColor }
1350
- : null,
1351
- action.style,
1352
- ]}
1457
+ const renderToolbarAction = action => {
1458
+ if (!action || action.hidden) return null;
1459
+
1460
+ const isActive = action.active === true;
1461
+ const hasLabel = normalizeText(action.label) !== '';
1462
+ const actionColor = action.color || tableActionTextColor;
1463
+
1464
+ return (
1465
+ <TouchableOpacity
1466
+ key={action.key || action.icon || action.label}
1467
+ accessibilityRole="button"
1468
+ accessibilityLabel={action.accessibilityLabel || action.label || action.key || action.icon}
1469
+ style={[
1470
+ styles.toolbarButton,
1471
+ { backgroundColor: tableActionBackgroundColor, borderColor: tableActionBorderColor },
1472
+ action.style,
1473
+ ]}
1353
1474
  activeOpacity={0.82}
1354
1475
  disabled={action.disabled === true}
1355
1476
  onPress={action.onPress}
@@ -1365,12 +1486,12 @@ const DefaultTable = ({
1365
1486
  <Text style={[styles.toolbarActionLabel, { color: actionColor }, action.labelStyle]} numberOfLines={1}>
1366
1487
  {action.label}
1367
1488
  </Text>
1368
- ) : null}
1369
- {action.badge !== undefined && action.badge !== null ? (
1370
- <Text style={[styles.toolbarBadgeText, { color: action.badgeColor || resolvedAccentColor }]}>
1371
- {action.badge}
1372
- </Text>
1373
- ) : null}
1489
+ ) : null}
1490
+ {action.badge !== undefined && action.badge !== null ? (
1491
+ <Text style={[styles.toolbarBadgeText, { color: action.badgeColor || tableActionTextColor }]}>
1492
+ {action.badge}
1493
+ </Text>
1494
+ ) : null}
1374
1495
  </TouchableOpacity>
1375
1496
  );
1376
1497
  };
@@ -1385,8 +1506,8 @@ const DefaultTable = ({
1385
1506
  [columnsForTable],
1386
1507
  );
1387
1508
 
1388
- const buildRowHelpers = useCallback(
1389
- row => {
1509
+ const buildRowHelpers = useCallback(
1510
+ row => {
1390
1511
  const openEdit = () => openEditModal(row);
1391
1512
  const openRow = typeof onRowPress === 'function' ? () => onRowPress(row) : null;
1392
1513
  const renderValue = (fieldName, fallback = '-') => {
@@ -1402,7 +1523,7 @@ const DefaultTable = ({
1402
1523
  const isEditing = editingCell === cellKey;
1403
1524
  const isSaving = savingCell === cellKey;
1404
1525
 
1405
- return (
1526
+ return (
1406
1527
  <DefaultInput
1407
1528
  accentColor={options.accentColor || resolvedAccentColor}
1408
1529
  column={column}
@@ -1410,14 +1531,15 @@ const DefaultTable = ({
1410
1531
  containerStyle={options.containerStyle}
1411
1532
  displayValue={options.displayValue}
1412
1533
  editing={isEditing}
1413
- getOptionsForColumn={getOptionsForColumn}
1534
+ getOptionsForColumn={resolvedGetOptionsForColumn}
1414
1535
  inputStyle={options.inputStyle}
1415
1536
  label={options.label}
1416
- numberOfLines={options.numberOfLines}
1417
- onCancelEditing={clearEdit}
1418
- onSave={value => saveCell(row, column, value)}
1419
- onStartEditing={() => beginEdit(row, column)}
1420
- readTextStyle={options.readTextStyle || options.textStyle}
1537
+ numberOfLines={options.numberOfLines}
1538
+ onBeforeOpen={() => loadListOptionsForColumns([column])}
1539
+ onCancelEditing={clearEdit}
1540
+ onSave={value => saveCell(row, column, value)}
1541
+ onStartEditing={() => beginEdit(row, column)}
1542
+ readTextStyle={options.readTextStyle || options.textStyle}
1421
1543
  row={row}
1422
1544
  saving={isSaving}
1423
1545
  showLabel={options.showLabel}
@@ -1441,24 +1563,36 @@ const DefaultTable = ({
1441
1563
  columnsForTable,
1442
1564
  editingCell,
1443
1565
  getColumnByField,
1444
- getOptionsForColumn,
1566
+ resolvedGetOptionsForColumn,
1445
1567
  openEditModal,
1446
1568
  onRowPress,
1447
1569
  saveCell,
1448
1570
  savingCell,
1449
1571
  storeName,
1450
- ],
1451
- );
1452
-
1453
- const renderCardItem = row => {
1454
- const helpers = buildRowHelpers(row);
1455
-
1456
- if (typeof renderCard === 'function') {
1457
- return (
1458
- <View key={row?.['@id'] || row?.id} style={styles.cardItem}>
1459
- {renderCard({
1460
- item: row,
1461
- openEdit: helpers.openEdit,
1572
+ ],
1573
+ );
1574
+
1575
+ const resolveRowStyle = useCallback(
1576
+ (row, index) => {
1577
+ if (typeof rowStyle === 'function') {
1578
+ return rowStyle(row, index);
1579
+ }
1580
+
1581
+ return rowStyle;
1582
+ },
1583
+ [rowStyle],
1584
+ );
1585
+
1586
+ const renderCardItem = (row, index = 0) => {
1587
+ const helpers = buildRowHelpers(row);
1588
+ const rowStyleValue = resolveRowStyle(row, index);
1589
+
1590
+ if (typeof renderCard === 'function') {
1591
+ return (
1592
+ <View key={row?.['@id'] || row?.id} style={[styles.cardItem, rowStyleValue]}>
1593
+ {renderCard({
1594
+ item: row,
1595
+ openEdit: helpers.openEdit,
1462
1596
  openRow: helpers.openRow,
1463
1597
  renderField: helpers.renderField,
1464
1598
  renderValue: helpers.renderValue,
@@ -1476,11 +1610,18 @@ const DefaultTable = ({
1476
1610
  ) : null}
1477
1611
  </View>
1478
1612
  );
1479
- }
1480
-
1481
- return (
1482
- <View key={row?.['@id'] || row?.id} style={[styles.defaultCard, { backgroundColor: tableSurfaceColor, borderColor: tableBorderColor }]}>
1483
- {tableColumns.map(column => (
1613
+ }
1614
+
1615
+ return (
1616
+ <View
1617
+ key={row?.['@id'] || row?.id}
1618
+ style={[
1619
+ styles.defaultCard,
1620
+ { backgroundColor: tableSurfaceColor, borderColor: tableBorderColor },
1621
+ rowStyleValue,
1622
+ ]}
1623
+ >
1624
+ {tableColumns.map(column => (
1484
1625
  <View key={getColumnKey(column)} style={styles.defaultCardLine}>
1485
1626
  <Text style={[styles.defaultCardLabel, { color: tableMutedColor }]}>
1486
1627
  {formatStoreColumnLabel({
@@ -1539,23 +1680,29 @@ const DefaultTable = ({
1539
1680
  );
1540
1681
  };
1541
1682
 
1542
- const renderTableItem = ({ item: row, index }) => {
1543
- const hasRowPress = typeof onRowPress === 'function';
1544
- const RowComponent = hasRowPress ? TouchableOpacity : View;
1545
- const rowPressProps = hasRowPress
1546
- ? {
1547
- activeOpacity: 0.84,
1548
- onPress: () => onRowPress(row),
1549
- }
1683
+ const renderTableItem = ({ item: row, index }) => {
1684
+ const hasRowPress = typeof onRowPress === 'function';
1685
+ const RowComponent = hasRowPress ? TouchableOpacity : View;
1686
+ const rowStyleValue = resolveRowStyle(row, index);
1687
+ const rowPressProps = hasRowPress
1688
+ ? {
1689
+ activeOpacity: 0.84,
1690
+ onPress: () => onRowPress(row),
1691
+ }
1550
1692
  : {};
1551
1693
  const rowBackgroundColor = index % 2 === 0 ? tableOddColor : tableEvenColor;
1552
1694
 
1553
- return (
1554
- <RowComponent
1555
- key={getRowKey(row)}
1556
- style={[styles.row, tableLayoutStyle, { backgroundColor: rowBackgroundColor, borderBottomColor: tableBorderColor }]}
1557
- {...rowPressProps}
1558
- >
1695
+ return (
1696
+ <RowComponent
1697
+ key={getRowKey(row)}
1698
+ style={[
1699
+ styles.row,
1700
+ tableLayoutStyle,
1701
+ { backgroundColor: rowBackgroundColor, borderBottomColor: tableBorderColor },
1702
+ rowStyleValue,
1703
+ ]}
1704
+ {...rowPressProps}
1705
+ >
1559
1706
  {tableColumns.map(column => (
1560
1707
  <React.Fragment key={getColumnKey(column)}>
1561
1708
  {renderEditableCell(row, column)}
@@ -1579,32 +1726,33 @@ const DefaultTable = ({
1579
1726
  const isCreate = formMode === 'create';
1580
1727
 
1581
1728
  return (
1582
- <Modal visible={Boolean(editingRow)} transparent animationType="fade" onRequestClose={closeEditModal}>
1583
- <View style={[styles.modalOverlay, { backgroundColor: withOpacity(tableTextColor, 0.42) }]}>
1584
- <View style={[styles.modalCard, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}>
1585
- <View style={[styles.modalHeader, { borderBottomColor: tableBorderColor }]}>
1586
- <Text style={[styles.modalTitle, { color: tableTextColor }]}>
1587
- {isCreate
1588
- ? global.t?.t(storeName, 'button', 'add') || 'Adicionar'
1589
- : global.t?.t(storeName, 'button', 'edit') || 'Editar'}
1590
- </Text>
1591
- <TouchableOpacity
1592
- style={[styles.modalCloseButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1593
- onPress={closeEditModal}>
1594
- <Icon name="x" size={18} color={tableMutedColor} />
1595
- </TouchableOpacity>
1596
- </View>
1729
+ <Modal visible={Boolean(editingRow)} transparent animationType="fade" onRequestClose={closeEditModal}>
1730
+ <View style={[styles.modalOverlay, { backgroundColor: modalOverlayColor }]}>
1731
+ <View style={[styles.modalCard, { borderColor: modalBorderColor, backgroundColor: modalBackgroundColor }]}>
1732
+ <View style={[styles.modalHeader, { borderBottomColor: modalBorderColor }]}>
1733
+ <Text style={[styles.modalTitle, { color: modalHeaderTextColor }]}>
1734
+ {isCreate
1735
+ ? global.t?.t(storeName, 'button', 'add') || 'Adicionar'
1736
+ : global.t?.t(storeName, 'button', 'edit') || 'Editar'}
1737
+ </Text>
1738
+ <TouchableOpacity
1739
+ style={[styles.modalCloseButton, { borderColor: modalBorderColor, backgroundColor: modalBackgroundColor }]}
1740
+ onPress={closeEditModal}>
1741
+ <Icon name="x" size={18} color={modalCloseIconColor} />
1742
+ </TouchableOpacity>
1743
+ </View>
1597
1744
 
1598
1745
  <DefaultForm
1599
1746
  accentColor={resolvedAccentColor}
1600
- actions={actions}
1747
+ actions={resolvedActions}
1601
1748
  columns={isCreate ? columnsForTable : editableColumns}
1602
- getOptionsForColumn={getOptionsForColumn}
1749
+ getOptionsForColumn={resolvedGetOptionsForColumn}
1603
1750
  mode={formMode}
1604
- onCancel={closeEditModal}
1605
- onSaved={(savedItem, originalRow) => {
1606
- onSaved?.(savedItem, originalRow);
1607
- closeEditModal();
1751
+ onBeforeOpen={column => loadListOptionsForColumns([column])}
1752
+ onCancel={closeEditModal}
1753
+ onSaved={(savedItem, originalRow) => {
1754
+ onSaved?.(savedItem, originalRow);
1755
+ closeEditModal();
1608
1756
  }}
1609
1757
  row={editingRow || {}}
1610
1758
  storeName={storeName}
@@ -1620,18 +1768,18 @@ const DefaultTable = ({
1620
1768
 
1621
1769
  return (
1622
1770
  <Modal visible transparent animationType="fade" onRequestClose={() => setIsColumnMenuOpen(false)}>
1623
- <View style={[styles.modalOverlay, { backgroundColor: withOpacity(tableTextColor, 0.42) }]}>
1624
- <View style={[styles.modalCard, styles.columnMenuModalCard, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}>
1625
- <View style={[styles.modalHeader, { borderBottomColor: tableBorderColor }]}>
1626
- <Text style={[styles.modalTitle, { color: tableTextColor }]} numberOfLines={1}>
1771
+ <View style={[styles.modalOverlay, { backgroundColor: modalOverlayColor }]}>
1772
+ <View style={[styles.modalCard, styles.columnMenuModalCard, { borderColor: modalBorderColor, backgroundColor: modalBackgroundColor }]}>
1773
+ <View style={[styles.modalHeader, { borderBottomColor: modalBorderColor }]}>
1774
+ <Text style={[styles.modalTitle, { color: modalHeaderTextColor }]} numberOfLines={1}>
1627
1775
  Colunas
1628
1776
  </Text>
1629
1777
  <TouchableOpacity
1630
- style={[styles.modalCloseButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1778
+ style={[styles.modalCloseButton, { borderColor: modalBorderColor, backgroundColor: modalBackgroundColor }]}
1631
1779
  activeOpacity={0.82}
1632
1780
  onPress={() => setIsColumnMenuOpen(false)}
1633
1781
  >
1634
- <Icon name="x" size={16} color={tableMutedColor} />
1782
+ <Icon name="x" size={16} color={modalCloseIconColor} />
1635
1783
  </TouchableOpacity>
1636
1784
  </View>
1637
1785
  <ScrollView style={styles.columnMenuModalBody} contentContainerStyle={styles.columnMenuModalList}>
@@ -1647,8 +1795,8 @@ const DefaultTable = ({
1647
1795
 
1648
1796
  return (
1649
1797
  <TouchableOpacity key={fieldName} style={styles.columnMenuItem} activeOpacity={0.82} onPress={() => toggleColumn(column)}>
1650
- <Icon name={checked ? 'check-square' : 'square'} size={16} color={checked ? resolvedAccentColor : tableMutedColor} />
1651
- <Text style={[styles.columnMenuText, { color: tableTextColor }]} numberOfLines={1}>{label}</Text>
1798
+ <Icon name={checked ? 'check-square' : 'square'} size={16} color={checked ? checkboxSelectedMarkColor : checkboxBorderColor} />
1799
+ <Text style={[styles.columnMenuText, { color: modalTextColor }]} numberOfLines={1}>{label}</Text>
1652
1800
  </TouchableOpacity>
1653
1801
  );
1654
1802
  })}
@@ -1660,16 +1808,16 @@ const DefaultTable = ({
1660
1808
  };
1661
1809
 
1662
1810
  return (
1663
- <View style={[styles.wrap, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]} onLayout={handleLayout}>
1664
- <View style={[styles.toolbar, { borderBottomColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}>
1665
- {shouldRenderCompactToolbarTotalItems ? (
1666
- <View style={styles.toolbarCompactLead}>
1667
- <View style={[styles.toolbarCountPill, { backgroundColor: withOpacity(resolvedAccentColor, 0.12) }]}>
1668
- <Text
1669
- style={[styles.toolbarCountText, { color: resolvedAccentColor }]}
1670
- numberOfLines={1}
1671
- adjustsFontSizeToFit
1672
- minimumFontScale={0.82}
1811
+ <View style={[styles.wrap, { borderColor: panelBorderColor, backgroundColor: panelBackgroundColor }]} onLayout={handleLayout}>
1812
+ <View style={[styles.toolbar, { borderBottomColor: toolbarBorderColor, backgroundColor: toolbarBackgroundColor }]}>
1813
+ {shouldRenderCompactToolbarTotalItems ? (
1814
+ <View style={styles.toolbarCompactLead}>
1815
+ <View style={[styles.toolbarCountPill, { backgroundColor: toolbarCountBackgroundColor }]}>
1816
+ <Text
1817
+ style={[styles.toolbarCountText, { color: toolbarCountTextColor }]}
1818
+ numberOfLines={1}
1819
+ adjustsFontSizeToFit
1820
+ minimumFontScale={0.82}
1673
1821
  >
1674
1822
  {resolvedTotalItemsText}
1675
1823
  </Text>
@@ -1704,50 +1852,70 @@ const DefaultTable = ({
1704
1852
  />
1705
1853
  ) : null}
1706
1854
  {!shouldRenderCompactToolbarTotalItems ? renderToolbarActions() : null}
1707
- {showColumnFiltersButton ? (
1708
- <TouchableOpacity
1709
- style={[
1710
- styles.toolbarButton,
1711
- { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor },
1712
- showColumnFilters ? { backgroundColor: withOpacity(resolvedAccentColor, 0.12), borderColor: resolvedAccentColor } : null,
1713
- ]}
1714
- activeOpacity={0.82}
1715
- onPress={() => setShowColumnFilters(prev => !prev)}
1716
- >
1717
- <Icon name="filter" size={14} color={showColumnFilters ? resolvedAccentColor : tableMutedColor} />
1718
- {activeFilterCount > 0 ? (
1719
- <Text style={[styles.toolbarBadgeText, { color: resolvedAccentColor }]}>{activeFilterCount}</Text>
1720
- ) : null}
1721
- </TouchableOpacity>
1722
- ) : null}
1723
- {(!isCompactView || !shouldForceCardsOnCompact) ? (
1724
- <TouchableOpacity
1725
- style={[styles.toolbarButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1726
- activeOpacity={0.82}
1727
- onPress={toggleViewMode}
1728
- >
1729
- <Icon name={viewMode === 'table' ? 'grid' : 'list'} size={14} color={tableMutedColor} />
1730
- </TouchableOpacity>
1731
- ) : null}
1732
- <TouchableOpacity
1733
- style={[styles.toolbarButton, { borderColor: tableBorderColor, backgroundColor: tableSurfaceColor }]}
1734
- activeOpacity={0.82}
1735
- onPress={() => setIsColumnMenuOpen(prev => !prev)}>
1736
- <Icon name="columns" size={14} color={tableMutedColor} />
1737
- </TouchableOpacity>
1738
- {shouldRenderAddButton ? (
1739
- <TouchableOpacity
1740
- style={[
1741
- styles.toolbarButton,
1742
- styles.toolbarAddButton,
1743
- { backgroundColor: resolvedAccentColor, borderColor: resolvedAccentColor },
1744
- ]}
1745
- activeOpacity={0.85}
1746
- onPress={openAddForm}
1747
- >
1748
- <Icon name="plus" size={16} color={tableOnAccentColor} />
1749
- </TouchableOpacity>
1750
- ) : null}
1855
+ {showColumnFiltersButton ? (
1856
+ <TouchableOpacity
1857
+ style={[
1858
+ styles.toolbarButton,
1859
+ { borderColor: tableButtonBorderColor, backgroundColor: tableButtonBackgroundColor },
1860
+ showColumnFilters
1861
+ ? { backgroundColor: tableButtonPressedBackgroundColor, borderColor: tableButtonPressedBorderColor }
1862
+ : null,
1863
+ ]}
1864
+ activeOpacity={0.82}
1865
+ onPress={() => setShowColumnFilters(prev => !prev)}
1866
+ >
1867
+ <Icon
1868
+ name="filter"
1869
+ size={14}
1870
+ color={showColumnFilters ? tableButtonPressedIconColor : tableButtonIconColor}
1871
+ />
1872
+ {activeFilterCount > 0 ? (
1873
+ <Text
1874
+ style={[
1875
+ styles.toolbarBadgeText,
1876
+ { color: showColumnFilters ? tableButtonPressedIconColor : tableButtonIconColor },
1877
+ ]}>
1878
+ {activeFilterCount}
1879
+ </Text>
1880
+ ) : null}
1881
+ </TouchableOpacity>
1882
+ ) : null}
1883
+ <TouchableOpacity
1884
+ style={[
1885
+ styles.toolbarButton,
1886
+ { borderColor: tableButtonBorderColor, backgroundColor: tableButtonBackgroundColor },
1887
+ effectiveViewMode !== 'table'
1888
+ ? { backgroundColor: tableButtonPressedBackgroundColor, borderColor: tableButtonPressedBorderColor }
1889
+ : null,
1890
+ ]}
1891
+ activeOpacity={0.82}
1892
+ onPress={toggleViewMode}
1893
+ >
1894
+ <Icon
1895
+ name={effectiveViewMode === 'table' ? 'list' : 'grid'}
1896
+ size={14}
1897
+ color={effectiveViewMode !== 'table' ? tableButtonPressedIconColor : tableButtonIconColor}
1898
+ />
1899
+ </TouchableOpacity>
1900
+ <TouchableOpacity
1901
+ style={[styles.toolbarButton, { borderColor: tableButtonBorderColor, backgroundColor: tableButtonBackgroundColor }]}
1902
+ activeOpacity={0.82}
1903
+ onPress={() => setIsColumnMenuOpen(prev => !prev)}>
1904
+ <Icon name="columns" size={14} color={tableButtonTextColor} />
1905
+ </TouchableOpacity>
1906
+ {shouldRenderAddButton ? (
1907
+ <TouchableOpacity
1908
+ style={[
1909
+ styles.toolbarButton,
1910
+ styles.toolbarAddButton,
1911
+ { backgroundColor: tableButtonBackgroundColor, borderColor: tableButtonBackgroundColor },
1912
+ ]}
1913
+ activeOpacity={0.85}
1914
+ onPress={openAddForm}
1915
+ >
1916
+ <Icon name="plus" size={16} color={tableButtonTextColor} />
1917
+ </TouchableOpacity>
1918
+ ) : null}
1751
1919
  </View>
1752
1920
 
1753
1921
  </View>
@@ -1756,7 +1924,7 @@ const DefaultTable = ({
1756
1924
  <FlatList
1757
1925
  data={sortedData}
1758
1926
  keyExtractor={getRowKey}
1759
- renderItem={({ item }) => renderCardItem(item)}
1927
+ renderItem={({ item, index }) => renderCardItem(item, index)}
1760
1928
  style={styles.cardsScroll}
1761
1929
  contentContainerStyle={styles.cardsGrid}
1762
1930
  ListEmptyComponent={renderEmptyState(false)}
@@ -1839,54 +2007,60 @@ const DefaultTable = ({
1839
2007
  </ScrollView>
1840
2008
  )}
1841
2009
 
1842
- {renderLoadingOverlay()}
1843
-
1844
- {shouldRenderFooterBar ? (
1845
- <View style={[styles.footerBar, { backgroundColor: tableSurfaceColor, borderTopColor: tableBorderColor }]}>
1846
- {summaryEntries.length > 0 ? (
1847
- <View style={styles.footerSummaryList}>
1848
- {summaryEntries.map(entry => (
1849
- <View key={entry.key} style={styles.footerSummaryItem}>
1850
- <Text style={[styles.footerSummaryLabel, { color: tableMutedColor }]} numberOfLines={1}>
1851
- {entry.label}
1852
- </Text>
1853
- <Text
1854
- style={[
1855
- styles.footerSummaryValue,
1856
- entry.path?.[0] === 'sum' || isMoneySummaryPath(entry.path)
1857
- ? { color: resolvedAccentColor }
1858
- : { color: tableTextColor },
1859
- ]}
1860
- numberOfLines={1}
1861
- adjustsFontSizeToFit
1862
- minimumFontScale={0.78}
1863
- >
2010
+ {renderLoadingOverlay()}
2011
+
2012
+ {footerComponent ? (
2013
+ React.isValidElement(footerComponent) ? (
2014
+ footerComponent
2015
+ ) : (
2016
+ React.createElement(footerComponent, footerProps)
2017
+ )
2018
+ ) : shouldRenderFooterBar ? (
2019
+ <View style={[styles.footerBar, { backgroundColor: tableSurfaceColor, borderTopColor: tableBorderColor }]}>
2020
+ {summaryEntries.length > 0 ? (
2021
+ <View style={styles.footerSummaryList}>
2022
+ {summaryEntries.map(entry => (
2023
+ <View key={entry.key} style={styles.footerSummaryItem}>
2024
+ <Text style={[styles.footerSummaryLabel, { color: tableMutedColor }]} numberOfLines={1}>
2025
+ {entry.label}
2026
+ </Text>
2027
+ <Text
2028
+ style={[
2029
+ styles.footerSummaryValue,
2030
+ entry.path?.[0] === 'sum' || isMoneySummaryPath(entry.path)
2031
+ ? { color: resolvedAccentColor }
2032
+ : { color: tableTextColor },
2033
+ ]}
2034
+ numberOfLines={1}
2035
+ adjustsFontSizeToFit
2036
+ minimumFontScale={0.78}
2037
+ >
1864
2038
  {formatSummaryValue({
1865
2039
  column: entry.column,
1866
2040
  columns: columnsForTable,
1867
2041
  path: entry.path,
1868
2042
  storeName,
1869
2043
  value: entry.value,
1870
- })}
1871
- </Text>
1872
- </View>
1873
- ))}
1874
- </View>
1875
- ) : null}
1876
- {shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems ? (
1877
- <View style={[styles.footerCountPill, { backgroundColor: withOpacity(resolvedAccentColor, 0.12) }]}>
1878
- <Text
1879
- style={[styles.footerCountText, { color: resolvedAccentColor }]}
1880
- numberOfLines={1}
1881
- adjustsFontSizeToFit
1882
- minimumFontScale={0.82}
1883
- >
1884
- {resolvedTotalItemsText}
1885
- </Text>
1886
- </View>
1887
- ) : null}
1888
- </View>
1889
- ) : null}
2044
+ })}
2045
+ </Text>
2046
+ </View>
2047
+ ))}
2048
+ </View>
2049
+ ) : null}
2050
+ {shouldRenderFooterTotalItems && !shouldRenderCompactToolbarTotalItems ? (
2051
+ <View style={[styles.footerCountPill, { backgroundColor: toolbarCountBackgroundColor }]}>
2052
+ <Text
2053
+ style={[styles.footerCountText, { color: toolbarCountTextColor }]}
2054
+ numberOfLines={1}
2055
+ adjustsFontSizeToFit
2056
+ minimumFontScale={0.82}
2057
+ >
2058
+ {resolvedTotalItemsText}
2059
+ </Text>
2060
+ </View>
2061
+ ) : null}
2062
+ </View>
2063
+ ) : null}
1890
2064
 
1891
2065
  {renderColumnMenuModal()}
1892
2066
  {renderEditModal()}