@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.
- package/change-log.md +330 -309
- package/dist/components/buttons/ConfirmButton.d.ts +2 -2
- package/dist/components/buttons/DeleteConfirmButton.d.ts +2 -2
- package/dist/components/buttons/IconButtons.d.ts +40 -41
- package/dist/components/errors/Errors.d.ts +1 -2
- package/dist/components/forms/FormField.d.ts +22 -0
- package/dist/components/forms/FormFields.d.ts +1 -56
- package/dist/components/forms/FormModal.d.ts +7 -34
- package/dist/components/forms/FormModalProvider.d.ts +19 -26
- package/dist/components/forms/FormProvider.d.ts +66 -0
- package/dist/components/forms/fields/FormBadgesSelection.d.ts +26 -0
- package/dist/components/forms/fields/FormCheckbox.d.ts +7 -0
- package/dist/components/forms/fields/FormDropdown.d.ts +19 -0
- package/dist/components/forms/fields/FormInput.d.ts +17 -0
- package/dist/components/forms/fields/FormSelect.d.ts +12 -0
- package/dist/components/forms/fields/index.d.ts +5 -0
- package/dist/components/indicators/CheckIndicator.d.ts +1 -2
- package/dist/components/indicators/LoadingIndicator.d.ts +4 -4
- package/dist/components/login/LoginPage.d.ts +1 -1
- package/dist/components/tables/DataTable.d.ts +2 -2
- package/dist/components/tables/DragAndDropList.d.ts +2 -2
- package/dist/components/tables/SearchBox.d.ts +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -2
- package/dist/index.js.LICENSE.txt +0 -4
- package/dist/localization/LocalizationContext.d.ts +1 -1
- package/dist/utils/hooks.d.ts +1 -1
- package/dist/utils/timeAndDate.d.ts +5 -2
- package/dist/utils/utils.d.ts +3 -3
- package/package.json +10 -11
- package/src/__tests__/buttons.test.tsx +545 -0
- package/src/__tests__/errors.test.tsx +339 -0
- package/src/__tests__/forms.test.tsx +3021 -0
- package/src/__tests__/hooks.test.tsx +413 -0
- package/src/__tests__/indicators.test.tsx +284 -0
- package/src/__tests__/localization.test.tsx +462 -0
- package/src/__tests__/login.test.tsx +417 -0
- package/src/__tests__/setupTests.ts +328 -0
- package/src/__tests__/tables.test.tsx +609 -0
- package/src/__tests__/timeAndDate.test.tsx +308 -0
- package/src/__tests__/utils.test.tsx +422 -0
- package/src/components/forms/FormField.tsx +92 -0
- package/src/components/forms/FormFields.tsx +3 -423
- package/src/components/forms/FormModal.tsx +168 -243
- package/src/components/forms/FormModalProvider.tsx +141 -95
- package/src/components/forms/FormProvider.tsx +218 -0
- package/src/components/forms/fields/FormBadgesSelection.tsx +108 -0
- package/src/components/forms/fields/FormCheckbox.tsx +76 -0
- package/src/components/forms/fields/FormDropdown.tsx +123 -0
- package/src/components/forms/fields/FormInput.tsx +114 -0
- package/src/components/forms/fields/FormSelect.tsx +47 -0
- package/src/components/forms/fields/index.ts +6 -0
- package/src/index.ts +32 -28
- package/src/localization/LocalizationContext.tsx +156 -131
- package/src/localization/localization.ts +131 -131
- package/src/utils/hooks.ts +108 -94
- package/src/utils/timeAndDate.ts +33 -4
- package/src/utils/utils.ts +74 -66
- package/dist/components/forms/CreateEditModal.d.ts +0 -41
- package/dist/components/forms/CreateEditModalProvider.d.ts +0 -41
- package/dist/components/forms/FormFields.test.d.ts +0 -4
- package/dist/login/Login.d.ts +0 -70
- 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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
information_is_being_loaded: "L'information est en cours de téléchargement...",
|
|
83
|
-
delete: "Supprimer",
|
|
84
|
-
are_you_sure: "
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
modal_create: 'Nouveau',
|
|
102
|
-
modal_edit: 'Éditer',
|
|
103
|
-
},
|
|
104
|
-
nl: {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
+
|
package/src/utils/hooks.ts
CHANGED
|
@@ -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
|
|
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
|
|
30
|
-
const [state, setState] = useState(initialState);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
};
|
package/src/utils/timeAndDate.ts
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export const
|
|
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
|
+
};
|
package/src/utils/utils.ts
CHANGED
|
@@ -1,66 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
value ===
|
|
5
|
-
|| value ===
|
|
6
|
-
|| value ===
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
return `${
|
|
31
|
-
} else
|
|
32
|
-
//
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export const
|
|
41
|
-
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
};
|