rl-core-front 0.18.7 → 0.18.9
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/package.json +1 -1
- package/src/_services/api/axios.factory.ts +3 -0
- package/src/_utils/storage.ts +31 -0
- package/src/components/app-shell.tsx +173 -114
- package/src/components/ui/column-visibility-modal.tsx +11 -4
- package/src/components/ui/combobox.tsx +46 -0
- package/src/components/ui/filter-sheet.tsx +9 -3
- package/src/contexts/auth-context.tsx +6 -1
- package/src/contexts/color-mode-context.tsx +29 -22
- package/src/contexts/i18n-context.tsx +5 -15
- package/src/contexts/socket-context.tsx +10 -1
- package/src/features/audit/components/record-audit-button.tsx +3 -1
- package/src/features/audit/hooks/use-audit-trail.ts +3 -1
- package/src/features/logs/hooks/use-request-logs.ts +3 -1
- package/src/features/notifications/hooks/use-notifications.ts +3 -1
- package/src/features/profile/profile-screen.tsx +3 -1
- package/src/features/queues/hooks/use-queue-jobs.ts +3 -1
- package/src/features/rbac/panels/catalog-panel.tsx +3 -1
- package/src/features/rbac/panels/groups-panel.tsx +3 -1
- package/src/features/rbac/panels/permissions-panel.tsx +3 -1
- package/src/features/rbac/panels/roles-panel.tsx +8 -4
- package/src/features/recovery/reset-password-screen.tsx +17 -5
- package/src/features/users/hooks/use-users.ts +3 -1
- package/src/hooks/use-column-visibility.ts +30 -18
- package/src/hooks/use-document-title.ts +49 -18
- package/src/hooks/use-filter-schema.ts +3 -1
- package/src/hooks/use-filters.ts +36 -10
- package/src/hooks/use-is-hydrated.ts +21 -0
- package/src/hooks/use-job-progress.ts +11 -4
- package/src/hooks/use-list-query.ts +9 -4
- package/src/hooks/use-stored-value.ts +35 -0
- package/src/hooks/use-tab-state.ts +62 -28
- package/src/hooks/use-table-view.ts +15 -17
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useSyncExternalStore,
|
|
9
|
+
} from "react";
|
|
4
10
|
|
|
5
11
|
import { safeStorage } from "#core/_utils/storage";
|
|
12
|
+
import { useIsHydrated } from "#core/hooks/use-is-hydrated";
|
|
13
|
+
import { useStoredValue } from "#core/hooks/use-stored-value";
|
|
6
14
|
|
|
7
15
|
const STORAGE_PREFIX = "tab:";
|
|
8
16
|
const DEFAULT_PARAM = "tab";
|
|
@@ -28,11 +36,30 @@ export interface UseTabStateResult<T extends string> {
|
|
|
28
36
|
const readFromUrl = (param: string): string | null =>
|
|
29
37
|
new URLSearchParams(window.location.search).get(param);
|
|
30
38
|
|
|
39
|
+
/** Quem lê a querystring, para saber que ela mudou por aqui. */
|
|
40
|
+
const urlListeners = new Set<() => void>();
|
|
41
|
+
|
|
42
|
+
const subscribeUrl = (onChange: () => void): (() => void) => {
|
|
43
|
+
urlListeners.add(onChange);
|
|
44
|
+
// O "voltar" do navegador também troca a aba, e esse não passa por aqui.
|
|
45
|
+
window.addEventListener("popstate", onChange);
|
|
46
|
+
return () => {
|
|
47
|
+
urlListeners.delete(onChange);
|
|
48
|
+
window.removeEventListener("popstate", onChange);
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
31
52
|
/** Troca só o próprio parâmetro, preservando o resto — o `?filter=` da listagem mora ao lado. */
|
|
32
53
|
const writeToUrl = (param: string, value: string): void => {
|
|
33
54
|
const params = new URLSearchParams(window.location.search);
|
|
34
55
|
params.set(param, value);
|
|
35
56
|
window.history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
|
57
|
+
|
|
58
|
+
// `replaceState` não dispara evento nenhum: sem este aviso, a tela ficaria
|
|
59
|
+
// na aba antiga até o próximo render por outro motivo.
|
|
60
|
+
for (const listener of urlListeners) {
|
|
61
|
+
listener();
|
|
62
|
+
}
|
|
36
63
|
};
|
|
37
64
|
|
|
38
65
|
/**
|
|
@@ -61,10 +88,25 @@ export function useTabState<T extends string>(
|
|
|
61
88
|
defaultTab: T = tabs[0],
|
|
62
89
|
{ param = DEFAULT_PARAM }: UseTabStateOptions = {},
|
|
63
90
|
): UseTabStateResult<T> {
|
|
64
|
-
|
|
91
|
+
// As duas memórias são de fora do React — a querystring e o armazenamento —,
|
|
92
|
+
// e por isso são lidas no próprio render, e não copiadas para um estado
|
|
93
|
+
// dentro de um efeito: no primeiro render do Next não há `window`, e a
|
|
94
|
+
// leitura do cliente entra assim que ele hidrata, sem um quadro na aba
|
|
95
|
+
// errada.
|
|
96
|
+
const urlTab = useSyncExternalStore(
|
|
97
|
+
subscribeUrl,
|
|
98
|
+
() => readFromUrl(param),
|
|
99
|
+
() => null,
|
|
100
|
+
);
|
|
101
|
+
const storedTab = useStoredValue(`${STORAGE_PREFIX}${storageKey}`);
|
|
102
|
+
|
|
103
|
+
const isTab = (value: string | null): value is T =>
|
|
104
|
+
value !== null && (tabs as readonly string[]).includes(value);
|
|
105
|
+
|
|
106
|
+
const tab: T = isTab(urlTab) ? urlTab : isTab(storedTab) ? storedTab : defaultTab;
|
|
65
107
|
|
|
66
108
|
// Por `ref` porque a lista costuma nascer dentro do render (filtrada por
|
|
67
|
-
// permissão, por exemplo): como dependência, ela refaria
|
|
109
|
+
// permissão, por exemplo): como dependência, ela refaria o `setTab` a cada
|
|
68
110
|
// render.
|
|
69
111
|
const known = useRef(tabs);
|
|
70
112
|
|
|
@@ -72,43 +114,35 @@ export function useTabState<T extends string>(
|
|
|
72
114
|
known.current = tabs;
|
|
73
115
|
});
|
|
74
116
|
|
|
75
|
-
const
|
|
76
|
-
return value !== null && (known.current as readonly string[]).includes(value);
|
|
77
|
-
}, []);
|
|
117
|
+
const hydrated = useIsHydrated();
|
|
78
118
|
|
|
79
|
-
//
|
|
80
|
-
//
|
|
119
|
+
// A URL passa a dizer onde a tela está, venha a aba de onde vier — sem isso,
|
|
120
|
+
// copiar o link depois de cair na aba do storage levaria à padrão. Lista
|
|
121
|
+
// vazia (nenhuma aba permitida) não tem o que dizer.
|
|
81
122
|
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
setStoredTab(resolved);
|
|
92
|
-
|
|
93
|
-
// A URL passa a dizer onde a tela está, venha a aba de onde vier — sem
|
|
94
|
-
// isso, copiar o link depois de cair na aba do storage levaria à padrão.
|
|
95
|
-
// Lista vazia (nenhuma aba permitida) não tem o que dizer.
|
|
96
|
-
if (resolved !== undefined && fromUrl !== resolved) {
|
|
97
|
-
writeToUrl(param, resolved);
|
|
123
|
+
// Só depois de hidratar: antes disso o armazenamento ainda não foi lido, e
|
|
124
|
+
// escrever aqui gravaria a aba padrão na URL — que manda sobre o storage e
|
|
125
|
+
// apagaria justamente a aba que se quer restaurar.
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!hydrated) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (tab !== undefined && readFromUrl(param) !== tab) {
|
|
131
|
+
writeToUrl(param, tab);
|
|
98
132
|
}
|
|
99
|
-
}, [
|
|
133
|
+
}, [hydrated, tab, param]);
|
|
100
134
|
|
|
101
135
|
const setTab = useCallback(
|
|
102
136
|
(next: string): void => {
|
|
103
|
-
if (!
|
|
137
|
+
if (!(known.current as readonly string[]).includes(next)) {
|
|
104
138
|
return;
|
|
105
139
|
}
|
|
106
140
|
|
|
107
|
-
|
|
141
|
+
// Escrever nos dois já avisa quem lê: a aba vem da URL e do storage.
|
|
108
142
|
writeToUrl(param, next);
|
|
109
143
|
safeStorage.set(`${STORAGE_PREFIX}${storageKey}`, next);
|
|
110
144
|
},
|
|
111
|
-
[storageKey,
|
|
145
|
+
[storageKey, param],
|
|
112
146
|
);
|
|
113
147
|
|
|
114
148
|
return { tab, setTab };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback,
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
4
|
|
|
5
5
|
import { safeStorage } from "#core/_utils/storage";
|
|
6
|
+
import { useStoredValue } from "#core/hooks/use-stored-value";
|
|
6
7
|
|
|
7
8
|
const STORAGE_PREFIX = "view:";
|
|
8
9
|
|
|
@@ -14,6 +15,9 @@ export interface UseTableViewResult {
|
|
|
14
15
|
setView: (next: TableView) => void;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
const isTableView = (value: string | null): value is TableView =>
|
|
19
|
+
value === "list" || value === "grid";
|
|
20
|
+
|
|
17
21
|
/**
|
|
18
22
|
* Guarda em qual formato a pessoa deixou a listagem, por `storageKey`.
|
|
19
23
|
*
|
|
@@ -26,28 +30,22 @@ export function useTableView(
|
|
|
26
30
|
storageKey?: string,
|
|
27
31
|
defaultView: TableView = "list",
|
|
28
32
|
): UseTableViewResult {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!storageKey) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const raw = safeStorage.get(`${STORAGE_PREFIX}${storageKey}`);
|
|
33
|
+
const key = storageKey ? `${STORAGE_PREFIX}${storageKey}` : undefined;
|
|
34
|
+
const stored = useStoredValue(key);
|
|
35
|
+
const [sessionView, setSessionView] = useState<TableView>(defaultView);
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
setStoredView(raw);
|
|
39
|
-
}
|
|
40
|
-
}, [storageKey]);
|
|
37
|
+
const view = key ? (isTableView(stored) ? stored : defaultView) : sessionView;
|
|
41
38
|
|
|
42
39
|
const setView = useCallback(
|
|
43
40
|
(next: TableView) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
safeStorage.set(`${STORAGE_PREFIX}${storageKey}`, next);
|
|
41
|
+
if (!key) {
|
|
42
|
+
setSessionView(next);
|
|
43
|
+
return;
|
|
48
44
|
}
|
|
45
|
+
// Escrever já avisa quem lê a chave — o estado vem do armazenamento.
|
|
46
|
+
safeStorage.set(key, next);
|
|
49
47
|
},
|
|
50
|
-
[
|
|
48
|
+
[key],
|
|
51
49
|
);
|
|
52
50
|
|
|
53
51
|
return { view, setView };
|