rl-core-front 0.14.1 → 0.14.2
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
CHANGED
|
@@ -17,6 +17,15 @@ export interface ConfirmDialogProps {
|
|
|
17
17
|
open: boolean;
|
|
18
18
|
title: string;
|
|
19
19
|
description?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Segundo parágrafo, quando a consequência muda por tela.
|
|
22
|
+
*
|
|
23
|
+
* Separado da `description` porque a explicação do que a ação faz é a mesma
|
|
24
|
+
* em todo lugar, e o que acontece com o que depende do registro não é: numa
|
|
25
|
+
* tela a exclusão passa e o histórico se mantém, na outra ela é recusada
|
|
26
|
+
* enquanto houver algo em uso. Um texto único para as duas mentiria numa.
|
|
27
|
+
*/
|
|
28
|
+
note?: string;
|
|
20
29
|
confirmLabel: string;
|
|
21
30
|
cancelLabel: string;
|
|
22
31
|
/** Pinta o botão de confirmar como destrutivo (excluir, resetar). */
|
|
@@ -34,6 +43,7 @@ export function ConfirmDialog({
|
|
|
34
43
|
open,
|
|
35
44
|
title,
|
|
36
45
|
description,
|
|
46
|
+
note,
|
|
37
47
|
confirmLabel,
|
|
38
48
|
cancelLabel,
|
|
39
49
|
destructive = false,
|
|
@@ -48,9 +58,21 @@ export function ConfirmDialog({
|
|
|
48
58
|
<DialogContent className="max-w-sm" expandable={false}>
|
|
49
59
|
<DialogHeader>
|
|
50
60
|
<DialogTitle>{title}</DialogTitle>
|
|
51
|
-
{description && <DialogDescription>{description}</DialogDescription>}
|
|
52
61
|
</DialogHeader>
|
|
53
62
|
<DialogForm onSubmit={onConfirm}>
|
|
63
|
+
{/* A explicação fica no corpo, e não no `DialogHeader`.
|
|
64
|
+
Ali dentro ela virava subtítulo: o `DialogContent` arranca o
|
|
65
|
+
cabeçalho para dividir a primeira linha com os botões de ampliar
|
|
66
|
+
e fechar, então a descrição era espremida numa coluna estreita ao
|
|
67
|
+
lado do X — enquanto o corpo do modal ficava vazio. Numa
|
|
68
|
+
confirmação a explicação **é** o conteúdo: ela precisa do corpo,
|
|
69
|
+
com respiro, para ser lida antes do clique. */}
|
|
70
|
+
{(description || note) && (
|
|
71
|
+
<div className="space-y-2 text-sm text-muted-foreground">
|
|
72
|
+
{description && <DialogDescription>{description}</DialogDescription>}
|
|
73
|
+
{note && <p>{note}</p>}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
54
76
|
<DialogFooter className="gap-2">
|
|
55
77
|
<Button type="button" variant="outline" onClick={onCancel}>
|
|
56
78
|
{cancelLabel}
|
|
@@ -5,16 +5,32 @@ import { useCallback, useState } from "react";
|
|
|
5
5
|
|
|
6
6
|
import { ConfirmDialog } from "#core/components/ui";
|
|
7
7
|
import { useI18n } from "#core/contexts";
|
|
8
|
+
import { RequestOperation, useRequest } from "#core/hooks/use-request";
|
|
8
9
|
|
|
9
10
|
export interface ConfirmOptions {
|
|
10
11
|
title: string;
|
|
11
12
|
description?: string;
|
|
13
|
+
/** Segundo parágrafo — a consequência que muda por tela. */
|
|
14
|
+
note?: string;
|
|
12
15
|
confirmLabel?: string;
|
|
13
16
|
cancelLabel?: string;
|
|
14
17
|
/** Ação sem volta (excluir, resetar 2FA) — pinta o botão de vermelho. */
|
|
15
18
|
destructive?: boolean;
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
export interface ConfirmedActionOptions extends ConfirmOptions {
|
|
22
|
+
/**
|
|
23
|
+
* O que recarregar depois que a ação deu certo.
|
|
24
|
+
*
|
|
25
|
+
* Fica aqui e não no `.then()` de quem chamou porque `acting` só volta a
|
|
26
|
+
* `false` quando isto termina: sem esperar a recarga, a tabela destrava
|
|
27
|
+
* mostrando ainda a linha que acabou de ser excluída.
|
|
28
|
+
*/
|
|
29
|
+
reload?: () => Promise<unknown> | unknown;
|
|
30
|
+
/** Toast de sucesso; `RequestOperation.Delete` dá o texto de exclusão. */
|
|
31
|
+
success?: RequestOperation | string;
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
interface Pending {
|
|
19
35
|
options: ConfirmOptions;
|
|
20
36
|
resolve: (confirmed: boolean) => void;
|
|
@@ -23,6 +39,18 @@ interface Pending {
|
|
|
23
39
|
export interface UseConfirmResult {
|
|
24
40
|
/** Aguarda a decisão do usuário: `true` confirmou, `false` cancelou. */
|
|
25
41
|
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Pergunta, executa e recarrega — o ciclo inteiro numa chamada.
|
|
44
|
+
*
|
|
45
|
+
* `true` quando a ação rodou até o fim; `false` se o usuário desistiu ou se
|
|
46
|
+
* a requisição falhou (o toast de erro é do `useRequest`).
|
|
47
|
+
*/
|
|
48
|
+
confirmAndRun: (
|
|
49
|
+
options: ConfirmedActionOptions,
|
|
50
|
+
action: () => Promise<unknown>,
|
|
51
|
+
) => Promise<boolean>;
|
|
52
|
+
/** Enquanto a ação confirmada roda — para travar a tabela que ela muda. */
|
|
53
|
+
acting: boolean;
|
|
26
54
|
/** Renderize no final da tela — é o modal em si. */
|
|
27
55
|
confirmDialog: JSX.Element;
|
|
28
56
|
}
|
|
@@ -34,9 +62,14 @@ export interface UseConfirmResult {
|
|
|
34
62
|
*
|
|
35
63
|
* A promise resolve quando o usuário decide — cancelar (ou fechar no `Esc`)
|
|
36
64
|
* resolve `false`, então o fluxo de quem chamou não muda.
|
|
65
|
+
*
|
|
66
|
+
* Quando a resposta do "sim" é sempre a mesma — chamar o serviço, recarregar a
|
|
67
|
+
* listagem e travar a tela no meio —, use o `confirmAndRun`: é o passo que
|
|
68
|
+
* cada tela remontava à mão, e onde mora a armadilha do parágrafo seguinte.
|
|
37
69
|
*/
|
|
38
70
|
export function useConfirm(): UseConfirmResult {
|
|
39
71
|
const { t } = useI18n();
|
|
72
|
+
const { run, loading: acting } = useRequest();
|
|
40
73
|
const [pending, setPending] = useState<Pending | null>(null);
|
|
41
74
|
|
|
42
75
|
const confirm = useCallback(
|
|
@@ -53,11 +86,39 @@ export function useConfirm(): UseConfirmResult {
|
|
|
53
86
|
[pending],
|
|
54
87
|
);
|
|
55
88
|
|
|
89
|
+
const confirmAndRun = useCallback(
|
|
90
|
+
async (
|
|
91
|
+
options: ConfirmedActionOptions,
|
|
92
|
+
action: () => Promise<unknown>,
|
|
93
|
+
): Promise<boolean> => {
|
|
94
|
+
if (!(await confirm(options))) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const done = await run(action, { success: options.success });
|
|
99
|
+
|
|
100
|
+
// A falha do `run` é `null`, e só ela. `undefined` é o que uma rota 204
|
|
101
|
+
// resolve **em caso de sucesso** — e exclusão é justamente 204. Quem
|
|
102
|
+
// monta este passo na tela erra aqui: compara com `undefined`, lê o
|
|
103
|
+
// sucesso como erro e sai sem recarregar, e a linha some do banco sem
|
|
104
|
+
// sumir da tela.
|
|
105
|
+
if (done === null) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await options.reload?.();
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
},
|
|
113
|
+
[confirm, run],
|
|
114
|
+
);
|
|
115
|
+
|
|
56
116
|
const confirmDialog = (
|
|
57
117
|
<ConfirmDialog
|
|
58
118
|
open={!!pending}
|
|
59
119
|
title={pending?.options.title ?? ""}
|
|
60
120
|
description={pending?.options.description}
|
|
121
|
+
note={pending?.options.note}
|
|
61
122
|
confirmLabel={pending?.options.confirmLabel ?? t("common.confirm")}
|
|
62
123
|
cancelLabel={pending?.options.cancelLabel ?? t("common.cancel")}
|
|
63
124
|
destructive={pending?.options.destructive}
|
|
@@ -66,5 +127,5 @@ export function useConfirm(): UseConfirmResult {
|
|
|
66
127
|
/>
|
|
67
128
|
);
|
|
68
129
|
|
|
69
|
-
return { confirm, confirmDialog };
|
|
130
|
+
return { confirm, confirmAndRun, acting, confirmDialog };
|
|
70
131
|
}
|
package/src/index.ts
CHANGED
|
@@ -72,7 +72,11 @@ export {
|
|
|
72
72
|
// ---------------------------------------------------------------------------
|
|
73
73
|
export type { UseColumnVisibilityResult } from "#core/hooks/use-column-visibility";
|
|
74
74
|
export { useColumnVisibility } from "#core/hooks/use-column-visibility";
|
|
75
|
-
export type {
|
|
75
|
+
export type {
|
|
76
|
+
ConfirmedActionOptions,
|
|
77
|
+
ConfirmOptions,
|
|
78
|
+
UseConfirmResult,
|
|
79
|
+
} from "#core/hooks/use-confirm";
|
|
76
80
|
export { useConfirm } from "#core/hooks/use-confirm";
|
|
77
81
|
export type { UseFilterSchemaResult } from "#core/hooks/use-filter-schema";
|
|
78
82
|
export { useFilterSchema } from "#core/hooks/use-filter-schema";
|