forlogic-core 2.2.0 → 2.2.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/dist/assets/docs-BEwTKYu3.css +1 -0
- package/dist/assets/docs-Bgpz6ETN.js +10752 -0
- package/dist/assets/index-SqMwTzMJ.js +97 -0
- package/dist/components/ui/combobox.d.ts +2 -1
- package/dist/components/ui/tooltip.d.ts +7 -1
- package/dist/exports/ui.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.html +34 -0
- package/dist/index.js +1 -1
- package/dist/mind-map/components/MindMap.d.ts +23 -0
- package/dist/mind-map/components/MindMapConnection.d.ts +12 -0
- package/dist/mind-map/components/MindMapNodeView.d.ts +24 -0
- package/dist/mind-map/components/MindMapToolbar.d.ts +26 -0
- package/dist/mind-map/hooks/useMindMapKeyboard.d.ts +15 -0
- package/dist/mind-map/hooks/useMindMapLayout.d.ts +5 -0
- package/dist/mind-map/hooks/useMindMapPanZoom.d.ts +21 -0
- package/dist/mind-map/hooks/useMindMapState.d.ts +32 -0
- package/dist/mind-map/index.d.ts +4 -0
- package/dist/mind-map/types.d.ts +91 -0
- package/dist/mind-map/utils/export-image.d.ts +9 -0
- package/dist/mind-map/utils/layout.d.ts +15 -0
- package/dist/mind-map/utils/nodeOps.d.ts +66 -0
- package/dist/mind-map/utils/serialize.d.ts +10 -0
- package/docs/STORAGE_BUCKETS.md +48 -43
- package/docs/design-system/README.md +2 -2
- package/docs/design-system/dialogs.md +2 -0
- package/docs/design-system/selectors.md +17 -0
- package/package.json +3 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { MindMapNode, MindMapProps } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Componente de mapa mental controlado/uncontrolled inspirado no FreeMind.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <MindMap defaultValue={{ id: 'root', text: 'Ideia Central' }} />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Atalhos:
|
|
12
|
+
* - `Enter` cria um nó irmão
|
|
13
|
+
* - `Insert` ou `Tab` cria um filho
|
|
14
|
+
* - `Delete` remove o nó (e descendentes)
|
|
15
|
+
* - `F2` renomeia
|
|
16
|
+
* - `Espaço` colapsa/expande
|
|
17
|
+
* - `Ctrl+Z` / `Ctrl+Shift+Z` undo/redo
|
|
18
|
+
* - Setas para navegar
|
|
19
|
+
*
|
|
20
|
+
* Pan: arrastar o fundo. Zoom: `Ctrl/⌘ + scroll` ou botões da toolbar.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MindMap: React.FC<MindMapProps>;
|
|
23
|
+
export type { MindMapNode, MindMapProps };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { MindMapLayoutNode } from '../types';
|
|
3
|
+
interface MindMapConnectionProps {
|
|
4
|
+
parent: MindMapLayoutNode;
|
|
5
|
+
child: MindMapLayoutNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Renderiza uma curva Bézier conectando o pai a um filho. Usa coordenadas
|
|
9
|
+
* absolutas dentro do `<svg>` do canvas.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MindMapConnection: React.FC<MindMapConnectionProps>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { MindMapLayoutNode, MindMapNode } from '../types';
|
|
3
|
+
interface MindMapNodeViewProps {
|
|
4
|
+
layout: MindMapLayoutNode;
|
|
5
|
+
selected: boolean;
|
|
6
|
+
editing: boolean;
|
|
7
|
+
readOnly: boolean;
|
|
8
|
+
onSelect: () => void;
|
|
9
|
+
onStartEditing: () => void;
|
|
10
|
+
onFinishEditing: (newText: string | null) => void;
|
|
11
|
+
onToggle: () => void;
|
|
12
|
+
onDragStart?: (e: React.DragEvent) => void;
|
|
13
|
+
onDragOver?: (e: React.DragEvent) => void;
|
|
14
|
+
onDragLeave?: (e: React.DragEvent) => void;
|
|
15
|
+
onDrop?: (e: React.DragEvent) => void;
|
|
16
|
+
isDropTarget?: boolean;
|
|
17
|
+
renderNodeContent?: (node: MindMapNode) => React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Renderiza a "caixa" de um nó do mapa mental: ícone opcional, texto, indicador
|
|
21
|
+
* de filhos colapsados e tooltip de nota.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MindMapNodeView: React.FC<MindMapNodeViewProps>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface MindMapToolbarProps {
|
|
3
|
+
canAddChild: boolean;
|
|
4
|
+
canAddSibling: boolean;
|
|
5
|
+
canDelete: boolean;
|
|
6
|
+
canUndo: boolean;
|
|
7
|
+
canRedo: boolean;
|
|
8
|
+
onAddChild: () => void;
|
|
9
|
+
onAddSibling: () => void;
|
|
10
|
+
onDelete: () => void;
|
|
11
|
+
onExpandAll: () => void;
|
|
12
|
+
onCollapseAll: () => void;
|
|
13
|
+
onFit: () => void;
|
|
14
|
+
onZoomIn: () => void;
|
|
15
|
+
onZoomOut: () => void;
|
|
16
|
+
onUndo: () => void;
|
|
17
|
+
onRedo: () => void;
|
|
18
|
+
onExportJson: () => void;
|
|
19
|
+
onExportImage: () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Toolbar superior do MindMap. Botões agrupados por função: edição, visualização,
|
|
23
|
+
* histórico e exportação.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MindMapToolbar: React.FC<MindMapToolbarProps>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MindMapLayoutResult, MindMapShortcut } from '../types';
|
|
2
|
+
import type { MindMapStateApi } from './useMindMapState';
|
|
3
|
+
export interface UseMindMapKeyboardOptions {
|
|
4
|
+
api: MindMapStateApi;
|
|
5
|
+
layout: MindMapLayoutResult;
|
|
6
|
+
containerRef: React.RefObject<HTMLElement>;
|
|
7
|
+
onStartEditing: (id: string) => void;
|
|
8
|
+
readOnly?: boolean;
|
|
9
|
+
extraShortcuts?: MindMapShortcut[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Atalhos de teclado estilo FreeMind. Disparado apenas quando o foco está
|
|
13
|
+
* dentro do container do MindMap (e não em um input contentEditable em edição).
|
|
14
|
+
*/
|
|
15
|
+
export declare function useMindMapKeyboard({ api, layout, containerRef, onStartEditing, readOnly, extraShortcuts, }: UseMindMapKeyboardOptions): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface PanZoomState {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
scale: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Pan + zoom para o canvas do mapa mental. Pan via arrasto no fundo (botão esquerdo
|
|
8
|
+
* do mouse + tecla espaço, ou botão do meio); zoom via Ctrl/Meta + scroll, ou botões.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useMindMapPanZoom(initial?: Partial<PanZoomState>): {
|
|
11
|
+
transform: PanZoomState;
|
|
12
|
+
setTransform: import("react").Dispatch<import("react").SetStateAction<PanZoomState>>;
|
|
13
|
+
zoomIn: () => void;
|
|
14
|
+
zoomOut: () => void;
|
|
15
|
+
reset: () => void;
|
|
16
|
+
fitTo: (contentW: number, contentH: number, viewportW: number, viewportH: number) => void;
|
|
17
|
+
onBackgroundPointerDown: (e: React.PointerEvent) => void;
|
|
18
|
+
onPointerMove: (e: React.PointerEvent) => void;
|
|
19
|
+
onPointerUp: (e: React.PointerEvent) => void;
|
|
20
|
+
onWheel: (e: React.WheelEvent) => void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MindMapNode } from '../types';
|
|
2
|
+
export interface UseMindMapStateOptions {
|
|
3
|
+
value?: MindMapNode;
|
|
4
|
+
defaultValue?: MindMapNode;
|
|
5
|
+
onChange?: (root: MindMapNode) => void;
|
|
6
|
+
readOnly?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Estado central do MindMap: árvore controlada/uncontrolled, seleção e histórico
|
|
10
|
+
* de undo/redo. Expõe ações imutáveis (sempre produzem nova árvore).
|
|
11
|
+
*/
|
|
12
|
+
export declare function useMindMapState(opts: UseMindMapStateOptions): {
|
|
13
|
+
root: MindMapNode;
|
|
14
|
+
setRoot: (next: MindMapNode) => void;
|
|
15
|
+
selectedId: string;
|
|
16
|
+
selectedNode: MindMapNode;
|
|
17
|
+
setSelectedId: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
18
|
+
renameNode: (id: string, text: string) => void;
|
|
19
|
+
updateNodeProps: (id: string, patch: Partial<MindMapNode>) => void;
|
|
20
|
+
addChild: (parentId: string, text?: string) => string;
|
|
21
|
+
addSibling: (nodeId: string, text?: string) => string;
|
|
22
|
+
removeNode: (id: string) => void;
|
|
23
|
+
moveNode: (id: string, newParentId: string) => void;
|
|
24
|
+
toggleNode: (id: string) => void;
|
|
25
|
+
expandAll: () => void;
|
|
26
|
+
collapseAll: () => void;
|
|
27
|
+
undo: () => void;
|
|
28
|
+
redo: () => void;
|
|
29
|
+
canUndo: boolean;
|
|
30
|
+
canRedo: boolean;
|
|
31
|
+
};
|
|
32
|
+
export type MindMapStateApi = ReturnType<typeof useMindMapState>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { MindMap } from './components/MindMap';
|
|
2
|
+
export { createMindMapNode, findNode, addChild, addSibling, removeNode, moveNode, toggleCollapsed, setCollapsedAll, generateNodeId, } from './utils/nodeOps';
|
|
3
|
+
export { exportMindMap, importMindMap } from './utils/serialize';
|
|
4
|
+
export type { MindMapNode, MindMapProps, MindMapShortcut, MindMapContext, MindMapLayoutNode, MindMapLayoutResult, } from './types';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Nó do mapa mental. Estrutura recursiva e serializável.
|
|
4
|
+
*/
|
|
5
|
+
export interface MindMapNode {
|
|
6
|
+
/** Identificador único do nó. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Texto principal exibido na caixa do nó. */
|
|
9
|
+
text: string;
|
|
10
|
+
/** Filhos do nó (opcional). */
|
|
11
|
+
children?: MindMapNode[];
|
|
12
|
+
/** Quando true, oculta a subárvore visualmente (mas preserva no modelo). */
|
|
13
|
+
collapsed?: boolean;
|
|
14
|
+
/** Cor de fundo da caixa do nó. Aceita hex/HSL/qualquer valor CSS válido. */
|
|
15
|
+
color?: string;
|
|
16
|
+
/** Nome de um ícone Lucide a exibir antes do texto (ex: "Star"). */
|
|
17
|
+
icon?: string;
|
|
18
|
+
/** Texto longo opcional, exibido em tooltip por um ícone na caixa. */
|
|
19
|
+
note?: string;
|
|
20
|
+
/** Lado em relação à raiz. Apenas filhos diretos da raiz definem livremente. */
|
|
21
|
+
side?: 'left' | 'right';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Snapshot da posição calculada de um nó, usado pelo layout.
|
|
25
|
+
*/
|
|
26
|
+
export interface MindMapLayoutNode {
|
|
27
|
+
node: MindMapNode;
|
|
28
|
+
parent: MindMapNode | null;
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
side: 'left' | 'right' | 'root';
|
|
34
|
+
depth: number;
|
|
35
|
+
visibleChildren: MindMapLayoutNode[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resultado completo do cálculo de layout.
|
|
39
|
+
*/
|
|
40
|
+
export interface MindMapLayoutResult {
|
|
41
|
+
nodes: MindMapLayoutNode[];
|
|
42
|
+
byId: Map<string, MindMapLayoutNode>;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Contexto exposto a atalhos custom.
|
|
48
|
+
*/
|
|
49
|
+
export interface MindMapContext {
|
|
50
|
+
selectedId: string | null;
|
|
51
|
+
root: MindMapNode;
|
|
52
|
+
setRoot: (next: MindMapNode) => void;
|
|
53
|
+
select: (id: string | null) => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Atalho de teclado adicional para o MindMap.
|
|
57
|
+
*/
|
|
58
|
+
export interface MindMapShortcut {
|
|
59
|
+
/** Chave (ex: "k", "F2", "Enter"). */
|
|
60
|
+
key: string;
|
|
61
|
+
/** Modificadores opcionais. */
|
|
62
|
+
ctrl?: boolean;
|
|
63
|
+
shift?: boolean;
|
|
64
|
+
alt?: boolean;
|
|
65
|
+
handler: (ctx: MindMapContext) => void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Props do componente `MindMap`.
|
|
69
|
+
*/
|
|
70
|
+
export interface MindMapProps {
|
|
71
|
+
/** Modo controlado: árvore atual. */
|
|
72
|
+
value?: MindMapNode;
|
|
73
|
+
/** Modo não-controlado: árvore inicial. */
|
|
74
|
+
defaultValue?: MindMapNode;
|
|
75
|
+
/** Callback chamado a cada mutação aplicada. */
|
|
76
|
+
onChange?: (root: MindMapNode) => void;
|
|
77
|
+
/** Callback ao selecionar um nó. */
|
|
78
|
+
onNodeSelect?: (node: MindMapNode | null) => void;
|
|
79
|
+
/** Modo somente leitura (sem edição/atalhos de mutação). */
|
|
80
|
+
readOnly?: boolean;
|
|
81
|
+
/** Esconde a toolbar superior. */
|
|
82
|
+
hideToolbar?: boolean;
|
|
83
|
+
/** Atalhos custom adicionais (são processados depois dos padrão). */
|
|
84
|
+
extraShortcuts?: MindMapShortcut[];
|
|
85
|
+
/** Render custom do conteúdo interno do nó (ícone + texto). */
|
|
86
|
+
renderNodeContent?: (node: MindMapNode) => React.ReactNode;
|
|
87
|
+
/** Classe extra no container raiz. */
|
|
88
|
+
className?: string;
|
|
89
|
+
/** Altura do canvas. Default 600px. */
|
|
90
|
+
height?: string | number;
|
|
91
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exporta o SVG do mapa mental como PNG, abrindo o resultado em download
|
|
3
|
+
* pelo browser. Usa um canvas off-screen para rasterizar o SVG.
|
|
4
|
+
*/
|
|
5
|
+
export declare function exportSvgAsPng(svg: SVGSVGElement, filename?: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Faz download de uma string como arquivo (utilitário interno).
|
|
8
|
+
*/
|
|
9
|
+
export declare function downloadTextFile(content: string, filename: string, mime?: string): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MindMapLayoutResult, MindMapNode } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Calcula o layout de um mapa mental: posições absolutas (x,y), tamanhos e
|
|
4
|
+
* dimensão total do canvas. Compatível com pan/zoom externos via transform.
|
|
5
|
+
*/
|
|
6
|
+
export declare function computeLayout(root: MindMapNode): MindMapLayoutResult;
|
|
7
|
+
export declare const MIND_MAP_LAYOUT_CONSTANTS: {
|
|
8
|
+
NODE_WIDTH: number;
|
|
9
|
+
NODE_HEIGHT: number;
|
|
10
|
+
ROOT_WIDTH: number;
|
|
11
|
+
ROOT_HEIGHT: number;
|
|
12
|
+
H_GAP: number;
|
|
13
|
+
V_GAP: number;
|
|
14
|
+
PADDING: number;
|
|
15
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { MindMapNode } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Gera um id curto e único o suficiente para uso local em árvores de mapas mentais.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateNodeId(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Cria um novo `MindMapNode` com defaults seguros.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createMindMapNode(text: string, partial?: Partial<MindMapNode>): MindMapNode;
|
|
10
|
+
/**
|
|
11
|
+
* Procura um nó pelo id e retorna o nó + seu pai (ou null).
|
|
12
|
+
*/
|
|
13
|
+
export declare function findNode(root: MindMapNode, id: string): {
|
|
14
|
+
node: MindMapNode;
|
|
15
|
+
parent: MindMapNode | null;
|
|
16
|
+
} | null;
|
|
17
|
+
/**
|
|
18
|
+
* Retorna true se `ancestorId` está na cadeia ancestral de `descendantId`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isAncestor(root: MindMapNode, ancestorId: string, descendantId: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Atualiza um nó por id retornando uma nova árvore.
|
|
23
|
+
*/
|
|
24
|
+
export declare function updateNode(root: MindMapNode, id: string, updater: (n: MindMapNode) => MindMapNode): MindMapNode;
|
|
25
|
+
/**
|
|
26
|
+
* Adiciona um filho a um nó. Para filhos diretos da raiz, escolhe automaticamente
|
|
27
|
+
* o lado com menos nós (balanceia ramos).
|
|
28
|
+
*/
|
|
29
|
+
export declare function addChild(root: MindMapNode, parentId: string, text: string): {
|
|
30
|
+
root: MindMapNode;
|
|
31
|
+
newId: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Adiciona um irmão (mesmo pai) logo após o nó referência.
|
|
35
|
+
*/
|
|
36
|
+
export declare function addSibling(root: MindMapNode, nodeId: string, text: string): {
|
|
37
|
+
root: MindMapNode;
|
|
38
|
+
newId: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Remove um nó (e descendentes). Não permite remover a raiz.
|
|
42
|
+
*/
|
|
43
|
+
export declare function removeNode(root: MindMapNode, id: string): {
|
|
44
|
+
root: MindMapNode;
|
|
45
|
+
nextSelectedId: string | null;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Move um nó para um novo pai. Retorna a árvore inalterada se o destino for
|
|
49
|
+
* o próprio nó ou um descendente (evita ciclo).
|
|
50
|
+
*/
|
|
51
|
+
export declare function moveNode(root: MindMapNode, id: string, newParentId: string): MindMapNode;
|
|
52
|
+
/**
|
|
53
|
+
* Alterna `collapsed` em um nó.
|
|
54
|
+
*/
|
|
55
|
+
export declare function toggleCollapsed(root: MindMapNode, id: string): MindMapNode;
|
|
56
|
+
/**
|
|
57
|
+
* Define `collapsed` em todos os nós com filhos.
|
|
58
|
+
*/
|
|
59
|
+
export declare function setCollapsedAll(root: MindMapNode, collapsed: boolean): MindMapNode;
|
|
60
|
+
/**
|
|
61
|
+
* Retorna lista linear (DFS) de todos os nós + pai, respeitando collapsed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function flattenVisible(root: MindMapNode): Array<{
|
|
64
|
+
node: MindMapNode;
|
|
65
|
+
parent: MindMapNode | null;
|
|
66
|
+
}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MindMapNode } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Serializa a árvore como JSON formatado.
|
|
4
|
+
*/
|
|
5
|
+
export declare function exportMindMap(root: MindMapNode): string;
|
|
6
|
+
/**
|
|
7
|
+
* Desserializa uma string JSON em um `MindMapNode` validado.
|
|
8
|
+
* Lança em caso de JSON inválido ou estrutura incompatível.
|
|
9
|
+
*/
|
|
10
|
+
export declare function importMindMap(json: string): MindMapNode;
|
package/docs/STORAGE_BUCKETS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Supabase Storage — Inventário e mapa de consumidores
|
|
2
2
|
|
|
3
3
|
> **Projeto Supabase:** `ccjfvpnndclajkleyqkc` (qualiex-db, prod)
|
|
4
|
-
> **Snapshot:** 2026-04-
|
|
4
|
+
> **Snapshot:** 2026-04-29
|
|
5
5
|
> **Objetivo:** mapear todos os buckets, quem os consome, suas RLS atuais e os riscos para guiar o próximo ciclo de hardening.
|
|
6
6
|
> **Escopo:** apenas documentação — nenhuma policy, bucket ou código foi alterado.
|
|
7
7
|
|
|
@@ -9,25 +9,25 @@
|
|
|
9
9
|
|
|
10
10
|
## 1. Visão geral
|
|
11
11
|
|
|
12
|
-
15 buckets ativos. Tamanho total ≈
|
|
12
|
+
15 buckets ativos. Tamanho total ≈ 330 GB (dominado por `content-videos`).
|
|
13
13
|
|
|
14
14
|
| # | Bucket | Público | Objetos | Tamanho | MIME limit | File size limit |
|
|
15
15
|
|---|---|---|---:|---:|---|---|
|
|
16
16
|
| 1 | `career-banners` | ✅ | 4 | 349 kB | — | — |
|
|
17
|
-
| 2 | `certificates` | ✅ | 2.
|
|
18
|
-
| 3 | `content-files` | ✅ | 5.
|
|
19
|
-
| 4 | `content-videos` | ✅ | 1.
|
|
17
|
+
| 2 | `certificates` | ✅ | 2.430 | 978 MB | — | — |
|
|
18
|
+
| 3 | `content-files` | ✅ | 5.362 | 2,5 GB | lista ampla | 2 GB |
|
|
19
|
+
| 4 | `content-videos` | ✅ | 1.923 | 325 GB | só vídeo | 3 GB |
|
|
20
20
|
| 5 | `contracts` | 🔒 | 151 | 47 MB | — | — |
|
|
21
21
|
| 6 | `imports` | 🔒 | 3 | 569 kB | — | — |
|
|
22
22
|
| 7 | `knowledge-files` | 🔒 | 8 | 24 MB | — | — |
|
|
23
23
|
| 8 | `library-assets` | ✅ | 10 | 105 kB | — | — |
|
|
24
|
-
| 9 | `performance` | ✅ |
|
|
25
|
-
| 10 | `performance-files` | ✅ | 33 | 6 MB | — | — |
|
|
26
|
-
| 11 | `resumes` | 🔒 |
|
|
27
|
-
| 12 | `thumbnails` | ✅ | 1.
|
|
28
|
-
| 13 | `trainings` | 🔒 |
|
|
29
|
-
| 14 | `university-assets` | ✅ |
|
|
30
|
-
| 15 | `user-uploads` | 🔒 |
|
|
24
|
+
| 9 | `performance` | ✅ | 37 | 11 MB | — | — |
|
|
25
|
+
| 10 | `performance-files` | ✅ | 33 | 6,4 MB | — | — |
|
|
26
|
+
| 11 | `resumes` | 🔒 | 738 | 161 MB | pdf/doc/docx | 10 MB |
|
|
27
|
+
| 12 | `thumbnails` | ✅ | 1.145 | 577 MB | — | — |
|
|
28
|
+
| 13 | `trainings` | 🔒 | 24 | 12 MB | — | — |
|
|
29
|
+
| 14 | `university-assets` | ✅ | 275 | 304 MB | — | — |
|
|
30
|
+
| 15 | `user-uploads` | 🔒 | 1.000 | 1,4 GB | — | — |
|
|
31
31
|
|
|
32
32
|
> ℹ️ A coluna **Público** indica `storage.buckets.public`. Buckets públicos liberam download anônimo via `/storage/v1/object/public/<bucket>/<path>` e, sem policy explícita, também permitem `LIST` anônimo.
|
|
33
33
|
|
|
@@ -76,18 +76,18 @@ Legenda: ✅ uso confirmado em código · 🔎 só leitura · (policy) tem RLS m
|
|
|
76
76
|
|
|
77
77
|
## 3. Detalhamento por bucket
|
|
78
78
|
|
|
79
|
-
### 3.1 `library-assets` —
|
|
79
|
+
### 3.1 `library-assets` — ✅ corrigido
|
|
80
80
|
|
|
81
81
|
- **Finalidade:** logos, favicons e marca branca da lib (Qualiex / Saber).
|
|
82
82
|
- **Consumidor:** [Admin](/projects/9dc9be11-bf85-4561-b36a-8d8f35fdbc06) — `lib/assets/index.ts`, `lib/setup/favicon.ts`. Consumido como leitura pública por todos os projetos via URL pública.
|
|
83
83
|
- **Estrutura:** raiz do bucket (`logo-qualiex-white.svg`, `favicon.png`, etc.).
|
|
84
84
|
- **Visibilidade:** público.
|
|
85
|
-
- **RLS atuais:**
|
|
86
|
-
|
|
85
|
+
- **RLS atuais (29/04/2026):** **nenhuma policy de write** no `storage.objects` para esse bucket. SELECT continua público pelo flag de bucket público. INSERT/UPDATE/DELETE bloqueados para qualquer role (apenas service_role contorna RLS).
|
|
86
|
+
- **Mudança recente:** a antiga policy `Allow public upload to library assets` (INSERT anônimo sem condição) foi **removida**.
|
|
87
87
|
- **Riscos:**
|
|
88
|
-
-
|
|
89
|
-
- 🟡 `LIST` anônimo (default de bucket público) expõe inventário, mas como são logos é baixo impacto.
|
|
90
|
-
- **Recomendação:**
|
|
88
|
+
- 🟢 Upload anônimo eliminado.
|
|
89
|
+
- 🟡 `LIST` anônimo (default de bucket público) ainda expõe inventário, mas como são logos é baixo impacto.
|
|
90
|
+
- **Recomendação:** se precisar permitir upload via app, adicionar policy `TO authenticated` + checagem de admin. Caso contrário, manter como está (gerenciado via service_role/console).
|
|
91
91
|
|
|
92
92
|
---
|
|
93
93
|
|
|
@@ -104,20 +104,23 @@ Legenda: ✅ uso confirmado em código · 🔎 só leitura · (policy) tem RLS m
|
|
|
104
104
|
|
|
105
105
|
---
|
|
106
106
|
|
|
107
|
-
### 3.3 `thumbnails` —
|
|
107
|
+
### 3.3 `thumbnails` — ⚠️ rollback aplicado (29/04/2026)
|
|
108
108
|
|
|
109
109
|
- **Finalidade:** miniaturas/capas de cursos e conteúdos.
|
|
110
110
|
- **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/modules/contents/hooks/useImageUpload.ts` (default bucket).
|
|
111
|
-
- **Estrutura:**
|
|
111
|
+
- **Estrutura atual:** sem prefixo obrigatório (uploads vão na raiz).
|
|
112
112
|
- **Visibilidade:** público.
|
|
113
|
-
- **RLS atuais:**
|
|
114
|
-
- `Users can upload thumbnails` —
|
|
115
|
-
- `
|
|
113
|
+
- **RLS atuais (29/04/2026 — pós-rollback):**
|
|
114
|
+
- `Users can upload thumbnails` — INSERT `TO authenticated`, sem scoping por alias.
|
|
115
|
+
- `Users can update thumbnails` — UPDATE `TO authenticated`, sem scoping por alias.
|
|
116
|
+
- `Users can delete thumbnails` — DELETE `TO authenticated`, sem scoping por alias.
|
|
117
|
+
- **Histórico:**
|
|
118
|
+
- Hardening anterior (`thumbnails_auth_insert/update/delete` exigindo `(storage.foldername(name))[1] = jwt.alias`) quebrou os uploads do projeto Educação, que escreve direto na raiz do bucket.
|
|
119
|
+
- Em 29/04/2026 fizemos rollback para o estado anterior (3 policies permissivas para `authenticated`).
|
|
116
120
|
- **Riscos:**
|
|
117
|
-
- 🔴
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
- **Recomendação:** trocar `Users can upload thumbnails` por uma policy `TO authenticated` com scoping `(storage.foldername(name))[1] = jwt.alias`. Aplicar o mesmo scoping em UPDATE/DELETE.
|
|
121
|
+
- 🔴 Sem isolamento multi-tenant: qualquer usuário autenticado pode sobrescrever/apagar arquivos de qualquer tenant.
|
|
122
|
+
- 🟡 `LIST` público (bucket público) — inventário visível.
|
|
123
|
+
- **Próximo passo (P1):** ajustar `src/modules/contents/hooks/useImageUpload.ts` para gravar em `{alias}/...` e então reaplicar as policies com scoping por alias.
|
|
121
124
|
|
|
122
125
|
---
|
|
123
126
|
|
|
@@ -240,24 +243,26 @@ Bucket multi-projeto, segregado por subpasta. **Usado por 4 projetos** (Colabora
|
|
|
240
243
|
| `evidence-attachments/{evidenceId}/...` | Matriz de Foco | Anexos de evidências |
|
|
241
244
|
| `evidences/...` | PDI | Anexos de planos de ação |
|
|
242
245
|
|
|
243
|
-
- **RLS atuais:**
|
|
244
|
-
- `interview_scoped_select` — SELECT escopado
|
|
245
|
-
- `
|
|
246
|
-
- `
|
|
247
|
-
- `
|
|
248
|
-
- `
|
|
249
|
-
- `
|
|
250
|
-
-
|
|
246
|
+
- **RLS atuais (29/04/2026):**
|
|
247
|
+
- `interview_scoped_select` — SELECT escopado em `interviews/{alias}/...`.
|
|
248
|
+
- `authenticated_interview_upload` — INSERT genérico para `auth.role() = 'authenticated'`, **sem scoping por subpasta/alias** (legado pré-migração `20260328223532`).
|
|
249
|
+
- `training_requests_attachments_select/insert/delete` — escopados em `training-requests/{alias}/...`.
|
|
250
|
+
- `imported_evidence_select/insert` — escopados em `imported-evidence/{alias}/...` (sem DELETE/UPDATE).
|
|
251
|
+
- `evidence_uploads_select` — SELECT `TO authenticated` em `evidence-images/...` e `evidence-attachments/...`, **sem checagem de alias**.
|
|
252
|
+
- `evidence_uploads_delete` — DELETE `TO authenticated` em `evidence-images/...` e `evidence-attachments/...`, **sem checagem de alias**.
|
|
253
|
+
- `Authenticated users can update user-uploads` — UPDATE `TO authenticated` exigindo `(storage.foldername(name))[2] = jwt.alias` (cobre `*/{alias}/...`, mas não `evidence-images/` ou `evidences/` que não têm alias na 2ª pasta).
|
|
254
|
+
- **Não há policies para `evidences/...` (PDI)** — INSERT/SELECT/DELETE caem no INSERT genérico de `authenticated_interview_upload` para gravar e provavelmente no SELECT genérico de outra migração.
|
|
251
255
|
|
|
252
256
|
- **Riscos:**
|
|
253
|
-
- 🔴
|
|
254
|
-
- 🔴
|
|
257
|
+
- 🔴 `authenticated_interview_upload` continua permitindo que qualquer authenticated grave em qualquer subpasta do bucket (cross-tenant + cross-projeto).
|
|
258
|
+
- 🔴 `evidence_uploads_select/delete` não checam alias — qualquer authenticated lê/deleta evidências da Matriz de Foco de outros tenants.
|
|
259
|
+
- 🟠 PDI (`evidences/...`) sem policies dedicadas; depende da policy genérica.
|
|
255
260
|
- 🟠 Padrão de path inconsistente entre projetos (uns usam alias na 1ª pasta, outros na 3ª, outros não usam).
|
|
256
261
|
|
|
257
262
|
- **Recomendação:**
|
|
258
263
|
- Padronizar para `{projeto}/{alias}/...`.
|
|
259
|
-
- Substituir
|
|
260
|
-
-
|
|
264
|
+
- Substituir `authenticated_interview_upload` por INSERT por subpasta + alias.
|
|
265
|
+
- Adicionar checagem de alias em `evidence_uploads_select/delete` (Matriz de Foco) e criar policies dedicadas para `evidences/...` (PDI).
|
|
261
266
|
|
|
262
267
|
---
|
|
263
268
|
|
|
@@ -357,12 +362,12 @@ Boas práticas observadas no projeto:
|
|
|
357
362
|
|
|
358
363
|
| Prioridade | Bucket | Ação |
|
|
359
364
|
|---|---|---|
|
|
360
|
-
|
|
|
361
|
-
|
|
|
362
|
-
| 🔴 P0 | `user-uploads` | Substituir
|
|
365
|
+
| ✅ done | `library-assets` | INSERT anônimo removido (sem policies de write). |
|
|
366
|
+
| ✅ done | `thumbnails` | INSERT/UPDATE/DELETE `TO authenticated` com scoping por alias. |
|
|
367
|
+
| 🔴 P0 | `user-uploads` | Substituir `authenticated_interview_upload` (INSERT genérico) por INSERT por subpasta + alias; adicionar checagem de alias em `evidence_uploads_select/delete`; criar policies dedicadas para `evidences/...` (PDI). |
|
|
363
368
|
| 🔴 P0 | `certificates` | Tornar privado + migrar Treinamentos para signed URLs. |
|
|
364
|
-
| 🟠 P1 | `university-assets`, `content-files` | Adicionar scoping por alias em UPDATE/DELETE/INSERT. |
|
|
365
369
|
| 🔴 P0 | `performance` | Realinhar path do código (`evidences/`, `task-reports/`) com a policy de INSERT (que exige `{alias}/...`); tornar privado; adicionar SELECT escopado. |
|
|
370
|
+
| 🟠 P1 | `university-assets`, `content-files` | Adicionar scoping por alias em UPDATE/DELETE/INSERT. |
|
|
366
371
|
| 🟠 P1 | `performance-files` | Deprecar bucket — nenhum código consumidor encontrado; backup dos 33 objetos e remover. |
|
|
367
372
|
| 🟠 P1 | `resumes` | Unificar convenção de path (alias + uid) e adicionar UPDATE/DELETE. |
|
|
368
373
|
| 🟡 P2 | `contracts`, `imports` | Padronizar `TO authenticated` (limpeza, sem mudança funcional). |
|
|
@@ -277,6 +277,7 @@ import {
|
|
|
277
277
|
| `onOpenChange` | `(open: boolean) => void` | - | Tooltip: Callback quando o estado muda |
|
|
278
278
|
| `sideOffset` | `number` | 4 | TooltipContent: Distância em pixels do trigger |
|
|
279
279
|
| `alignOffset` | `number` | 0 | TooltipContent: Offset do alinhamento em pixels |
|
|
280
|
+
| `container` | `HTMLElement | null` | document.body | TooltipContent: Nó-alvo do Portal. O conteúdo é portado por padrão para document.body, evitando clipping em containers com overflow. |
|
|
280
281
|
|
|
281
282
|
**Exemplos:**
|
|
282
283
|
```tsx
|
|
@@ -324,6 +325,7 @@ import {
|
|
|
324
325
|
- Evite tooltips em elementos que requerem ação rápida (botões principais)
|
|
325
326
|
- Combine com ícones de informação para ajuda contextual
|
|
326
327
|
- Use delayDuration={0} para exibição instantânea quando necessário
|
|
328
|
+
- TooltipContent é renderizado via Portal (default: document.body), evitando clipping em sidebars, cards com scroll, dialogs e demais containers com overflow. Use a prop opcional container para portar para um nó específico.
|
|
327
329
|
|
|
328
330
|
> Fonte: `src/design-system/docs/components/TooltipDoc.tsx`
|
|
329
331
|
|
|
@@ -88,6 +88,23 @@ const options = [
|
|
|
88
88
|
placeholder="Select multiple..."
|
|
89
89
|
/>
|
|
90
90
|
|
|
91
|
+
// Seleção única com botão limpar (clearable=true por padrão)
|
|
92
|
+
<Combobox
|
|
93
|
+
options={options}
|
|
94
|
+
value={selected}
|
|
95
|
+
onChange={setSelected}
|
|
96
|
+
placeholder="Selecione..."
|
|
97
|
+
clearable
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
// Desabilitar botão limpar
|
|
101
|
+
<Combobox
|
|
102
|
+
options={options}
|
|
103
|
+
value={selected}
|
|
104
|
+
onChange={setSelected}
|
|
105
|
+
clearable={false}
|
|
106
|
+
/>
|
|
107
|
+
|
|
91
108
|
// =====================
|
|
92
109
|
// POPOVER COM COMMAND
|
|
93
110
|
// =====================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forlogic-core",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -85,10 +85,12 @@
|
|
|
85
85
|
"@tiptap/extension-underline": "^3.14.0",
|
|
86
86
|
"@tiptap/react": "^3.14.0",
|
|
87
87
|
"@tiptap/starter-kit": "^3.14.0",
|
|
88
|
+
"@types/dompurify": "^3.2.0",
|
|
88
89
|
"class-variance-authority": "^0.7.1",
|
|
89
90
|
"clsx": "^2.1.1",
|
|
90
91
|
"date-fns": "^3.6.0",
|
|
91
92
|
"date-fns-tz": "^3.2.0",
|
|
93
|
+
"dompurify": "^3.4.2",
|
|
92
94
|
"exceljs": "^4.4.0",
|
|
93
95
|
"i18next-browser-languagedetector": "^8.2.0",
|
|
94
96
|
"jszip": "^3.10.1",
|