@flowtomic/logic 0.1.0 → 0.1.1
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/hooks/useAnimatedIndicator/index.d.ts +3 -0
- package/dist/hooks/useAnimatedIndicator/index.d.ts.map +1 -0
- package/dist/hooks/useAnimatedIndicator/index.js +1 -0
- package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.d.ts +94 -0
- package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.d.ts.map +1 -0
- package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.js +228 -0
- package/dist/hooks/useGenealogy/index.d.ts +3 -0
- package/dist/hooks/useGenealogy/index.d.ts.map +1 -0
- package/dist/hooks/useGenealogy/index.js +1 -0
- package/dist/hooks/useGenealogy/useGenealogy.d.ts +96 -0
- package/dist/hooks/useGenealogy/useGenealogy.d.ts.map +1 -0
- package/dist/hooks/useGenealogy/useGenealogy.js +335 -0
- package/dist/hooks/useMoblile/index.js +1 -868
- package/dist/hooks/useProjectProgress/index.d.ts +3 -0
- package/dist/hooks/useProjectProgress/index.d.ts.map +1 -0
- package/dist/hooks/useProjectProgress/index.js +1 -0
- package/dist/hooks/useProjectProgress/useProjectProgress.d.ts +103 -0
- package/dist/hooks/useProjectProgress/useProjectProgress.d.ts.map +1 -0
- package/dist/hooks/useProjectProgress/useProjectProgress.js +124 -0
- package/dist/hooks/useProjectStats/index.d.ts +3 -0
- package/dist/hooks/useProjectStats/index.d.ts.map +1 -0
- package/dist/hooks/useProjectStats/index.js +1 -0
- package/dist/hooks/useProjectStats/useProjectStats.d.ts +130 -0
- package/dist/hooks/useProjectStats/useProjectStats.d.ts.map +1 -0
- package/dist/hooks/useProjectStats/useProjectStats.js +87 -0
- package/dist/hooks/useResizable/index.d.ts +1 -1
- package/dist/hooks/useResizable/index.d.ts.map +1 -1
- package/dist/hooks/useStatCard/useStatCard.d.ts +44 -5
- package/dist/hooks/useStatCard/useStatCard.d.ts.map +1 -1
- package/dist/hooks/useStatCard/useStatCard.js +81 -20
- package/dist/hooks/useTimeTracker/index.d.ts +3 -0
- package/dist/hooks/useTimeTracker/index.d.ts.map +1 -0
- package/dist/hooks/useTimeTracker/index.js +1 -0
- package/dist/hooks/useTimeTracker/useTimeTracker.d.ts +106 -0
- package/dist/hooks/useTimeTracker/useTimeTracker.d.ts.map +1 -0
- package/dist/hooks/useTimeTracker/useTimeTracker.js +143 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGenealogy - Headless UI Hook
|
|
3
|
+
*
|
|
4
|
+
* Fornece apenas: lógica, estado, processamento e API para árvores genealógicas
|
|
5
|
+
* NÃO fornece: markup, styles ou implementações pré-construídas
|
|
6
|
+
*
|
|
7
|
+
* Padrão Headless UI: você controla o markup e styles
|
|
8
|
+
*/
|
|
9
|
+
import { useCallback, useMemo, useState } from "react";
|
|
10
|
+
/**
|
|
11
|
+
* Calcula o nível (geração) de cada entidade na hierarquia
|
|
12
|
+
* Retorna um Map com entityId -> nível (0 = raiz, 1 = filhos da raiz, etc)
|
|
13
|
+
*/
|
|
14
|
+
function calculateLevels(hierarchy) {
|
|
15
|
+
const levels = new Map();
|
|
16
|
+
const visited = new Set();
|
|
17
|
+
// Função recursiva para calcular nível
|
|
18
|
+
const calculateLevel = (entityId) => {
|
|
19
|
+
if (levels.has(entityId)) {
|
|
20
|
+
return levels.get(entityId);
|
|
21
|
+
}
|
|
22
|
+
if (visited.has(entityId)) {
|
|
23
|
+
// Ciclo detectado, retornar 0 como fallback
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
visited.add(entityId);
|
|
27
|
+
const entityHierarchy = hierarchy.get(entityId);
|
|
28
|
+
if (!entityHierarchy || entityHierarchy.parents.length === 0) {
|
|
29
|
+
// Sem pais = raiz (nível 0)
|
|
30
|
+
levels.set(entityId, 0);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
// Nível = nível do pai mais alto + 1
|
|
34
|
+
const parentLevels = entityHierarchy.parents.map((parent) => calculateLevel(parent.id));
|
|
35
|
+
const maxParentLevel = Math.max(...parentLevels);
|
|
36
|
+
const level = maxParentLevel + 1;
|
|
37
|
+
levels.set(entityId, level);
|
|
38
|
+
return level;
|
|
39
|
+
};
|
|
40
|
+
// Calcular nível para todas as entidades
|
|
41
|
+
hierarchy.forEach((_, entityId) => {
|
|
42
|
+
calculateLevel(entityId);
|
|
43
|
+
});
|
|
44
|
+
return levels;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Organiza entidades por nível (geração) e calcula posições hierárquicas
|
|
48
|
+
*/
|
|
49
|
+
function calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
|
|
50
|
+
const positions = new Map();
|
|
51
|
+
const levels = calculateLevels(hierarchy);
|
|
52
|
+
// Agrupar entidades por nível
|
|
53
|
+
const entitiesByLevel = new Map();
|
|
54
|
+
levels.forEach((level, entityId) => {
|
|
55
|
+
if (!entitiesByLevel.has(level)) {
|
|
56
|
+
entitiesByLevel.set(level, []);
|
|
57
|
+
}
|
|
58
|
+
entitiesByLevel.get(level).push(entityId);
|
|
59
|
+
});
|
|
60
|
+
// Calcular posições para cada nível, começando do nível 0 (raiz)
|
|
61
|
+
const sortedLevels = Array.from(entitiesByLevel.keys()).sort((a, b) => a - b);
|
|
62
|
+
sortedLevels.forEach((level) => {
|
|
63
|
+
const entityIds = entitiesByLevel.get(level);
|
|
64
|
+
// Ordenar entidades do mesmo nível (por ID para consistência)
|
|
65
|
+
entityIds.sort();
|
|
66
|
+
// Calcular posição Y baseada no nível (pais acima, filhos abaixo)
|
|
67
|
+
const y = level * (nodeHeight + verticalSpacing);
|
|
68
|
+
// Para cada entidade, tentar centralizar abaixo dos pais
|
|
69
|
+
entityIds.forEach((entityId) => {
|
|
70
|
+
const entityHierarchy = hierarchy.get(entityId);
|
|
71
|
+
let x = 0;
|
|
72
|
+
if (entityHierarchy && entityHierarchy.parents.length > 0) {
|
|
73
|
+
// Calcular posição média dos pais
|
|
74
|
+
const parentPositions = entityHierarchy.parents
|
|
75
|
+
.map((parent) => positions.get(parent.id))
|
|
76
|
+
.filter((pos) => pos !== undefined);
|
|
77
|
+
if (parentPositions.length > 0) {
|
|
78
|
+
const avgX = parentPositions.reduce((sum, pos) => sum + pos.x, 0) / parentPositions.length;
|
|
79
|
+
x = avgX;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Se não tem pais ou não conseguiu calcular, distribuir horizontalmente
|
|
83
|
+
if (x === 0 && entityIds.length > 1) {
|
|
84
|
+
const index = entityIds.indexOf(entityId);
|
|
85
|
+
const totalWidth = entityIds.length * nodeWidth + (entityIds.length - 1) * horizontalSpacing;
|
|
86
|
+
const startX = -totalWidth / 2 + nodeWidth / 2;
|
|
87
|
+
x = startX + index * (nodeWidth + horizontalSpacing);
|
|
88
|
+
}
|
|
89
|
+
positions.set(entityId, { x, y });
|
|
90
|
+
});
|
|
91
|
+
// Ajustar posições para evitar sobreposição
|
|
92
|
+
entityIds.forEach((entityId, index) => {
|
|
93
|
+
const currentPos = positions.get(entityId);
|
|
94
|
+
const minX = currentPos.x;
|
|
95
|
+
// Verificar se há sobreposição com outros nós do mesmo nível
|
|
96
|
+
entityIds.slice(0, index).forEach((otherId) => {
|
|
97
|
+
const otherPos = positions.get(otherId);
|
|
98
|
+
if (Math.abs(currentPos.x - otherPos.x) < nodeWidth + horizontalSpacing) {
|
|
99
|
+
// Ajustar para evitar sobreposição
|
|
100
|
+
currentPos.x = otherPos.x + nodeWidth + horizontalSpacing;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
return positions;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Gera nodes do ReactFlow a partir dos dados de genealogia
|
|
109
|
+
*/
|
|
110
|
+
function generateNodes(data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
|
|
111
|
+
const nodes = [];
|
|
112
|
+
// Calcular posições hierárquicas
|
|
113
|
+
const positions = calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing);
|
|
114
|
+
// Criar nodes com posições calculadas
|
|
115
|
+
data.people.forEach((entity) => {
|
|
116
|
+
const position = positions.get(entity.id) || { x: 0, y: 0 };
|
|
117
|
+
const entityHierarchy = hierarchy.get(entity.id);
|
|
118
|
+
nodes.push({
|
|
119
|
+
id: entity.id,
|
|
120
|
+
type: "genealogy",
|
|
121
|
+
position,
|
|
122
|
+
data: {
|
|
123
|
+
person: entity,
|
|
124
|
+
hierarchy: entityHierarchy,
|
|
125
|
+
isExpanded: expansionState[entity.id] ?? false,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
return nodes;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Gera edges do ReactFlow a partir dos relacionamentos
|
|
133
|
+
* Para hierarquia vertical: source (pai) -> target (filho)
|
|
134
|
+
*/
|
|
135
|
+
function generateEdges(data, hierarchy, expansionState) {
|
|
136
|
+
const edges = [];
|
|
137
|
+
const edgeIds = new Set();
|
|
138
|
+
data.relationships.forEach((relationship) => {
|
|
139
|
+
// Determinar source e target baseado no tipo de relacionamento
|
|
140
|
+
let source;
|
|
141
|
+
let target;
|
|
142
|
+
switch (relationship.type) {
|
|
143
|
+
case "father":
|
|
144
|
+
case "mother": {
|
|
145
|
+
// from é pai/mãe de to
|
|
146
|
+
// source = pai (from), target = filho (to)
|
|
147
|
+
source = relationship.from;
|
|
148
|
+
target = relationship.to;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "child": {
|
|
152
|
+
// from é filho de to
|
|
153
|
+
// source = pai (to), target = filho (from)
|
|
154
|
+
source = relationship.to;
|
|
155
|
+
target = relationship.from;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
default: {
|
|
159
|
+
source = relationship.from;
|
|
160
|
+
target = relationship.to;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Criar edge ID único
|
|
164
|
+
const edgeId = `e-${source}-${target}-${relationship.type}`;
|
|
165
|
+
// Evitar duplicatas
|
|
166
|
+
if (edgeIds.has(edgeId)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
edgeIds.add(edgeId);
|
|
170
|
+
// Verificar se ambos os nós existem
|
|
171
|
+
const sourceHierarchy = hierarchy.get(source);
|
|
172
|
+
const targetHierarchy = hierarchy.get(target);
|
|
173
|
+
if (!sourceHierarchy || !targetHierarchy) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Só criar edge se ambos os nós estiverem visíveis (expandidos ou raiz)
|
|
177
|
+
const sourceExpanded = expansionState[source] ?? true;
|
|
178
|
+
const targetExpanded = expansionState[target] ?? true;
|
|
179
|
+
// Para relacionamentos pai/filho, só mostrar se o nó pai estiver expandido
|
|
180
|
+
if (relationship.type === "father" ||
|
|
181
|
+
relationship.type === "mother" ||
|
|
182
|
+
relationship.type === "child") {
|
|
183
|
+
if (!sourceExpanded || !targetExpanded)
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
edges.push({
|
|
187
|
+
id: edgeId,
|
|
188
|
+
source,
|
|
189
|
+
target,
|
|
190
|
+
type: "animated",
|
|
191
|
+
data: {
|
|
192
|
+
relationship,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
return edges;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Constrói hierarquia de entidades a partir dos dados
|
|
200
|
+
*/
|
|
201
|
+
function buildHierarchy(data) {
|
|
202
|
+
const hierarchy = new Map();
|
|
203
|
+
// Inicializar todas as entidades
|
|
204
|
+
data.people.forEach((entity) => {
|
|
205
|
+
hierarchy.set(entity.id, {
|
|
206
|
+
person: entity,
|
|
207
|
+
parents: [],
|
|
208
|
+
children: [],
|
|
209
|
+
adoptions: [],
|
|
210
|
+
siblings: [],
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
// Processar relacionamentos
|
|
214
|
+
data.relationships.forEach((relationship) => {
|
|
215
|
+
const fromHierarchy = hierarchy.get(relationship.from);
|
|
216
|
+
const toHierarchy = hierarchy.get(relationship.to);
|
|
217
|
+
if (!fromHierarchy || !toHierarchy)
|
|
218
|
+
return;
|
|
219
|
+
switch (relationship.type) {
|
|
220
|
+
case "father":
|
|
221
|
+
case "mother": {
|
|
222
|
+
// from é pai/mãe de to
|
|
223
|
+
if (!toHierarchy.parents.find((p) => p.id === relationship.from)) {
|
|
224
|
+
toHierarchy.parents.push(fromHierarchy.person);
|
|
225
|
+
}
|
|
226
|
+
if (relationship.type === "father") {
|
|
227
|
+
toHierarchy.father = fromHierarchy.person;
|
|
228
|
+
}
|
|
229
|
+
else if (relationship.type === "mother") {
|
|
230
|
+
toHierarchy.mother = fromHierarchy.person;
|
|
231
|
+
}
|
|
232
|
+
if (!fromHierarchy.children.find((c) => c.id === relationship.to)) {
|
|
233
|
+
fromHierarchy.children.push(toHierarchy.person);
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "child": {
|
|
238
|
+
// from é filho de to
|
|
239
|
+
if (!toHierarchy.children.find((c) => c.id === relationship.from)) {
|
|
240
|
+
toHierarchy.children.push(fromHierarchy.person);
|
|
241
|
+
}
|
|
242
|
+
if (!fromHierarchy.parents.find((p) => p.id === relationship.to)) {
|
|
243
|
+
fromHierarchy.parents.push(toHierarchy.person);
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
// Calcular irmãos (pessoas com os mesmos pais)
|
|
250
|
+
hierarchy.forEach((personHierarchy) => {
|
|
251
|
+
if (personHierarchy.parents.length > 0) {
|
|
252
|
+
const parentIds = personHierarchy.parents.map((p) => p.id);
|
|
253
|
+
hierarchy.forEach((otherHierarchy, otherId) => {
|
|
254
|
+
if (otherId !== personHierarchy.person.id) {
|
|
255
|
+
const hasCommonParent = otherHierarchy.parents.some((p) => parentIds.includes(p.id));
|
|
256
|
+
if (hasCommonParent && !personHierarchy.siblings.find((s) => s.id === otherId)) {
|
|
257
|
+
personHierarchy.siblings.push(otherHierarchy.person);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return hierarchy;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Hook headless para gerenciar árvores genealógicas
|
|
267
|
+
*/
|
|
268
|
+
export function useGenealogy({ data, initialExpanded = [], nodeWidth = 200, nodeHeight = 100, horizontalSpacing = 250, verticalSpacing = 150, onNodeSelect, onNodeExpand, onAddRelation, }) {
|
|
269
|
+
// Estado de expansão dos nós
|
|
270
|
+
const [expansionState, setExpansionState] = useState(() => {
|
|
271
|
+
const state = {};
|
|
272
|
+
initialExpanded.forEach((id) => {
|
|
273
|
+
state[id] = true;
|
|
274
|
+
});
|
|
275
|
+
return state;
|
|
276
|
+
});
|
|
277
|
+
// Estado de seleção
|
|
278
|
+
const [selectedNodeId, setSelectedNodeId] = useState(null);
|
|
279
|
+
// Construir hierarquia
|
|
280
|
+
const hierarchy = useMemo(() => buildHierarchy(data), [data]);
|
|
281
|
+
// Gerar nodes e edges
|
|
282
|
+
const nodes = useMemo(() => generateNodes(data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing), [data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing]);
|
|
283
|
+
const edges = useMemo(() => generateEdges(data, hierarchy, expansionState), [data, hierarchy, expansionState]);
|
|
284
|
+
// Função para selecionar um nó
|
|
285
|
+
const selectNode = useCallback((nodeId) => {
|
|
286
|
+
setSelectedNodeId(nodeId);
|
|
287
|
+
const entity = data.people.find((p) => p.id === nodeId);
|
|
288
|
+
if (entity && onNodeSelect) {
|
|
289
|
+
onNodeSelect(nodeId, entity);
|
|
290
|
+
}
|
|
291
|
+
}, [data.people, onNodeSelect]);
|
|
292
|
+
// Função para expandir/colapsar um nó
|
|
293
|
+
const expandNode = useCallback((nodeId, expanded) => {
|
|
294
|
+
setExpansionState((prev) => {
|
|
295
|
+
const newState = { ...prev };
|
|
296
|
+
const isCurrentlyExpanded = prev[nodeId] ?? false;
|
|
297
|
+
newState[nodeId] = expanded ?? !isCurrentlyExpanded;
|
|
298
|
+
if (onNodeExpand) {
|
|
299
|
+
onNodeExpand(nodeId, newState[nodeId]);
|
|
300
|
+
}
|
|
301
|
+
return newState;
|
|
302
|
+
});
|
|
303
|
+
}, [onNodeExpand]);
|
|
304
|
+
// Função para colapsar um nó
|
|
305
|
+
const collapseNode = useCallback((nodeId) => {
|
|
306
|
+
expandNode(nodeId, false);
|
|
307
|
+
}, [expandNode]);
|
|
308
|
+
// Função para adicionar relacionamento
|
|
309
|
+
const addRelation = useCallback((fromId, toId, type) => {
|
|
310
|
+
if (onAddRelation) {
|
|
311
|
+
onAddRelation(fromId, toId, type);
|
|
312
|
+
}
|
|
313
|
+
}, [onAddRelation]);
|
|
314
|
+
// Função para obter hierarquia de uma pessoa
|
|
315
|
+
const getPersonHierarchy = useCallback((personId) => {
|
|
316
|
+
return hierarchy.get(personId);
|
|
317
|
+
}, [hierarchy]);
|
|
318
|
+
// Função para obter relacionamentos de uma pessoa
|
|
319
|
+
const getPersonRelations = useCallback((personId) => {
|
|
320
|
+
return data.relationships.filter((r) => r.from === personId || r.to === personId);
|
|
321
|
+
}, [data.relationships]);
|
|
322
|
+
return {
|
|
323
|
+
nodes,
|
|
324
|
+
edges,
|
|
325
|
+
hierarchy,
|
|
326
|
+
selectedNodeId,
|
|
327
|
+
expansionState,
|
|
328
|
+
selectNode,
|
|
329
|
+
expandNode,
|
|
330
|
+
collapseNode,
|
|
331
|
+
addRelation,
|
|
332
|
+
getPersonHierarchy,
|
|
333
|
+
getPersonRelations,
|
|
334
|
+
};
|
|
335
|
+
}
|