rl-core-front 0.14.1 → 0.14.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.
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,31 @@ 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
|
+
/**
|
|
53
|
+
* O caso mais comum, com os textos prontos: `Excluir “Cartão Inter”?` mais o
|
|
54
|
+
* aviso de que não há volta, o botão vermelho e o rótulo "Excluir".
|
|
55
|
+
*
|
|
56
|
+
* Existe para a tela não repetir cinco campos a cada lixeira — era o que
|
|
57
|
+
* fazia cada projeto embrulhar o `confirm` num hook próprio e acabar com
|
|
58
|
+
* duas perguntas de excluir diferentes no mesmo produto.
|
|
59
|
+
*
|
|
60
|
+
* O texto **não** menciona recuperação, mesmo onde a exclusão é lógica:
|
|
61
|
+
* lixeira por baixo é rede de segurança do dado, não promessa ao usuário, e
|
|
62
|
+
* prometer desfazer convida ao clique sem cuidado.
|
|
63
|
+
*/
|
|
64
|
+
confirmDelete: (name: string, note?: string) => Promise<boolean>;
|
|
65
|
+
/** Enquanto a ação confirmada roda — para travar a tabela que ela muda. */
|
|
66
|
+
acting: boolean;
|
|
26
67
|
/** Renderize no final da tela — é o modal em si. */
|
|
27
68
|
confirmDialog: JSX.Element;
|
|
28
69
|
}
|
|
@@ -34,9 +75,14 @@ export interface UseConfirmResult {
|
|
|
34
75
|
*
|
|
35
76
|
* A promise resolve quando o usuário decide — cancelar (ou fechar no `Esc`)
|
|
36
77
|
* resolve `false`, então o fluxo de quem chamou não muda.
|
|
78
|
+
*
|
|
79
|
+
* Quando a resposta do "sim" é sempre a mesma — chamar o serviço, recarregar a
|
|
80
|
+
* listagem e travar a tela no meio —, use o `confirmAndRun`: é o passo que
|
|
81
|
+
* cada tela remontava à mão, e onde mora a armadilha do parágrafo seguinte.
|
|
37
82
|
*/
|
|
38
83
|
export function useConfirm(): UseConfirmResult {
|
|
39
84
|
const { t } = useI18n();
|
|
85
|
+
const { run, loading: acting } = useRequest();
|
|
40
86
|
const [pending, setPending] = useState<Pending | null>(null);
|
|
41
87
|
|
|
42
88
|
const confirm = useCallback(
|
|
@@ -53,11 +99,51 @@ export function useConfirm(): UseConfirmResult {
|
|
|
53
99
|
[pending],
|
|
54
100
|
);
|
|
55
101
|
|
|
102
|
+
const confirmDelete = useCallback(
|
|
103
|
+
(name: string, note?: string): Promise<boolean> =>
|
|
104
|
+
confirm({
|
|
105
|
+
title: t("common.deleteTitle", { name }),
|
|
106
|
+
description: t("common.deleteWarning"),
|
|
107
|
+
note,
|
|
108
|
+
confirmLabel: t("common.delete"),
|
|
109
|
+
destructive: true,
|
|
110
|
+
}),
|
|
111
|
+
[confirm, t],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const confirmAndRun = useCallback(
|
|
115
|
+
async (
|
|
116
|
+
options: ConfirmedActionOptions,
|
|
117
|
+
action: () => Promise<unknown>,
|
|
118
|
+
): Promise<boolean> => {
|
|
119
|
+
if (!(await confirm(options))) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const done = await run(action, { success: options.success });
|
|
124
|
+
|
|
125
|
+
// A falha do `run` é `null`, e só ela. `undefined` é o que uma rota 204
|
|
126
|
+
// resolve **em caso de sucesso** — e exclusão é justamente 204. Quem
|
|
127
|
+
// monta este passo na tela erra aqui: compara com `undefined`, lê o
|
|
128
|
+
// sucesso como erro e sai sem recarregar, e a linha some do banco sem
|
|
129
|
+
// sumir da tela.
|
|
130
|
+
if (done === null) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await options.reload?.();
|
|
135
|
+
|
|
136
|
+
return true;
|
|
137
|
+
},
|
|
138
|
+
[confirm, run],
|
|
139
|
+
);
|
|
140
|
+
|
|
56
141
|
const confirmDialog = (
|
|
57
142
|
<ConfirmDialog
|
|
58
143
|
open={!!pending}
|
|
59
144
|
title={pending?.options.title ?? ""}
|
|
60
145
|
description={pending?.options.description}
|
|
146
|
+
note={pending?.options.note}
|
|
61
147
|
confirmLabel={pending?.options.confirmLabel ?? t("common.confirm")}
|
|
62
148
|
cancelLabel={pending?.options.cancelLabel ?? t("common.cancel")}
|
|
63
149
|
destructive={pending?.options.destructive}
|
|
@@ -66,5 +152,5 @@ export function useConfirm(): UseConfirmResult {
|
|
|
66
152
|
/>
|
|
67
153
|
);
|
|
68
154
|
|
|
69
|
-
return { confirm, confirmDialog };
|
|
155
|
+
return { confirm, confirmDelete, confirmAndRun, acting, confirmDialog };
|
|
70
156
|
}
|
package/src/i18n/messages/en.ts
CHANGED
|
@@ -195,6 +195,9 @@ export const en: Messages = {
|
|
|
195
195
|
cancel: "Cancel",
|
|
196
196
|
edit: "Edit",
|
|
197
197
|
delete: "Delete",
|
|
198
|
+
deleteTitle: "Delete “{name}”?",
|
|
199
|
+
deleteWarning:
|
|
200
|
+
"This action cannot be undone. The record leaves the lists and stops appearing in the system.",
|
|
198
201
|
create: "Create",
|
|
199
202
|
confirm: "Confirm",
|
|
200
203
|
send: "Send",
|
package/src/i18n/messages/pt.ts
CHANGED
|
@@ -197,6 +197,9 @@ export const pt = {
|
|
|
197
197
|
cancel: "Cancelar",
|
|
198
198
|
edit: "Editar",
|
|
199
199
|
delete: "Excluir",
|
|
200
|
+
deleteTitle: "Excluir “{name}”?",
|
|
201
|
+
deleteWarning:
|
|
202
|
+
"Esta ação não pode ser desfeita. O registro sai das listas e deixa de aparecer no sistema.",
|
|
200
203
|
create: "Criar",
|
|
201
204
|
confirm: "Confirmar",
|
|
202
205
|
send: "Enviar",
|
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";
|