@jasperoosthoek/react-toolbox 0.8.0 → 0.9.0

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 (63) hide show
  1. package/change-log.md +330 -309
  2. package/dist/components/buttons/ConfirmButton.d.ts +2 -2
  3. package/dist/components/buttons/DeleteConfirmButton.d.ts +2 -2
  4. package/dist/components/buttons/IconButtons.d.ts +40 -41
  5. package/dist/components/errors/Errors.d.ts +1 -2
  6. package/dist/components/forms/FormField.d.ts +22 -0
  7. package/dist/components/forms/FormFields.d.ts +1 -56
  8. package/dist/components/forms/FormModal.d.ts +7 -34
  9. package/dist/components/forms/FormModalProvider.d.ts +19 -26
  10. package/dist/components/forms/FormProvider.d.ts +66 -0
  11. package/dist/components/forms/fields/FormBadgesSelection.d.ts +26 -0
  12. package/dist/components/forms/fields/FormCheckbox.d.ts +7 -0
  13. package/dist/components/forms/fields/FormDropdown.d.ts +19 -0
  14. package/dist/components/forms/fields/FormInput.d.ts +17 -0
  15. package/dist/components/forms/fields/FormSelect.d.ts +12 -0
  16. package/dist/components/forms/fields/index.d.ts +5 -0
  17. package/dist/components/indicators/CheckIndicator.d.ts +1 -2
  18. package/dist/components/indicators/LoadingIndicator.d.ts +4 -4
  19. package/dist/components/login/LoginPage.d.ts +1 -1
  20. package/dist/components/tables/DataTable.d.ts +2 -2
  21. package/dist/components/tables/DragAndDropList.d.ts +2 -2
  22. package/dist/components/tables/SearchBox.d.ts +2 -2
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.js +2 -2
  25. package/dist/index.js.LICENSE.txt +0 -4
  26. package/dist/localization/LocalizationContext.d.ts +1 -1
  27. package/dist/utils/hooks.d.ts +1 -1
  28. package/dist/utils/timeAndDate.d.ts +5 -2
  29. package/dist/utils/utils.d.ts +3 -3
  30. package/package.json +10 -11
  31. package/src/__tests__/buttons.test.tsx +545 -0
  32. package/src/__tests__/errors.test.tsx +339 -0
  33. package/src/__tests__/forms.test.tsx +3021 -0
  34. package/src/__tests__/hooks.test.tsx +413 -0
  35. package/src/__tests__/indicators.test.tsx +284 -0
  36. package/src/__tests__/localization.test.tsx +462 -0
  37. package/src/__tests__/login.test.tsx +417 -0
  38. package/src/__tests__/setupTests.ts +328 -0
  39. package/src/__tests__/tables.test.tsx +609 -0
  40. package/src/__tests__/timeAndDate.test.tsx +308 -0
  41. package/src/__tests__/utils.test.tsx +422 -0
  42. package/src/components/forms/FormField.tsx +92 -0
  43. package/src/components/forms/FormFields.tsx +3 -423
  44. package/src/components/forms/FormModal.tsx +168 -243
  45. package/src/components/forms/FormModalProvider.tsx +141 -95
  46. package/src/components/forms/FormProvider.tsx +218 -0
  47. package/src/components/forms/fields/FormBadgesSelection.tsx +108 -0
  48. package/src/components/forms/fields/FormCheckbox.tsx +76 -0
  49. package/src/components/forms/fields/FormDropdown.tsx +123 -0
  50. package/src/components/forms/fields/FormInput.tsx +114 -0
  51. package/src/components/forms/fields/FormSelect.tsx +47 -0
  52. package/src/components/forms/fields/index.ts +6 -0
  53. package/src/index.ts +32 -28
  54. package/src/localization/LocalizationContext.tsx +156 -131
  55. package/src/localization/localization.ts +131 -131
  56. package/src/utils/hooks.ts +108 -94
  57. package/src/utils/timeAndDate.ts +33 -4
  58. package/src/utils/utils.ts +74 -66
  59. package/dist/components/forms/CreateEditModal.d.ts +0 -41
  60. package/dist/components/forms/CreateEditModalProvider.d.ts +0 -41
  61. package/dist/components/forms/FormFields.test.d.ts +0 -4
  62. package/dist/login/Login.d.ts +0 -70
  63. package/src/components/forms/FormFields.test.tsx +0 -107
@@ -1,131 +1,131 @@
1
- export type LocalizationFunction = (...args: (string | number)[]) => string;
2
-
3
- export type LocalizationElement = {
4
- [languageString: string]: (string | LocalizationFunction)
5
- }
6
-
7
- export type AdditionalLocalization = {
8
- [lang: string]: LocalizationElement;
9
- }
10
-
11
- export interface LocalizationStrings {
12
- select: string;
13
- search: string;
14
- no_information_to_display: string;
15
- information_is_being_loaded: string;
16
- delete: string;
17
- are_you_sure: string;
18
- close: string;
19
- save: string;
20
- cancel: string;
21
- ok: string;
22
- your_email: string;
23
- enter_email: string;
24
- your_password: string;
25
- enter_password: string;
26
- login: string;
27
- logout: string;
28
- forgot_password: string;
29
- reset_password: string;
30
- required_field: string;
31
- choose_one: string;
32
- everything: string;
33
- number_of_rows: string;
34
- modal_create: string;
35
- modal_edit: string;
36
- }
37
-
38
- export type Localization = {
39
- [lang: string]: LocalizationStrings;
40
- }
41
-
42
- export type Languages = {
43
- [languageIso: string]: string;
44
- }
45
- export const defaultLanguages: Languages = {
46
- en: "English",
47
- fr: "Français",
48
- nl: "Nederlands",
49
- } as const;
50
-
51
- export const defaultLocalization: Localization = {
52
- en: {
53
- select: "Select",
54
- search: "Search",
55
- no_information_to_display: "No information to display",
56
- information_is_being_loaded: "The information is being loaded...",
57
- delete: "Delete",
58
- are_you_sure: "Are you sure?",
59
- close: "Close",
60
- save: "Save",
61
- cancel: "Cancel",
62
- ok: "OK",
63
- your_email: "Your email",
64
- enter_email: "Enter email address",
65
- your_password: "Your password",
66
- enter_password: "Enter password",
67
- login: "Login",
68
- logout: "Logout",
69
- forgot_password: "Forgot password?",
70
- reset_password: "Reset password",
71
- required_field: "required",
72
- choose_one: "Choose one",
73
- everything: "Everything",
74
- number_of_rows: "Number of rows",
75
- modal_create: 'New',
76
- modal_edit: 'Edit',
77
- },
78
- fr: {
79
- no_information_to_display: "Sélectionner",
80
- select: "Recherche",
81
- search: "Aucune information à afficher",
82
- information_is_being_loaded: "L'information est en cours de téléchargement...",
83
- delete: "Supprimer",
84
- are_you_sure: "Es-tu sûr?",
85
- close: "Fermer",
86
- save: "Sauvegarder",
87
- cancel: "Annuler",
88
- ok: "D'accord'",
89
- your_email: "Votre adresse e-mail",
90
- enter_email: "Entrez l'adresse e-mail",
91
- your_password: "Votre mot de passe",
92
- enter_password: "Entrez votre mot de passe",
93
- login: "Connexion",
94
- logout: "Se déconnecter",
95
- forgot_password: "Mot de passe oublié?",
96
- reset_password: "Réinitialiser le mot de passe",
97
- required_field: "requis",
98
- choose_one: "Choisissez-en un",
99
- number_of_rows: "Tout",
100
- everything: "Nombre de rangées",
101
- modal_create: 'Nouveau',
102
- modal_edit: 'Éditer',
103
- },
104
- nl: {
105
- no_information_to_display: "Geen informatie om weer te geven.",
106
- select: "Selecteer",
107
- search: "Zoeken",
108
- information_is_being_loaded: "De gegevens worden geladen...",
109
- delete: "Verwijderen",
110
- are_you_sure: "Weet u het zeker?",
111
- close: "Sluiten",
112
- save: "Opslaan",
113
- cancel: "Annuleren",
114
- ok: "OK",
115
- your_email: "Uw e-mail",
116
- enter_email: "Voer e-mailadres in",
117
- your_password: "Uw wachtwoord",
118
- enter_password: "Voer wachtwoord in",
119
- login: "Inloggen",
120
- logout: "Uitloggen",
121
- forgot_password: "Wachtwoord vergeten?",
122
- reset_password: "Wachtwoord resetten",
123
- required_field: "verplicht",
124
- choose_one: "Maak een keuze",
125
- number_of_rows: "Aantal rijen",
126
- everything: "Alles",
127
- modal_create: 'Nieuw',
128
- modal_edit: 'Bewerken',
129
- },
130
- } as const;
131
-
1
+ export type LocalizationFunction = (...args: (string | number)[]) => string;
2
+
3
+ export type LocalizationElement = {
4
+ [languageString: string]: (string | LocalizationFunction)
5
+ }
6
+
7
+ export type AdditionalLocalization = {
8
+ [lang: string]: LocalizationElement;
9
+ }
10
+
11
+ export interface LocalizationStrings {
12
+ select: string;
13
+ search: string;
14
+ no_information_to_display: string;
15
+ information_is_being_loaded: string;
16
+ delete: string;
17
+ are_you_sure: string;
18
+ close: string;
19
+ save: string;
20
+ cancel: string;
21
+ ok: string;
22
+ your_email: string;
23
+ enter_email: string;
24
+ your_password: string;
25
+ enter_password: string;
26
+ login: string;
27
+ logout: string;
28
+ forgot_password: string;
29
+ reset_password: string;
30
+ required_field: string;
31
+ choose_one: string;
32
+ everything: string;
33
+ number_of_rows: string;
34
+ modal_create: string;
35
+ modal_edit: string;
36
+ }
37
+
38
+ export type Localization = {
39
+ [lang: string]: LocalizationStrings;
40
+ }
41
+
42
+ export type Languages = {
43
+ [languageIso: string]: string;
44
+ }
45
+ export const defaultLanguages: Languages = {
46
+ en: "English",
47
+ fr: "Français",
48
+ nl: "Nederlands",
49
+ } as const;
50
+
51
+ export const defaultLocalization: Localization = {
52
+ en: {
53
+ select: "Select",
54
+ search: "Search",
55
+ no_information_to_display: "No information to display",
56
+ information_is_being_loaded: "The information is being loaded...",
57
+ delete: "Delete",
58
+ are_you_sure: "Are you sure?",
59
+ close: "Close",
60
+ save: "Save",
61
+ cancel: "Cancel",
62
+ ok: "OK",
63
+ your_email: "Your email",
64
+ enter_email: "Enter email address",
65
+ your_password: "Your password",
66
+ enter_password: "Enter password",
67
+ login: "Login",
68
+ logout: "Logout",
69
+ forgot_password: "Forgot password?",
70
+ reset_password: "Reset password",
71
+ required_field: "required",
72
+ choose_one: "Choose one",
73
+ everything: "Everything",
74
+ number_of_rows: "Number of rows",
75
+ modal_create: 'New',
76
+ modal_edit: 'Edit',
77
+ },
78
+ fr: {
79
+ select: "Sélectionner",
80
+ search: "Recherche",
81
+ no_information_to_display: "Aucune information à afficher",
82
+ information_is_being_loaded: "L'information est en cours de téléchargement...",
83
+ delete: "Supprimer",
84
+ are_you_sure: "Êtes-vous sûr?",
85
+ close: "Fermer",
86
+ save: "Sauvegarder",
87
+ cancel: "Annuler",
88
+ ok: "D'accord'",
89
+ your_email: "Votre adresse e-mail",
90
+ enter_email: "Entrez l'adresse e-mail",
91
+ your_password: "Votre mot de passe",
92
+ enter_password: "Entrez votre mot de passe",
93
+ login: "Connexion",
94
+ logout: "Se déconnecter",
95
+ forgot_password: "Mot de passe oublié?",
96
+ reset_password: "Réinitialiser le mot de passe",
97
+ required_field: "requis",
98
+ choose_one: "Choisissez-en un",
99
+ everything: "Tout",
100
+ number_of_rows: "Nombre de rangées",
101
+ modal_create: 'Nouveau',
102
+ modal_edit: 'Éditer',
103
+ },
104
+ nl: {
105
+ select: "Selecteer",
106
+ search: "Zoeken",
107
+ no_information_to_display: "Geen informatie om weer te geven.",
108
+ information_is_being_loaded: "De gegevens worden geladen...",
109
+ delete: "Verwijderen",
110
+ are_you_sure: "Weet u het zeker?",
111
+ close: "Sluiten",
112
+ save: "Opslaan",
113
+ cancel: "Annuleren",
114
+ ok: "OK",
115
+ your_email: "Uw e-mail",
116
+ enter_email: "Voer e-mailadres in",
117
+ your_password: "Uw wachtwoord",
118
+ enter_password: "Voer wachtwoord in",
119
+ login: "Inloggen",
120
+ logout: "Uitloggen",
121
+ forgot_password: "Wachtwoord vergeten?",
122
+ reset_password: "Wachtwoord resetten",
123
+ required_field: "verplicht",
124
+ choose_one: "Maak een keuze",
125
+ everything: "Alles",
126
+ number_of_rows: "Aantal rijen",
127
+ modal_create: 'Nieuw',
128
+ modal_edit: 'Bewerken',
129
+ },
130
+ } as const;
131
+
@@ -1,95 +1,109 @@
1
- import React, { useRef, useEffect, useState, useCallback } from 'react';
2
-
3
- // https://stackoverflow.com/questions/53446020/how-to-compare-oldvalues-and-newvalues-on-react-hooks-useeffect
4
- export const usePrevious = <T>(value: T): T | undefined => {
5
- // Explicitly set initial value to `undefined`
6
- const ref = useRef<T | undefined>(undefined);
7
-
8
- useEffect(() => {
9
- ref.current = value;
10
- });
11
- return ref.current;
12
- };
13
-
14
- // https://stackoverflow.com/questions/54666401/how-to-use-throttle-or-debounce-with-react-hook
15
- export const useDebouncedEffect = (effect: () => void, deps: any[], delay: number) => {
16
- useEffect(() => {
17
- const handler = setTimeout(() => effect(), delay);
18
-
19
- return () => clearTimeout(handler);
20
- }, [...(deps || []), delay, effect]);
21
- };
22
-
23
- // https://stackoverflow.com/questions/30626030/can-you-force-a-react-component-to-rerender-without-calling-setstate
24
- export const useForceUpdate = () => {
25
- const [, updateState] = useState<any>(null);
26
- return useCallback(() => updateState({}), []);
27
- }
28
-
29
- export const useSetState = <T>(initialState: T): [T, (subState: Partial<T>) => void] => {
30
- const [state, setState] = useState(initialState);
31
- const [callback, setCallback] = useState<() => void | undefined>();
32
- useEffect(() => {
33
- if (typeof callback === 'function') callback();
34
- }, [callback]);
35
-
36
- const setSubState = (obj: Partial<T>, callback?: () => void) => {
37
- setState({ ...state, ...obj });
38
- if (callback) setCallback(callback);
39
- }
40
- return [state, setSubState];
41
- }
42
-
43
- // https://devtrium.com/posts/set-interval-react
44
- export const useInterval = (func: () => void, value: number) => useEffect(() => {
45
- if (typeof func !== 'function') {
46
- throw('First argument of useInterval should be a function');
47
- } else if(
48
- typeof value !== 'number'
49
- || !isFinite(value)
50
- || value <= 0
51
- ) {
52
- throw('Second argument of useInterval should be a positive number');
53
- }
54
- const interval = setInterval(func, value);
55
-
56
- return () => clearInterval(interval);
57
- }, []);
58
-
59
- export const useLocalStorage = <T,>(key: string, initialValue: T): [T, (value: T) => void] => {
60
- const initialLocalStorageValue = localStorage.getItem(key);
61
-
62
- if (initialLocalStorageValue === null) {
63
- localStorage.setItem(key, JSON.stringify(initialValue));
64
- }
65
-
66
- const [state, setState] = useState<T>(
67
- initialLocalStorageValue
68
- ? JSON.parse(initialLocalStorageValue) as T
69
- : initialValue
70
- );
71
-
72
- useEffect(() => {
73
- const handleStorageChange = (event: StorageEvent) => {
74
- if (event.key === key && event.newValue) {
75
- setState(JSON.parse(event.newValue) as T);
76
- }
77
- };
78
-
79
- // Manually dispatch an event to update components in the same document
80
- window.addEventListener('storage', handleStorageChange);
81
- return () => window.removeEventListener('storage', handleStorageChange);
82
- }, [key]);
83
-
84
- const setLocalStorage = (value: T) => {
85
- setState(value);
86
- localStorage.setItem(key, JSON.stringify(value));
87
- window.dispatchEvent(new StorageEvent('storage', {
88
- key,
89
- newValue: JSON.stringify(value),
90
- oldValue: localStorage.getItem(key),
91
- }));
92
- };
93
-
94
- return [state, setLocalStorage];
1
+ import React, { useRef, useEffect, useState, useCallback } from 'react';
2
+
3
+ // https://stackoverflow.com/questions/53446020/how-to-compare-oldvalues-and-newvalues-on-react-hooks-useeffect
4
+ export const usePrevious = <T>(value: T): T | undefined => {
5
+ // Explicitly set initial value to `undefined`
6
+ const ref = useRef<T | undefined>(undefined);
7
+
8
+ useEffect(() => {
9
+ ref.current = value;
10
+ });
11
+ return ref.current;
12
+ };
13
+
14
+ // https://stackoverflow.com/questions/54666401/how-to-use-throttle-or-debounce-with-react-hook
15
+ export const useDebouncedEffect = (effect: () => void, deps: any[], delay: number) => {
16
+ useEffect(() => {
17
+ const handler = setTimeout(() => effect(), delay);
18
+
19
+ return () => clearTimeout(handler);
20
+ }, [...(deps || []), delay]);
21
+ };
22
+
23
+ // https://stackoverflow.com/questions/30626030/can-you-force-a-react-component-to-rerender-without-calling-setstate
24
+ export const useForceUpdate = () => {
25
+ const [, updateState] = useState<any>(null);
26
+ return useCallback(() => updateState({}), []);
27
+ }
28
+
29
+ export const useSetState = <T>(initialState: T): [T, (subState: Partial<T>, callback?: () => void) => void] => {
30
+ const [state, setState] = useState(initialState);
31
+
32
+ const setSubState = useCallback((obj: Partial<T>, callback?: () => void) => {
33
+ setState(prevState => {
34
+ const newState = { ...prevState, ...obj };
35
+ return newState;
36
+ });
37
+ if (callback) {
38
+ // Execute callback in next tick to ensure state is updated
39
+ Promise.resolve().then(callback);
40
+ }
41
+ }, []);
42
+
43
+ return [state, setSubState];
44
+ }
45
+
46
+ // https://devtrium.com/posts/set-interval-react
47
+ export const useInterval = (func: () => void, value: number) => useEffect(() => {
48
+ if (typeof func !== 'function') {
49
+ throw('First argument of useInterval should be a function');
50
+ } else if(
51
+ typeof value !== 'number'
52
+ || !isFinite(value)
53
+ || value <= 0
54
+ ) {
55
+ throw('Second argument of useInterval should be a positive number');
56
+ }
57
+ const interval = setInterval(func, value);
58
+
59
+ return () => clearInterval(interval);
60
+ }, []);
61
+
62
+ export const useLocalStorage = <T,>(key: string, initialValue: T): [T, (value: T) => void] => {
63
+ // Get initial value from localStorage or use provided initial value
64
+ const [state, setState] = useState<T>(() => {
65
+ try {
66
+ const item = localStorage.getItem(key);
67
+ if (item === null) {
68
+ localStorage.setItem(key, JSON.stringify(initialValue));
69
+ return initialValue;
70
+ }
71
+ return JSON.parse(item) as T;
72
+ } catch (error) {
73
+ console.error('Error reading from localStorage:', error);
74
+ return initialValue;
75
+ }
76
+ });
77
+
78
+ useEffect(() => {
79
+ const handleStorageChange = (event: StorageEvent) => {
80
+ if (event.key === key && event.newValue) {
81
+ try {
82
+ setState(JSON.parse(event.newValue) as T);
83
+ } catch (error) {
84
+ console.error('Error parsing localStorage value:', error);
85
+ }
86
+ }
87
+ };
88
+
89
+ window.addEventListener('storage', handleStorageChange);
90
+ return () => window.removeEventListener('storage', handleStorageChange);
91
+ }, [key]);
92
+
93
+ const setLocalStorage = useCallback((value: T) => {
94
+ try {
95
+ setState(value);
96
+ localStorage.setItem(key, JSON.stringify(value));
97
+ // Dispatch storage event for components in the same window
98
+ window.dispatchEvent(new StorageEvent('storage', {
99
+ key,
100
+ newValue: JSON.stringify(value),
101
+ oldValue: localStorage.getItem(key),
102
+ }));
103
+ } catch (error) {
104
+ console.error('Error writing to localStorage:', error);
105
+ }
106
+ }, [key]);
107
+
108
+ return [state, setLocalStorage];
95
109
  };
@@ -1,4 +1,33 @@
1
- import moment from 'moment';
2
-
3
- export const getTimestamp = () => Math.round(new Date().getTime()/1000);
4
- export const getToday = () => moment().utc().startOf('day');
1
+ import { startOfDay, format, parseISO, isValid } from 'date-fns';
2
+ import { toZonedTime, fromZonedTime } from 'date-fns-tz';
3
+
4
+ export const getTimestamp = () => Math.round(new Date().getTime() / 1000);
5
+
6
+ export const getToday = () => {
7
+ const now = new Date();
8
+ const utcDate = toZonedTime(now, 'UTC');
9
+ return startOfDay(utcDate);
10
+ };
11
+
12
+ // Common date formatting utilities
13
+ export const formatDate = (date: Date | string, pattern: string = 'yyyy-MM-dd') => {
14
+ const dateObj = typeof date === 'string' ? parseISO(date) : date;
15
+ return isValid(dateObj) ? format(dateObj, pattern) : '';
16
+ };
17
+
18
+ export const formatDateTime = (date: Date | string, pattern: string = 'yyyy-MM-dd HH:mm') => {
19
+ const dateObj = typeof date === 'string' ? parseISO(date) : date;
20
+ return isValid(dateObj) ? format(dateObj, pattern) : '';
21
+ };
22
+
23
+ // Convert local date to UTC
24
+ export const toUtc = (date: Date | string, timezone?: string) => {
25
+ const dateObj = typeof date === 'string' ? parseISO(date) : date;
26
+ return timezone ? fromZonedTime(dateObj, timezone) : dateObj;
27
+ };
28
+
29
+ // Convert UTC date to local timezone
30
+ export const fromUtc = (date: Date | string, timezone: string) => {
31
+ const dateObj = typeof date === 'string' ? parseISO(date) : date;
32
+ return toZonedTime(dateObj, timezone);
33
+ };
@@ -1,66 +1,74 @@
1
- import axios, { AxiosInstance } from 'axios';
2
-
3
- export const isEmpty = (value: unknown) =>
4
- value === undefined
5
- || value === null
6
- || value === false
7
- || (typeof value === 'object' && Object.keys(value).length === 0)
8
- || (typeof value === 'string' && value.trim().length === 0);
9
-
10
-
11
- export const snakeToCamelCase = (str: string) => str.replace(
12
- /([-_][a-z])/g,
13
- (group) => group
14
- .replace('-', '')
15
- .replace('_', '')
16
- );
17
-
18
- export const camelToSnakeCase = (str: string) => (str
19
- .split(/(?=[A-Z])/)
20
- .map(x => x.toUpperCase())
21
- .join('_')
22
- );
23
-
24
- export const pluralToSingle = (str: string) => {
25
- if (str.slice(-1) !== 's') {
26
- // This string is not plural: keep it unaltered
27
- return str;
28
- } else if (str.slice(-3) === 'ies') {
29
- // Handle special case of categories
30
- return `${str.slice(0, -3)}y`;
31
- } else if (str.slice(-3) === 'IES') {
32
- // Same but in upper case
33
- return `${str.slice(0, -3)}Y`;
34
- } else {
35
- // Standard plural
36
- return str.slice(0, -1);
37
- }
38
- }
39
-
40
- export const arrayToObject = <T extends any[]>(array: T, byKey: string) => Object.fromEntries(array.map(obj => [obj[byKey], obj]));
41
-
42
- export const roundFixed = (str: string | number, decimals?: number) => parseFloat(`${str}`).toFixed(decimals || 0);
43
- export const round = (str: string | number, decimals?: number) => parseFloat(parseFloat(`${str}`).toFixed(decimals || 0));
44
-
45
- export type DownloadFileOptions = {
46
- axios: AxiosInstance;
47
- }
48
- export const downloadFile = (url: string, filename: string, options: DownloadFileOptions) => (
49
- // https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743
50
-
51
- (options.axios || axios.create())({
52
- url,
53
- method: 'GET',
54
- responseType: 'blob',
55
- }).then((response) => {
56
- const url = window.URL.createObjectURL(new Blob([response.data]));
57
- const link = document.createElement('a');
58
- link.href = url;
59
- link.setAttribute(
60
- 'download',
61
- filename,
62
- );
63
- document.body.appendChild(link);
64
- link.click();
65
- })
66
- );
1
+ export const isEmpty = (value: unknown) =>
2
+ value === undefined
3
+ || value === null
4
+ || value === false
5
+ || (typeof value === 'object' && Object.keys(value).length === 0)
6
+ || (typeof value === 'string' && value.trim().length === 0);
7
+
8
+
9
+ export const snakeToCamelCase = (str: string) => str.replace(
10
+ /([-_])(.)/g,
11
+ (match, separator, char) => char.toUpperCase()
12
+ );
13
+
14
+ export const camelToSnakeCase = (str: string) => {
15
+ return str
16
+ .replace(/([a-z])([A-Z])/g, '$1_$2') // Insert underscore between lowercase and uppercase
17
+ .replace(/([a-zA-Z])([0-9])/g, '$1_$2') // Insert underscore between letter and number
18
+ .replace(/([0-9])([a-zA-Z])/g, '$1_$2') // Insert underscore between number and letter
19
+ .toUpperCase();
20
+ };
21
+
22
+ export const pluralToSingle = (str: string) => {
23
+ if (str.slice(-1) !== 's' && str.slice(-1) !== 'S') {
24
+ // This string is not plural: keep it unaltered
25
+ return str;
26
+ } else if (str.slice(-3).toLowerCase() === 'ies') {
27
+ // Handle special case of categories (case insensitive)
28
+ const prefix = str.slice(0, -3);
29
+ const lastChar = str.slice(-3, -2); // Get the character before 'ies'
30
+ return `${prefix}${lastChar === lastChar.toUpperCase() ? 'Y' : 'y'}`;
31
+ } else {
32
+ // Standard plural
33
+ return str.slice(0, -1);
34
+ }
35
+ }
36
+
37
+ export const arrayToObject = <T extends any[]>(array: T, byKey: string) => Object.fromEntries(array.map(obj => [obj[byKey], obj]));
38
+
39
+ export const roundFixed = (str: string | number, decimals?: number) => parseFloat(`${str}`).toFixed(decimals || 0);
40
+ export const round = (str: string | number, decimals?: number) => parseFloat(parseFloat(`${str}`).toFixed(decimals || 0));
41
+
42
+ export type DownloadFileOptions = {
43
+ headers?: Record<string, string>;
44
+ fetchFn?: typeof fetch;
45
+ }
46
+
47
+ export const downloadFile = async (url: string, filename: string, options: DownloadFileOptions = {}) => {
48
+ try {
49
+ const fetchFn = options.fetchFn || fetch;
50
+ const response = await fetchFn(url, {
51
+ method: 'GET',
52
+ headers: options.headers || {},
53
+ });
54
+
55
+ if (!response.ok) {
56
+ throw new Error(`HTTP error! status: ${response.status}`);
57
+ }
58
+
59
+ const blob = await response.blob();
60
+ const downloadUrl = window.URL.createObjectURL(blob);
61
+ const link = document.createElement('a');
62
+ link.href = downloadUrl;
63
+ link.setAttribute('download', filename);
64
+ document.body.appendChild(link);
65
+ link.click();
66
+
67
+ // Cleanup
68
+ document.body.removeChild(link);
69
+ window.URL.revokeObjectURL(downloadUrl);
70
+ } catch (error) {
71
+ console.error('Download failed:', error);
72
+ throw error;
73
+ }
74
+ };