@adiba-banking-cloud/backoffice 0.2.0 → 0.2.3

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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-QwR6crYB.js');
3
+ var index = require('./index-BHsXwqJB.js');
4
4
 
5
5
  function _mergeNamespaces(n, m) {
6
6
  m.forEach(function (e) {
@@ -22,6 +22,8 @@ require('@fontsource/poppins/800.css');
22
22
  var axios = require('axios');
23
23
  var dates = require('@mantine/dates');
24
24
  require('@mantine/dates/styles.css');
25
+ var reactQuery = require('@tanstack/react-query');
26
+ var reactQueryDevtools = require('@tanstack/react-query-devtools');
25
27
 
26
28
  function _interopNamespaceDefault(e) {
27
29
  var n = Object.create(null);
@@ -12170,7 +12172,7 @@ const initChart$1 = props => {
12170
12172
  }
12171
12173
 
12172
12174
  // Fallback: use dynamic import (async, but will work in Vite/Storybook)
12173
- Promise.resolve().then(function () { return require('./heatmap-c3tdleHx.js'); }).then(function (n) { return n.heatmap; }).then(heatmapModule => {
12175
+ Promise.resolve().then(function () { return require('./heatmap-QyWScAOZ.js'); }).then(function (n) { return n.heatmap; }).then(heatmapModule => {
12174
12176
  const moduleFn = typeof heatmapModule === "function" ? heatmapModule : heatmapModule?.default || heatmapModule;
12175
12177
  if (typeof moduleFn === "function") {
12176
12178
  moduleFn(Highcharts);
@@ -13733,19 +13735,32 @@ const SimpleTable = _ref => {
13733
13735
  actionFn,
13734
13736
  isStriped
13735
13737
  } = _ref;
13736
- columns.sort((a, b) => a.order - b.order);
13737
- const indexColumn = {
13738
- order: -99,
13739
- id: "index",
13740
- label: "Id"
13741
- };
13742
- const actionColumn = {
13743
- order: 99999,
13744
- id: "action",
13745
- label: ""
13746
- };
13747
- withIndex ? columns.unshift(indexColumn) : null;
13748
- withAction && actionFn ? columns.push(actionColumn) : null;
13738
+ // Memoize the final columns array to avoid mutations on every render
13739
+ const finalColumns = React.useMemo(() => {
13740
+ // Create a new array to avoid mutating the original
13741
+ const sortedColumns = [...columns].sort((a, b) => a.order - b.order);
13742
+ const indexColumn = {
13743
+ order: -99,
13744
+ id: "index",
13745
+ label: "Id"
13746
+ };
13747
+ const actionColumn = {
13748
+ order: 99999,
13749
+ id: "action",
13750
+ label: ""
13751
+ };
13752
+
13753
+ // Build final array without mutations
13754
+ const result = [];
13755
+ if (withIndex) {
13756
+ result.push(indexColumn);
13757
+ }
13758
+ result.push(...sortedColumns);
13759
+ if (withAction && actionFn) {
13760
+ result.push(actionColumn);
13761
+ }
13762
+ return result;
13763
+ }, [columns, withIndex, withAction, actionFn]);
13749
13764
  const isStripedProps = {
13750
13765
  striped: "even",
13751
13766
  highlightOnHover: true,
@@ -13754,7 +13769,7 @@ const SimpleTable = _ref => {
13754
13769
  };
13755
13770
  return /*#__PURE__*/React.createElement(core.Table, isStriped && isStripedProps, /*#__PURE__*/React.createElement(core.Table.Thead, null, /*#__PURE__*/React.createElement(core.Table.Tr, {
13756
13771
  bg: isStriped ? "gray.1" : "transparent"
13757
- }, columns.map((column, index) => {
13772
+ }, finalColumns.map((column, index) => {
13758
13773
  return /*#__PURE__*/React.createElement(SimpleHeader, _extends({
13759
13774
  key: `column-${index}`
13760
13775
  }, column));
@@ -13762,7 +13777,7 @@ const SimpleTable = _ref => {
13762
13777
  return /*#__PURE__*/React.createElement(SimpleBody, {
13763
13778
  key: index,
13764
13779
  row,
13765
- columns,
13780
+ columns: finalColumns,
13766
13781
  withAction,
13767
13782
  withIndex,
13768
13783
  actionFn,
@@ -14445,6 +14460,127 @@ const MAX_PAGE_SIZE = 100;
14445
14460
  */
14446
14461
  const DEFAULT_API_TIMEOUT = 30000;
14447
14462
 
14463
+ /**
14464
+ * Configuration for URL filter hook
14465
+ */
14466
+
14467
+ /**
14468
+ * Return type for useUrlFilters hook
14469
+ */
14470
+
14471
+ /**
14472
+ * Generic hook for managing URL-based filters with pagination
14473
+ *
14474
+ * @example
14475
+ * ```tsx
14476
+ * interface MyFilters {
14477
+ * page: number;
14478
+ * limit: number;
14479
+ * name?: string;
14480
+ * status?: string;
14481
+ * }
14482
+ *
14483
+ * const { filters, updateFilters, clearFilters } = useUrlFilters<MyFilters>({
14484
+ * parseFilters: (params) => ({
14485
+ * page: parseInt(params.get('page') || '1', 10),
14486
+ * limit: parseInt(params.get('limit') || '10', 10),
14487
+ * name: params.get('name') || undefined,
14488
+ * status: params.get('status') || undefined,
14489
+ * }),
14490
+ * serializeFilters: (filters, params) => {
14491
+ * if (filters.page) params.set('page', String(filters.page));
14492
+ * if (filters.limit) params.set('limit', String(filters.limit));
14493
+ * if (filters.name) params.set('name', filters.name);
14494
+ * if (filters.status) params.set('status', filters.status);
14495
+ * },
14496
+ * });
14497
+ * ```
14498
+ */
14499
+ function useUrlFilters(options) {
14500
+ const {
14501
+ defaultPageSize = DEFAULT_PAGE_SIZE,
14502
+ parseFilters,
14503
+ serializeFilters,
14504
+ hasActiveFilters: checkActiveFilters,
14505
+ toApiParams
14506
+ } = options;
14507
+ const [searchParams, setSearchParams] = reactRouterDom.useSearchParams();
14508
+ const filters = React.useMemo(() => {
14509
+ const parsed = parseFilters(searchParams);
14510
+
14511
+ // Ensure page and limit are always present with valid defaults
14512
+ const page = typeof parsed.page === "number" && parsed.page > 0 ? parsed.page : 1;
14513
+ const limit = typeof parsed.limit === "number" && parsed.limit > 0 ? parsed.limit : defaultPageSize;
14514
+ return {
14515
+ ...parsed,
14516
+ page,
14517
+ limit
14518
+ };
14519
+ }, [searchParams, parseFilters, defaultPageSize]);
14520
+ const updateFilters = newFilters => {
14521
+ const params = new URLSearchParams(searchParams);
14522
+
14523
+ // Merge new filters with existing filters
14524
+ const mergedFilters = {
14525
+ ...filters,
14526
+ ...newFilters
14527
+ };
14528
+
14529
+ // Determine if we should reset page to 1
14530
+ // Reset if: page is not explicitly set AND other filters are changing
14531
+ const hasNonPaginationChanges = Object.keys(newFilters).some(key => key !== "page" && key !== "limit");
14532
+ if (newFilters.page === undefined && hasNonPaginationChanges) {
14533
+ mergedFilters.page = 1; // Reset to page 1
14534
+ }
14535
+
14536
+ // Ensure page and limit are numbers
14537
+ if (typeof mergedFilters.page !== "number" || mergedFilters.page < 1) {
14538
+ mergedFilters.page = 1;
14539
+ }
14540
+ if (typeof mergedFilters.limit !== "number" || mergedFilters.limit < 1) {
14541
+ mergedFilters.limit = defaultPageSize;
14542
+ }
14543
+
14544
+ // Serialize all filters
14545
+ serializeFilters(mergedFilters, params);
14546
+ setSearchParams(params, {
14547
+ replace: true
14548
+ });
14549
+ };
14550
+ const clearFilters = () => {
14551
+ const params = new URLSearchParams();
14552
+ params.set("page", "1");
14553
+ params.set("limit", String(defaultPageSize));
14554
+ setSearchParams(params, {
14555
+ replace: true
14556
+ });
14557
+ };
14558
+ const hasActiveFilters = React.useMemo(() => {
14559
+ if (checkActiveFilters) {
14560
+ return checkActiveFilters(filters);
14561
+ }
14562
+ // Default: check if any non-pagination fields have values
14563
+ return Object.keys(filters).some(key => {
14564
+ if (key === "page" || key === "limit") return false;
14565
+ const value = filters[key];
14566
+ return value !== undefined && value !== null && value !== "";
14567
+ });
14568
+ }, [filters, checkActiveFilters]);
14569
+ const apiParams = React.useMemo(() => {
14570
+ if (toApiParams) {
14571
+ return toApiParams(filters);
14572
+ }
14573
+ return undefined;
14574
+ }, [filters, toApiParams]);
14575
+ return {
14576
+ filters,
14577
+ updateFilters,
14578
+ clearFilters,
14579
+ hasActiveFilters,
14580
+ apiParams
14581
+ };
14582
+ }
14583
+
14448
14584
  class ApiClient {
14449
14585
  constructor(config) {
14450
14586
  const {
@@ -14548,6 +14684,103 @@ const ModalContentWrapper = _ref => {
14548
14684
  }, children));
14549
14685
  };
14550
14686
 
14687
+ /**
14688
+ * Default QueryClient configuration
14689
+ */
14690
+ const defaultQueryClient = new reactQuery.QueryClient({
14691
+ defaultOptions: {
14692
+ queries: {
14693
+ retry: 1,
14694
+ refetchOnWindowFocus: false,
14695
+ staleTime: 30000,
14696
+ // 30 seconds
14697
+ gcTime: 5 * 60 * 1000 // 5 minutes
14698
+ },
14699
+ mutations: {
14700
+ retry: false
14701
+ }
14702
+ }
14703
+ });
14704
+ /**
14705
+ * Higher-order component that wraps a component with all necessary providers:
14706
+ * - QueryClientProvider (React Query)
14707
+ * - MantineProvider (Mantine UI)
14708
+ * - DatesProvider (Mantine Dates)
14709
+ *
14710
+ * @param Component - Component to wrap
14711
+ * @param options - Optional configuration
14712
+ * @returns Wrapped component with all providers
14713
+ *
14714
+ * @example
14715
+ * ```tsx
14716
+ * // Using default configuration
14717
+ * const WrappedComponent = withProviders(MyComponent);
14718
+ *
14719
+ * // Using custom QueryClient
14720
+ * const customQueryClient = new QueryClient({ ... });
14721
+ * const WrappedComponent = withProviders(MyComponent, {
14722
+ * queryClient: customQueryClient,
14723
+ * });
14724
+ * ```
14725
+ */
14726
+ function withProviders(Component) {
14727
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
14728
+ const {
14729
+ queryClient = defaultQueryClient,
14730
+ enableDevtools = true,
14731
+ theme: customTheme = theme,
14732
+ datesSettings = {
14733
+ locale: "en",
14734
+ timezone: "UTC"
14735
+ }
14736
+ } = options;
14737
+ const WrappedComponent = props => {
14738
+ const showDevtools = enableDevtools && typeof process !== "undefined" && process.env?.NODE_ENV === "development";
14739
+ return /*#__PURE__*/React__namespace.createElement(reactQuery.QueryClientProvider, {
14740
+ client: queryClient
14741
+ }, /*#__PURE__*/React__namespace.createElement(core.MantineProvider, {
14742
+ theme: customTheme
14743
+ }, /*#__PURE__*/React__namespace.createElement(dates.DatesProvider, {
14744
+ settings: datesSettings
14745
+ }, /*#__PURE__*/React__namespace.createElement(Component, props), showDevtools && /*#__PURE__*/React__namespace.createElement(reactQueryDevtools.ReactQueryDevtools, null))));
14746
+ };
14747
+ WrappedComponent.displayName = `withProviders(${Component.displayName || Component.name || "Component"})`;
14748
+ return WrappedComponent;
14749
+ }
14750
+
14751
+ /**
14752
+ * String utility functions for text manipulation
14753
+ */
14754
+ const StringHelpers = {
14755
+ /**
14756
+ * Extracts text between curly braces from a string
14757
+ * @param input - Input string that may contain text in braces
14758
+ * @returns The text between braces, or empty string if not found
14759
+ * @example
14760
+ * extractBetweenBraces("Hello {world}") // returns "world"
14761
+ * extractBetweenBraces("No braces") // returns ""
14762
+ */
14763
+ extractBetweenBraces: input => {
14764
+ const regex = /^[^{]*\{([^}]*)\}[^{]*$/;
14765
+ const match = input.match(regex);
14766
+ return match ? match[1] : "";
14767
+ },
14768
+ /**
14769
+ * Removes text within curly braces from a string
14770
+ * @param input - Input string that may contain text in braces
14771
+ * @returns The string with braces and their content removed, trimmed
14772
+ * @example
14773
+ * extractOutsideBraces("Hello {world}") // returns "Hello"
14774
+ * extractOutsideBraces("No braces") // returns "No braces"
14775
+ */
14776
+ extractOutsideBraces: input => {
14777
+ const regex = /\{[^}]*\}/g;
14778
+ return input.replace(regex, "").trim();
14779
+ }
14780
+ };
14781
+ const extractBetweenBraces = StringHelpers.extractBetweenBraces;
14782
+ const extractOutsideBraces = StringHelpers.extractOutsideBraces;
14783
+
14551
14784
  exports.ApplicationMenu = ApplicationMenu;
14552
14785
  exports.ApplicationPanel = ApplicationPanel;
14553
14786
  exports.AvatarLabelPanel = AvatarLabelPanel;
@@ -14594,7 +14827,11 @@ exports.TwoFactorModal = TwoFactorModal;
14594
14827
  exports.UserMenu = UserMenu;
14595
14828
  exports.apiClient = apiClient;
14596
14829
  exports.createApiClient = createApiClient;
14830
+ exports.extractBetweenBraces = extractBetweenBraces;
14831
+ exports.extractOutsideBraces = extractOutsideBraces;
14597
14832
  exports.getDefaultExportFromCjs = getDefaultExportFromCjs;
14598
14833
  exports.theme = theme;
14599
14834
  exports.useManagedModals = useManagedModals;
14600
14835
  exports.useModal = useModal;
14836
+ exports.useUrlFilters = useUrlFilters;
14837
+ exports.withProviders = withProviders;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-QwR6crYB.js');
3
+ var index = require('./index-BHsXwqJB.js');
4
4
  require('@mantine/modals');
5
5
  require('react');
6
6
  require('@mantine/core');
@@ -23,6 +23,8 @@ require('@fontsource/poppins/800.css');
23
23
  require('axios');
24
24
  require('@mantine/dates');
25
25
  require('@mantine/dates/styles.css');
26
+ require('@tanstack/react-query');
27
+ require('@tanstack/react-query-devtools');
26
28
 
27
29
 
28
30
 
@@ -72,6 +74,10 @@ exports.TwoFactorModal = index.TwoFactorModal;
72
74
  exports.UserMenu = index.UserMenu;
73
75
  exports.apiClient = index.apiClient;
74
76
  exports.createApiClient = index.createApiClient;
77
+ exports.extractBetweenBraces = index.extractBetweenBraces;
78
+ exports.extractOutsideBraces = index.extractOutsideBraces;
75
79
  exports.theme = index.theme;
76
80
  exports.useManagedModals = index.useManagedModals;
77
81
  exports.useModal = index.useModal;
82
+ exports.useUrlFilters = index.useUrlFilters;
83
+ exports.withProviders = index.withProviders;
@@ -1,4 +1,4 @@
1
- import { g as getDefaultExportFromCjs } from './index-9HtrJaD9.js';
1
+ import { g as getDefaultExportFromCjs } from './index-BlLQ1b_6.js';
2
2
 
3
3
  function _mergeNamespaces(n, m) {
4
4
  m.forEach(function (e) {
@@ -7,7 +7,7 @@ import _extends from '@babel/runtime/helpers/extends';
7
7
  import HighchartsReact from 'highcharts-react-official';
8
8
  import HighchartsRounded from 'highcharts-rounded-corners';
9
9
  import * as IconSax from 'iconsax-react';
10
- import { Link } from 'react-router-dom';
10
+ import { Link, useSearchParams } from 'react-router-dom';
11
11
  import { useDisclosure, useToggle } from '@mantine/hooks';
12
12
  import { useForm } from '@mantine/form';
13
13
  import '@fontsource/poppins/100.css';
@@ -21,6 +21,8 @@ import '@fontsource/poppins/800.css';
21
21
  import axios from 'axios';
22
22
  import { DatesProvider } from '@mantine/dates';
23
23
  import '@mantine/dates/styles.css';
24
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
25
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
24
26
 
25
27
  function _mergeNamespaces(n, m) {
26
28
  m.forEach(function (e) {
@@ -12149,7 +12151,7 @@ const initChart$1 = props => {
12149
12151
  }
12150
12152
 
12151
12153
  // Fallback: use dynamic import (async, but will work in Vite/Storybook)
12152
- import('./heatmap-Dfb5-pNh.js').then(function (n) { return n.h; }).then(heatmapModule => {
12154
+ import('./heatmap-CZ2o0bqY.js').then(function (n) { return n.h; }).then(heatmapModule => {
12153
12155
  const moduleFn = typeof heatmapModule === "function" ? heatmapModule : heatmapModule?.default || heatmapModule;
12154
12156
  if (typeof moduleFn === "function") {
12155
12157
  moduleFn(Highcharts);
@@ -13712,19 +13714,32 @@ const SimpleTable = _ref => {
13712
13714
  actionFn,
13713
13715
  isStriped
13714
13716
  } = _ref;
13715
- columns.sort((a, b) => a.order - b.order);
13716
- const indexColumn = {
13717
- order: -99,
13718
- id: "index",
13719
- label: "Id"
13720
- };
13721
- const actionColumn = {
13722
- order: 99999,
13723
- id: "action",
13724
- label: ""
13725
- };
13726
- withIndex ? columns.unshift(indexColumn) : null;
13727
- withAction && actionFn ? columns.push(actionColumn) : null;
13717
+ // Memoize the final columns array to avoid mutations on every render
13718
+ const finalColumns = useMemo(() => {
13719
+ // Create a new array to avoid mutating the original
13720
+ const sortedColumns = [...columns].sort((a, b) => a.order - b.order);
13721
+ const indexColumn = {
13722
+ order: -99,
13723
+ id: "index",
13724
+ label: "Id"
13725
+ };
13726
+ const actionColumn = {
13727
+ order: 99999,
13728
+ id: "action",
13729
+ label: ""
13730
+ };
13731
+
13732
+ // Build final array without mutations
13733
+ const result = [];
13734
+ if (withIndex) {
13735
+ result.push(indexColumn);
13736
+ }
13737
+ result.push(...sortedColumns);
13738
+ if (withAction && actionFn) {
13739
+ result.push(actionColumn);
13740
+ }
13741
+ return result;
13742
+ }, [columns, withIndex, withAction, actionFn]);
13728
13743
  const isStripedProps = {
13729
13744
  striped: "even",
13730
13745
  highlightOnHover: true,
@@ -13733,7 +13748,7 @@ const SimpleTable = _ref => {
13733
13748
  };
13734
13749
  return /*#__PURE__*/React__default.createElement(Table, isStriped && isStripedProps, /*#__PURE__*/React__default.createElement(Table.Thead, null, /*#__PURE__*/React__default.createElement(Table.Tr, {
13735
13750
  bg: isStriped ? "gray.1" : "transparent"
13736
- }, columns.map((column, index) => {
13751
+ }, finalColumns.map((column, index) => {
13737
13752
  return /*#__PURE__*/React__default.createElement(SimpleHeader, _extends({
13738
13753
  key: `column-${index}`
13739
13754
  }, column));
@@ -13741,7 +13756,7 @@ const SimpleTable = _ref => {
13741
13756
  return /*#__PURE__*/React__default.createElement(SimpleBody, {
13742
13757
  key: index,
13743
13758
  row,
13744
- columns,
13759
+ columns: finalColumns,
13745
13760
  withAction,
13746
13761
  withIndex,
13747
13762
  actionFn,
@@ -14424,6 +14439,127 @@ const MAX_PAGE_SIZE = 100;
14424
14439
  */
14425
14440
  const DEFAULT_API_TIMEOUT = 30000;
14426
14441
 
14442
+ /**
14443
+ * Configuration for URL filter hook
14444
+ */
14445
+
14446
+ /**
14447
+ * Return type for useUrlFilters hook
14448
+ */
14449
+
14450
+ /**
14451
+ * Generic hook for managing URL-based filters with pagination
14452
+ *
14453
+ * @example
14454
+ * ```tsx
14455
+ * interface MyFilters {
14456
+ * page: number;
14457
+ * limit: number;
14458
+ * name?: string;
14459
+ * status?: string;
14460
+ * }
14461
+ *
14462
+ * const { filters, updateFilters, clearFilters } = useUrlFilters<MyFilters>({
14463
+ * parseFilters: (params) => ({
14464
+ * page: parseInt(params.get('page') || '1', 10),
14465
+ * limit: parseInt(params.get('limit') || '10', 10),
14466
+ * name: params.get('name') || undefined,
14467
+ * status: params.get('status') || undefined,
14468
+ * }),
14469
+ * serializeFilters: (filters, params) => {
14470
+ * if (filters.page) params.set('page', String(filters.page));
14471
+ * if (filters.limit) params.set('limit', String(filters.limit));
14472
+ * if (filters.name) params.set('name', filters.name);
14473
+ * if (filters.status) params.set('status', filters.status);
14474
+ * },
14475
+ * });
14476
+ * ```
14477
+ */
14478
+ function useUrlFilters(options) {
14479
+ const {
14480
+ defaultPageSize = DEFAULT_PAGE_SIZE,
14481
+ parseFilters,
14482
+ serializeFilters,
14483
+ hasActiveFilters: checkActiveFilters,
14484
+ toApiParams
14485
+ } = options;
14486
+ const [searchParams, setSearchParams] = useSearchParams();
14487
+ const filters = useMemo(() => {
14488
+ const parsed = parseFilters(searchParams);
14489
+
14490
+ // Ensure page and limit are always present with valid defaults
14491
+ const page = typeof parsed.page === "number" && parsed.page > 0 ? parsed.page : 1;
14492
+ const limit = typeof parsed.limit === "number" && parsed.limit > 0 ? parsed.limit : defaultPageSize;
14493
+ return {
14494
+ ...parsed,
14495
+ page,
14496
+ limit
14497
+ };
14498
+ }, [searchParams, parseFilters, defaultPageSize]);
14499
+ const updateFilters = newFilters => {
14500
+ const params = new URLSearchParams(searchParams);
14501
+
14502
+ // Merge new filters with existing filters
14503
+ const mergedFilters = {
14504
+ ...filters,
14505
+ ...newFilters
14506
+ };
14507
+
14508
+ // Determine if we should reset page to 1
14509
+ // Reset if: page is not explicitly set AND other filters are changing
14510
+ const hasNonPaginationChanges = Object.keys(newFilters).some(key => key !== "page" && key !== "limit");
14511
+ if (newFilters.page === undefined && hasNonPaginationChanges) {
14512
+ mergedFilters.page = 1; // Reset to page 1
14513
+ }
14514
+
14515
+ // Ensure page and limit are numbers
14516
+ if (typeof mergedFilters.page !== "number" || mergedFilters.page < 1) {
14517
+ mergedFilters.page = 1;
14518
+ }
14519
+ if (typeof mergedFilters.limit !== "number" || mergedFilters.limit < 1) {
14520
+ mergedFilters.limit = defaultPageSize;
14521
+ }
14522
+
14523
+ // Serialize all filters
14524
+ serializeFilters(mergedFilters, params);
14525
+ setSearchParams(params, {
14526
+ replace: true
14527
+ });
14528
+ };
14529
+ const clearFilters = () => {
14530
+ const params = new URLSearchParams();
14531
+ params.set("page", "1");
14532
+ params.set("limit", String(defaultPageSize));
14533
+ setSearchParams(params, {
14534
+ replace: true
14535
+ });
14536
+ };
14537
+ const hasActiveFilters = useMemo(() => {
14538
+ if (checkActiveFilters) {
14539
+ return checkActiveFilters(filters);
14540
+ }
14541
+ // Default: check if any non-pagination fields have values
14542
+ return Object.keys(filters).some(key => {
14543
+ if (key === "page" || key === "limit") return false;
14544
+ const value = filters[key];
14545
+ return value !== undefined && value !== null && value !== "";
14546
+ });
14547
+ }, [filters, checkActiveFilters]);
14548
+ const apiParams = useMemo(() => {
14549
+ if (toApiParams) {
14550
+ return toApiParams(filters);
14551
+ }
14552
+ return undefined;
14553
+ }, [filters, toApiParams]);
14554
+ return {
14555
+ filters,
14556
+ updateFilters,
14557
+ clearFilters,
14558
+ hasActiveFilters,
14559
+ apiParams
14560
+ };
14561
+ }
14562
+
14427
14563
  class ApiClient {
14428
14564
  constructor(config) {
14429
14565
  const {
@@ -14527,4 +14663,101 @@ const ModalContentWrapper = _ref => {
14527
14663
  }, children));
14528
14664
  };
14529
14665
 
14530
- export { ApplicationMenu as A, BasicHeatmap as B, CalendarHeatmap as C, DonutChart as D, EqualizerColumn as E, File as F, SimpleForm as G, MaskedTilePanel as H, InterpolatedHeatmap as I, TilePanel as J, useModal as K, LabelPanel as L, MultiAxisArea as M, useManagedModals as N, DEFAULT_PAGE_SIZE as O, PageTitle as P, MAX_PAGE_SIZE as Q, DEFAULT_API_TIMEOUT as R, SimpleColumn as S, TitledPanel as T, UserMenu as U, createApiClient as V, apiClient as W, ModalContentWrapper as X, StackedColumn as a, SimpleArea as b, StackedArea as c, Icons as d, DynamicLogo as e, DynamicShigaLogo as f, getDefaultExportFromCjs as g, SideMenu as h, SimplePanel as i, SearchPanel as j, AvatarLabelPanel as k, SimpleText as l, TitleWithIndex as m, ConnectionPanel as n, ApplicationPanel as o, SubscriptionPlans as p, PaymentMethod as q, PaymentMethodAdd as r, SimpleTable as s, theme as t, ErrorModal as u, InfoModal as v, SimpleModal as w, SuccessModal as x, TwoFactorModal as y, Drawer as z };
14666
+ /**
14667
+ * Default QueryClient configuration
14668
+ */
14669
+ const defaultQueryClient = new QueryClient({
14670
+ defaultOptions: {
14671
+ queries: {
14672
+ retry: 1,
14673
+ refetchOnWindowFocus: false,
14674
+ staleTime: 30000,
14675
+ // 30 seconds
14676
+ gcTime: 5 * 60 * 1000 // 5 minutes
14677
+ },
14678
+ mutations: {
14679
+ retry: false
14680
+ }
14681
+ }
14682
+ });
14683
+ /**
14684
+ * Higher-order component that wraps a component with all necessary providers:
14685
+ * - QueryClientProvider (React Query)
14686
+ * - MantineProvider (Mantine UI)
14687
+ * - DatesProvider (Mantine Dates)
14688
+ *
14689
+ * @param Component - Component to wrap
14690
+ * @param options - Optional configuration
14691
+ * @returns Wrapped component with all providers
14692
+ *
14693
+ * @example
14694
+ * ```tsx
14695
+ * // Using default configuration
14696
+ * const WrappedComponent = withProviders(MyComponent);
14697
+ *
14698
+ * // Using custom QueryClient
14699
+ * const customQueryClient = new QueryClient({ ... });
14700
+ * const WrappedComponent = withProviders(MyComponent, {
14701
+ * queryClient: customQueryClient,
14702
+ * });
14703
+ * ```
14704
+ */
14705
+ function withProviders(Component) {
14706
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
14707
+ const {
14708
+ queryClient = defaultQueryClient,
14709
+ enableDevtools = true,
14710
+ theme: customTheme = theme,
14711
+ datesSettings = {
14712
+ locale: "en",
14713
+ timezone: "UTC"
14714
+ }
14715
+ } = options;
14716
+ const WrappedComponent = props => {
14717
+ const showDevtools = enableDevtools && typeof process !== "undefined" && process.env?.NODE_ENV === "development";
14718
+ return /*#__PURE__*/React.createElement(QueryClientProvider, {
14719
+ client: queryClient
14720
+ }, /*#__PURE__*/React.createElement(MantineProvider, {
14721
+ theme: customTheme
14722
+ }, /*#__PURE__*/React.createElement(DatesProvider, {
14723
+ settings: datesSettings
14724
+ }, /*#__PURE__*/React.createElement(Component, props), showDevtools && /*#__PURE__*/React.createElement(ReactQueryDevtools, null))));
14725
+ };
14726
+ WrappedComponent.displayName = `withProviders(${Component.displayName || Component.name || "Component"})`;
14727
+ return WrappedComponent;
14728
+ }
14729
+
14730
+ /**
14731
+ * String utility functions for text manipulation
14732
+ */
14733
+ const StringHelpers = {
14734
+ /**
14735
+ * Extracts text between curly braces from a string
14736
+ * @param input - Input string that may contain text in braces
14737
+ * @returns The text between braces, or empty string if not found
14738
+ * @example
14739
+ * extractBetweenBraces("Hello {world}") // returns "world"
14740
+ * extractBetweenBraces("No braces") // returns ""
14741
+ */
14742
+ extractBetweenBraces: input => {
14743
+ const regex = /^[^{]*\{([^}]*)\}[^{]*$/;
14744
+ const match = input.match(regex);
14745
+ return match ? match[1] : "";
14746
+ },
14747
+ /**
14748
+ * Removes text within curly braces from a string
14749
+ * @param input - Input string that may contain text in braces
14750
+ * @returns The string with braces and their content removed, trimmed
14751
+ * @example
14752
+ * extractOutsideBraces("Hello {world}") // returns "Hello"
14753
+ * extractOutsideBraces("No braces") // returns "No braces"
14754
+ */
14755
+ extractOutsideBraces: input => {
14756
+ const regex = /\{[^}]*\}/g;
14757
+ return input.replace(regex, "").trim();
14758
+ }
14759
+ };
14760
+ const extractBetweenBraces = StringHelpers.extractBetweenBraces;
14761
+ const extractOutsideBraces = StringHelpers.extractOutsideBraces;
14762
+
14763
+ export { extractOutsideBraces as $, ApplicationMenu as A, BasicHeatmap as B, CalendarHeatmap as C, DonutChart as D, EqualizerColumn as E, File as F, SimpleForm as G, MaskedTilePanel as H, InterpolatedHeatmap as I, TilePanel as J, useModal as K, LabelPanel as L, MultiAxisArea as M, useManagedModals as N, useUrlFilters as O, PageTitle as P, DEFAULT_PAGE_SIZE as Q, MAX_PAGE_SIZE as R, SimpleColumn as S, TitledPanel as T, UserMenu as U, DEFAULT_API_TIMEOUT as V, createApiClient as W, apiClient as X, ModalContentWrapper as Y, withProviders as Z, extractBetweenBraces as _, StackedColumn as a, SimpleArea as b, StackedArea as c, Icons as d, DynamicLogo as e, DynamicShigaLogo as f, getDefaultExportFromCjs as g, SideMenu as h, SimplePanel as i, SearchPanel as j, AvatarLabelPanel as k, SimpleText as l, TitleWithIndex as m, ConnectionPanel as n, ApplicationPanel as o, SubscriptionPlans as p, PaymentMethod as q, PaymentMethodAdd as r, SimpleTable as s, theme as t, ErrorModal as u, InfoModal as v, SimpleModal as w, SuccessModal as x, TwoFactorModal as y, Drawer as z };
@@ -1,4 +1,4 @@
1
- export { A as ApplicationMenu, o as ApplicationPanel, k as AvatarLabelPanel, B as BasicHeatmap, C as CalendarHeatmap, n as ConnectionPanel, R as DEFAULT_API_TIMEOUT, O as DEFAULT_PAGE_SIZE, D as DonutChart, z as Drawer, e as DynamicLogo, f as DynamicShigaLogo, E as EqualizerColumn, u as ErrorModal, F as File, d as Icons, v as InfoModal, I as InterpolatedHeatmap, L as LabelPanel, Q as MAX_PAGE_SIZE, H as MaskedTilePanel, X as ModalContentWrapper, M as MultiAxisArea, P as PageTitle, q as PaymentMethod, r as PaymentMethodAdd, j as SearchPanel, h as SideMenu, b as SimpleArea, S as SimpleColumn, G as SimpleForm, w as SimpleModal, i as SimplePanel, s as SimpleTable, l as SimpleText, c as StackedArea, a as StackedColumn, p as SubscriptionPlans, x as SuccessModal, J as TilePanel, m as TitleWithIndex, T as TitledPanel, y as TwoFactorModal, U as UserMenu, W as apiClient, V as createApiClient, t as theme, N as useManagedModals, K as useModal } from './index-9HtrJaD9.js';
1
+ export { A as ApplicationMenu, o as ApplicationPanel, k as AvatarLabelPanel, B as BasicHeatmap, C as CalendarHeatmap, n as ConnectionPanel, V as DEFAULT_API_TIMEOUT, Q as DEFAULT_PAGE_SIZE, D as DonutChart, z as Drawer, e as DynamicLogo, f as DynamicShigaLogo, E as EqualizerColumn, u as ErrorModal, F as File, d as Icons, v as InfoModal, I as InterpolatedHeatmap, L as LabelPanel, R as MAX_PAGE_SIZE, H as MaskedTilePanel, Y as ModalContentWrapper, M as MultiAxisArea, P as PageTitle, q as PaymentMethod, r as PaymentMethodAdd, j as SearchPanel, h as SideMenu, b as SimpleArea, S as SimpleColumn, G as SimpleForm, w as SimpleModal, i as SimplePanel, s as SimpleTable, l as SimpleText, c as StackedArea, a as StackedColumn, p as SubscriptionPlans, x as SuccessModal, J as TilePanel, m as TitleWithIndex, T as TitledPanel, y as TwoFactorModal, U as UserMenu, X as apiClient, W as createApiClient, _ as extractBetweenBraces, $ as extractOutsideBraces, t as theme, N as useManagedModals, K as useModal, O as useUrlFilters, Z as withProviders } from './index-BlLQ1b_6.js';
2
2
  import '@mantine/modals';
3
3
  import 'react';
4
4
  import '@mantine/core';
@@ -21,3 +21,5 @@ import '@fontsource/poppins/800.css';
21
21
  import 'axios';
22
22
  import '@mantine/dates';
23
23
  import '@mantine/dates/styles.css';
24
+ import '@tanstack/react-query';
25
+ import '@tanstack/react-query-devtools';
@@ -1,5 +1,9 @@
1
1
  export * from "./components";
2
2
  export * from "./shared/hooks/modals/useModal";
3
3
  export * from "./shared/hooks/modals/useManagedModals";
4
+ export * from "./shared/hooks";
4
5
  export * from "./shared/api";
5
6
  export * from "./shared/components";
7
+ export * from "./shared/types";
8
+ export * from "./shared/hocs";
9
+ export * from "./shared/utils";
@@ -14,3 +14,13 @@ export interface ApiError {
14
14
  message: string;
15
15
  error: string;
16
16
  }
17
+ /**
18
+ * Paginated API response
19
+ * Combines ApiResponse structure with pagination metadata
20
+ */
21
+ export interface PaginatedApiResponse<T> extends Omit<ApiResponse<T[]>, "data" | "total" | "page" | "limit"> {
22
+ data: T[];
23
+ total: number;
24
+ page: number;
25
+ limit: number;
26
+ }
@@ -0,0 +1 @@
1
+ export * from "./withProviders";
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+ import { theme } from "../../components/theme";
3
+ import { QueryClient } from "@tanstack/react-query";
4
+ import "@mantine/dates/styles.css";
5
+ export interface WithProvidersOptions {
6
+ /**
7
+ * Custom QueryClient instance (optional)
8
+ * If not provided, uses default configuration
9
+ */
10
+ queryClient?: QueryClient;
11
+ /**
12
+ * Whether to show React Query Devtools in development (default: true)
13
+ */
14
+ enableDevtools?: boolean;
15
+ /**
16
+ * Mantine theme override (optional)
17
+ */
18
+ theme?: typeof theme;
19
+ /**
20
+ * DatesProvider settings override (optional)
21
+ */
22
+ datesSettings?: {
23
+ locale: string;
24
+ timezone: string;
25
+ };
26
+ }
27
+ /**
28
+ * Higher-order component that wraps a component with all necessary providers:
29
+ * - QueryClientProvider (React Query)
30
+ * - MantineProvider (Mantine UI)
31
+ * - DatesProvider (Mantine Dates)
32
+ *
33
+ * @param Component - Component to wrap
34
+ * @param options - Optional configuration
35
+ * @returns Wrapped component with all providers
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // Using default configuration
40
+ * const WrappedComponent = withProviders(MyComponent);
41
+ *
42
+ * // Using custom QueryClient
43
+ * const customQueryClient = new QueryClient({ ... });
44
+ * const WrappedComponent = withProviders(MyComponent, {
45
+ * queryClient: customQueryClient,
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function withProviders<P extends object>(Component: React.ComponentType<P>, options?: WithProvidersOptions): React.ComponentType<P>;
@@ -0,0 +1 @@
1
+ export * from "./useUrlFilters";
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Configuration for URL filter hook
3
+ */
4
+ export interface UseUrlFiltersOptions<TFilters extends Record<string, any>> {
5
+ /**
6
+ * Default page size (defaults to DEFAULT_PAGE_SIZE from constants)
7
+ */
8
+ defaultPageSize?: number;
9
+ /**
10
+ * Function to parse filter values from URL search params
11
+ * @param searchParams - URLSearchParams object
12
+ * @returns Parsed filter object
13
+ */
14
+ parseFilters: (searchParams: URLSearchParams) => Partial<TFilters>;
15
+ /**
16
+ * Function to serialize filter values to URL search params
17
+ * @param filters - Filter object
18
+ * @param params - URLSearchParams object to update
19
+ */
20
+ serializeFilters: (filters: Partial<TFilters>, params: URLSearchParams) => void;
21
+ /**
22
+ * Function to check if any filters are active (excluding pagination)
23
+ * @param filters - Filter object
24
+ * @returns true if any filters are active
25
+ */
26
+ hasActiveFilters?: (filters: TFilters) => boolean;
27
+ /**
28
+ * Function to convert filters to API parameters
29
+ * @param filters - Filter object
30
+ * @returns API parameters object
31
+ */
32
+ toApiParams?: (filters: TFilters) => Record<string, any>;
33
+ }
34
+ /**
35
+ * Return type for useUrlFilters hook
36
+ */
37
+ export interface UseUrlFiltersReturn<TFilters extends Record<string, any>> {
38
+ /**
39
+ * Current filter values (includes page and limit)
40
+ */
41
+ filters: TFilters;
42
+ /**
43
+ * Update filters in URL
44
+ * @param newFilters - Partial filter object to update
45
+ */
46
+ updateFilters: (newFilters: Partial<TFilters>) => void;
47
+ /**
48
+ * Clear all filters (resets to default page and limit)
49
+ */
50
+ clearFilters: () => void;
51
+ /**
52
+ * Whether any filters are currently active (excluding pagination)
53
+ */
54
+ hasActiveFilters: boolean;
55
+ /**
56
+ * API parameters derived from filters (if toApiParams provided)
57
+ */
58
+ apiParams?: Record<string, any>;
59
+ }
60
+ /**
61
+ * Generic hook for managing URL-based filters with pagination
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * interface MyFilters {
66
+ * page: number;
67
+ * limit: number;
68
+ * name?: string;
69
+ * status?: string;
70
+ * }
71
+ *
72
+ * const { filters, updateFilters, clearFilters } = useUrlFilters<MyFilters>({
73
+ * parseFilters: (params) => ({
74
+ * page: parseInt(params.get('page') || '1', 10),
75
+ * limit: parseInt(params.get('limit') || '10', 10),
76
+ * name: params.get('name') || undefined,
77
+ * status: params.get('status') || undefined,
78
+ * }),
79
+ * serializeFilters: (filters, params) => {
80
+ * if (filters.page) params.set('page', String(filters.page));
81
+ * if (filters.limit) params.set('limit', String(filters.limit));
82
+ * if (filters.name) params.set('name', filters.name);
83
+ * if (filters.status) params.set('status', filters.status);
84
+ * },
85
+ * });
86
+ * ```
87
+ */
88
+ export declare function useUrlFilters<TFilters extends Record<string, any>>(options: UseUrlFiltersOptions<TFilters>): UseUrlFiltersReturn<TFilters>;
@@ -0,0 +1 @@
1
+ export * from "./pagination";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Standard pagination parameters for API requests
3
+ */
4
+ export interface PaginationParams {
5
+ page?: number;
6
+ limit?: number;
7
+ }
8
+ /**
9
+ * Standard paginated response structure
10
+ */
11
+ export interface PaginatedResponse<T> {
12
+ data: T[];
13
+ total: number;
14
+ page: number;
15
+ limit: number;
16
+ }
@@ -0,0 +1 @@
1
+ export * from "./stringHelpers";
@@ -0,0 +1,2 @@
1
+ export declare const extractBetweenBraces: (input: string) => string;
2
+ export declare const extractOutsideBraces: (input: string) => string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adiba-banking-cloud/backoffice",
3
3
  "author": "TUROG Technologies",
4
- "version": "0.2.0",
4
+ "version": "0.2.3",
5
5
  "description": "An ADIBA component library for backoffice and dashboard applications",
6
6
  "license": "ISC",
7
7
  "main": "build/index.cjs.js",
@@ -42,13 +42,17 @@
42
42
  "iconsax-react": "^0.0.8",
43
43
  "react": ">=18",
44
44
  "react-dom": ">=18",
45
- "react-router-dom": "^7.6.2"
45
+ "react-router-dom": "^7.6.2",
46
+ "@tanstack/react-query": "^5.0.0",
47
+ "@tanstack/react-query-devtools": "^5.0.0"
46
48
  },
47
49
  "repository": {
48
50
  "type": "git",
49
51
  "url": "git+https://github.com/turog-technologies/adiba-common-libraries.git"
50
52
  },
51
53
  "devDependencies": {
54
+ "@tanstack/react-query": "^5.0.0",
55
+ "@tanstack/react-query-devtools": "^5.0.0",
52
56
  "axios": "^1.6.0",
53
57
  "@babel/core": "^7.21.3",
54
58
  "@babel/plugin-transform-runtime": "^7.21.0",