@flowtomic/logic 0.1.7 → 0.1.8

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.
Files changed (38) hide show
  1. package/dist/hooks/useDashboardLayout/index.d.ts +2 -2
  2. package/dist/hooks/useDashboardLayout/index.d.ts.map +1 -1
  3. package/dist/hooks/useDashboardLayout/useDashboardLayout.d.ts +21 -0
  4. package/dist/hooks/useDashboardLayout/useDashboardLayout.d.ts.map +1 -1
  5. package/dist/hooks/useResizable/index.d.ts +1 -1
  6. package/dist/hooks/useResizable/index.d.ts.map +1 -1
  7. package/dist/hooks/useResizable/useResizable.d.ts +5 -0
  8. package/dist/hooks/useResizable/useResizable.d.ts.map +1 -1
  9. package/dist/index.js +2 -2
  10. package/package.json +3 -3
  11. package/dist/hooks/useAnimatedIndicator/index.js +0 -1
  12. package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.js +0 -242
  13. package/dist/hooks/useAutocomplete/index.js +0 -1
  14. package/dist/hooks/useAutocomplete/useAutocomplete.js +0 -380
  15. package/dist/hooks/useDashboardLayout/index.js +0 -1
  16. package/dist/hooks/useDashboardLayout/useDashboardLayout.js +0 -175
  17. package/dist/hooks/useGenealogy/index.js +0 -1
  18. package/dist/hooks/useGenealogy/useGenealogy.js +0 -341
  19. package/dist/hooks/useMoblile/index.js +0 -1
  20. package/dist/hooks/useMoblile/useMobile.js +0 -15
  21. package/dist/hooks/useProjectProgress/index.js +0 -1
  22. package/dist/hooks/useProjectProgress/useProjectProgress.js +0 -124
  23. package/dist/hooks/useProjectStats/index.js +0 -1
  24. package/dist/hooks/useProjectStats/useProjectStats.js +0 -87
  25. package/dist/hooks/useReactTableBack/index.js +0 -1
  26. package/dist/hooks/useReactTableBack/useReactTableBack.js +0 -124
  27. package/dist/hooks/useReactTableFront/index.js +0 -1
  28. package/dist/hooks/useReactTableFront/useReactTableFront.js +0 -112
  29. package/dist/hooks/useResizable/index.js +0 -1
  30. package/dist/hooks/useResizable/useResizable.js +0 -129
  31. package/dist/hooks/useScriptEditor/index.js +0 -1
  32. package/dist/hooks/useScriptEditor/useScriptEditor.js +0 -247
  33. package/dist/hooks/useStatCard/index.js +0 -1
  34. package/dist/hooks/useStatCard/useStatCard.js +0 -187
  35. package/dist/hooks/useThemeTransition/index.js +0 -1
  36. package/dist/hooks/useThemeTransition/useThemeTransition.js +0 -35
  37. package/dist/hooks/useTimeTracker/index.js +0 -1
  38. package/dist/hooks/useTimeTracker/useTimeTracker.js +0 -143
@@ -1,175 +0,0 @@
1
- /**
2
- * useDashboardLayout - Headless UI Hook
3
- *
4
- * Fornece apenas: lógica, estado, processamento e API
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
- * @example
10
- * ```tsx
11
- * function MyDashboard() {
12
- * const [widgets, setWidgets] = useState<WidgetLayout[]>([]);
13
- * const gridConfig: GridConfig = { columns: 12, cellSize: 50, gap: 16 };
14
- *
15
- * const {
16
- * getWidgetGridPosition,
17
- * moveWidget,
18
- * resizeWidget,
19
- * findNextAvailablePosition,
20
- * } = useDashboardLayout({
21
- * widgetsLayout: widgets,
22
- * gridConfig,
23
- * onUpdateLayout: setWidgets,
24
- * });
25
- *
26
- * return (
27
- * <div>
28
- * {widgets.map(widget => (
29
- * <div style={getWidgetGridPosition(widget)}>
30
- * Widget Content
31
- * </div>
32
- * ))}
33
- * </div>
34
- * );
35
- * }
36
- * ```
37
- */
38
- import { useCallback, useMemo } from "react";
39
- const DEFAULT_GRID_CONFIG = {
40
- columns: 12,
41
- cellSize: 50,
42
- gap: 16,
43
- };
44
- /**
45
- * Hook para gerenciar layout do dashboard
46
- *
47
- * Hook headless que fornece apenas lógica de layout, sem UI.
48
- * Você controla o markup e styles.
49
- */
50
- export function useDashboardLayout({ widgetsLayout, gridConfig, onUpdateLayout, }) {
51
- // Normaliza gridConfig com defaults
52
- const normalizedGridConfig = useMemo(() => {
53
- return {
54
- ...DEFAULT_GRID_CONFIG,
55
- ...gridConfig,
56
- };
57
- }, [gridConfig]);
58
- /**
59
- * Calcula posição CSS Grid para um widget
60
- */
61
- const getWidgetGridPosition = useCallback((widget) => {
62
- return {
63
- gridColumnStart: widget.x + 1, // CSS Grid é 1-indexed
64
- gridColumnEnd: widget.x + widget.w + 1,
65
- gridRowStart: widget.y + 1,
66
- gridRowEnd: widget.y + widget.h + 1,
67
- };
68
- }, []);
69
- /**
70
- * Verifica se uma posição está ocupada
71
- */
72
- const isPositionOccupied = useCallback((x, y, w, h, excludeWidgetId) => {
73
- return widgetsLayout.some((widget) => {
74
- if (excludeWidgetId && widget.id === excludeWidgetId) {
75
- return false;
76
- }
77
- // Verifica sobreposição
78
- const widgetRight = widget.x + widget.w;
79
- const widgetBottom = widget.y + widget.h;
80
- const newRight = x + w;
81
- const newBottom = y + h;
82
- return !(widgetRight <= x ||
83
- widget.x >= newRight ||
84
- widgetBottom <= y ||
85
- widget.y >= newBottom);
86
- });
87
- }, [widgetsLayout]);
88
- /**
89
- * Encontra próxima posição disponível
90
- */
91
- const findNextAvailablePosition = useCallback((w, h) => {
92
- const maxColumns = normalizedGridConfig.columns;
93
- const maxRows = 100; // Limite razoável
94
- for (let y = 0; y < maxRows; y++) {
95
- for (let x = 0; x <= maxColumns - w; x++) {
96
- if (!isPositionOccupied(x, y, w, h)) {
97
- return { x, y };
98
- }
99
- }
100
- }
101
- return null;
102
- }, [normalizedGridConfig.columns, isPositionOccupied]);
103
- /**
104
- * Valida se um widget pode ser movido para uma posição
105
- */
106
- const canMoveToPosition = useCallback((widgetId, x, y) => {
107
- const widget = widgetsLayout.find((w) => w.id === widgetId);
108
- if (!widget)
109
- return false;
110
- // Valida limites do grid
111
- if (x < 0 || y < 0)
112
- return false;
113
- if (x + widget.w > normalizedGridConfig.columns)
114
- return false;
115
- // Verifica colisões
116
- return !isPositionOccupied(x, y, widget.w, widget.h, widgetId);
117
- }, [widgetsLayout, normalizedGridConfig.columns, isPositionOccupied]);
118
- /**
119
- * Valida se um widget pode ser redimensionado
120
- */
121
- const canResize = useCallback((widgetId, newW, newH) => {
122
- const widget = widgetsLayout.find((w) => w.id === widgetId);
123
- if (!widget)
124
- return false;
125
- // Valida tamanhos mínimos e máximos
126
- if (newW < 2 || newH < 2)
127
- return false; // Mínimo 2x2
128
- if (newW > normalizedGridConfig.columns)
129
- return false;
130
- if (newH > 20)
131
- return false; // Máximo 20 linhas
132
- // Valida limites do grid
133
- if (widget.x + newW > normalizedGridConfig.columns)
134
- return false;
135
- // Verifica colisões
136
- return !isPositionOccupied(widget.x, widget.y, newW, newH, widgetId);
137
- }, [widgetsLayout, normalizedGridConfig.columns, isPositionOccupied]);
138
- /**
139
- * Move widget para nova posição
140
- */
141
- const moveWidget = useCallback((widgetId, x, y) => {
142
- if (!canMoveToPosition(widgetId, x, y)) {
143
- return;
144
- }
145
- const updatedLayout = widgetsLayout.map((widget) => widget.id === widgetId ? { ...widget, x, y } : widget);
146
- onUpdateLayout(updatedLayout);
147
- }, [widgetsLayout, canMoveToPosition, onUpdateLayout]);
148
- /**
149
- * Redimensiona widget
150
- */
151
- const resizeWidget = useCallback((widgetId, w, h) => {
152
- if (!canResize(widgetId, w, h)) {
153
- return;
154
- }
155
- const updatedLayout = widgetsLayout.map((widget) => widget.id === widgetId ? { ...widget, w, h } : widget);
156
- onUpdateLayout(updatedLayout);
157
- }, [widgetsLayout, canResize, onUpdateLayout]);
158
- /**
159
- * Atualiza layout completo
160
- */
161
- const updateLayout = useCallback((layout) => {
162
- onUpdateLayout(layout);
163
- }, [onUpdateLayout]);
164
- return {
165
- gridConfig: normalizedGridConfig,
166
- getWidgetGridPosition,
167
- isPositionOccupied,
168
- findNextAvailablePosition,
169
- canMoveToPosition,
170
- canResize,
171
- moveWidget,
172
- resizeWidget,
173
- updateLayout,
174
- };
175
- }
@@ -1 +0,0 @@
1
- export { useGenealogy } from "./useGenealogy";
@@ -1,341 +0,0 @@
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
- const cachedLevel = levels.get(entityId);
20
- if (cachedLevel !== undefined) {
21
- return cachedLevel;
22
- }
23
- if (visited.has(entityId)) {
24
- // Ciclo detectado, retornar 0 como fallback
25
- return 0;
26
- }
27
- visited.add(entityId);
28
- const entityHierarchy = hierarchy.get(entityId);
29
- if (!entityHierarchy || entityHierarchy.parents.length === 0) {
30
- // Sem pais = raiz (nível 0)
31
- levels.set(entityId, 0);
32
- return 0;
33
- }
34
- // Nível = nível do pai mais alto + 1
35
- const parentLevels = entityHierarchy.parents.map((parent) => calculateLevel(parent.id));
36
- const maxParentLevel = Math.max(...parentLevels);
37
- const level = maxParentLevel + 1;
38
- levels.set(entityId, level);
39
- return level;
40
- };
41
- // Calcular nível para todas as entidades
42
- hierarchy.forEach((_, entityId) => {
43
- calculateLevel(entityId);
44
- });
45
- return levels;
46
- }
47
- /**
48
- * Organiza entidades por nível (geração) e calcula posições hierárquicas
49
- */
50
- function calculateHierarchicalPositions(_data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
51
- const positions = new Map();
52
- const levels = calculateLevels(hierarchy);
53
- // Agrupar entidades por nível
54
- const entitiesByLevel = new Map();
55
- levels.forEach((level, entityId) => {
56
- if (!entitiesByLevel.has(level)) {
57
- entitiesByLevel.set(level, []);
58
- }
59
- entitiesByLevel.get(level)?.push(entityId);
60
- });
61
- // Calcular posições para cada nível, começando do nível 0 (raiz)
62
- const sortedLevels = Array.from(entitiesByLevel.keys()).sort((a, b) => a - b);
63
- sortedLevels.forEach((level) => {
64
- const entityIds = entitiesByLevel.get(level);
65
- if (!entityIds)
66
- return;
67
- // Ordenar entidades do mesmo nível (por ID para consistência)
68
- entityIds.sort();
69
- // Calcular posição Y baseada no nível (pais acima, filhos abaixo)
70
- const y = level * (nodeHeight + verticalSpacing);
71
- // Para cada entidade, tentar centralizar abaixo dos pais
72
- entityIds.forEach((entityId) => {
73
- const entityHierarchy = hierarchy.get(entityId);
74
- let x = 0;
75
- if (entityHierarchy && entityHierarchy.parents.length > 0) {
76
- // Calcular posição média dos pais
77
- const parentPositions = entityHierarchy.parents
78
- .map((parent) => positions.get(parent.id))
79
- .filter((pos) => pos !== undefined);
80
- if (parentPositions.length > 0) {
81
- const avgX = parentPositions.reduce((sum, pos) => sum + pos.x, 0) / parentPositions.length;
82
- x = avgX;
83
- }
84
- }
85
- // Se não tem pais ou não conseguiu calcular, distribuir horizontalmente
86
- if (x === 0 && entityIds.length > 1) {
87
- const index = entityIds.indexOf(entityId);
88
- const totalWidth = entityIds.length * nodeWidth + (entityIds.length - 1) * horizontalSpacing;
89
- const startX = -totalWidth / 2 + nodeWidth / 2;
90
- x = startX + index * (nodeWidth + horizontalSpacing);
91
- }
92
- positions.set(entityId, { x, y });
93
- });
94
- // Ajustar posições para evitar sobreposição
95
- entityIds.forEach((entityId, index) => {
96
- const currentPos = positions.get(entityId);
97
- if (!currentPos)
98
- return;
99
- // Verificar se há sobreposição com outros nós do mesmo nível
100
- entityIds.slice(0, index).forEach((otherId) => {
101
- const otherPos = positions.get(otherId);
102
- if (!otherPos)
103
- return;
104
- if (Math.abs(currentPos.x - otherPos.x) < nodeWidth + horizontalSpacing) {
105
- // Ajustar para evitar sobreposição
106
- currentPos.x = otherPos.x + nodeWidth + horizontalSpacing;
107
- }
108
- });
109
- });
110
- });
111
- return positions;
112
- }
113
- /**
114
- * Gera nodes do ReactFlow a partir dos dados de genealogia
115
- */
116
- function generateNodes(data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing) {
117
- const nodes = [];
118
- // Calcular posições hierárquicas
119
- const positions = calculateHierarchicalPositions(data, hierarchy, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing);
120
- // Criar nodes com posições calculadas
121
- data.people.forEach((entity) => {
122
- const position = positions.get(entity.id) || { x: 0, y: 0 };
123
- const entityHierarchy = hierarchy.get(entity.id);
124
- nodes.push({
125
- id: entity.id,
126
- type: "genealogy",
127
- position,
128
- data: {
129
- person: entity,
130
- hierarchy: entityHierarchy,
131
- isExpanded: expansionState[entity.id] ?? false,
132
- },
133
- });
134
- });
135
- return nodes;
136
- }
137
- /**
138
- * Gera edges do ReactFlow a partir dos relacionamentos
139
- * Para hierarquia vertical: source (pai) -> target (filho)
140
- */
141
- function generateEdges(data, hierarchy, expansionState) {
142
- const edges = [];
143
- const edgeIds = new Set();
144
- data.relationships.forEach((relationship) => {
145
- // Determinar source e target baseado no tipo de relacionamento
146
- let source;
147
- let target;
148
- switch (relationship.type) {
149
- case "father":
150
- case "mother": {
151
- // from é pai/mãe de to
152
- // source = pai (from), target = filho (to)
153
- source = relationship.from;
154
- target = relationship.to;
155
- break;
156
- }
157
- case "child": {
158
- // from é filho de to
159
- // source = pai (to), target = filho (from)
160
- source = relationship.to;
161
- target = relationship.from;
162
- break;
163
- }
164
- default: {
165
- source = relationship.from;
166
- target = relationship.to;
167
- }
168
- }
169
- // Criar edge ID único
170
- const edgeId = `e-${source}-${target}-${relationship.type}`;
171
- // Evitar duplicatas
172
- if (edgeIds.has(edgeId)) {
173
- return;
174
- }
175
- edgeIds.add(edgeId);
176
- // Verificar se ambos os nós existem
177
- const sourceHierarchy = hierarchy.get(source);
178
- const targetHierarchy = hierarchy.get(target);
179
- if (!sourceHierarchy || !targetHierarchy) {
180
- return;
181
- }
182
- // Só criar edge se ambos os nós estiverem visíveis (expandidos ou raiz)
183
- const sourceExpanded = expansionState[source] ?? true;
184
- const targetExpanded = expansionState[target] ?? true;
185
- // Para relacionamentos pai/filho, só mostrar se o nó pai estiver expandido
186
- if (relationship.type === "father" ||
187
- relationship.type === "mother" ||
188
- relationship.type === "child") {
189
- if (!sourceExpanded || !targetExpanded)
190
- return;
191
- }
192
- edges.push({
193
- id: edgeId,
194
- source,
195
- target,
196
- type: "animated",
197
- data: {
198
- relationship,
199
- },
200
- });
201
- });
202
- return edges;
203
- }
204
- /**
205
- * Constrói hierarquia de entidades a partir dos dados
206
- */
207
- function buildHierarchy(data) {
208
- const hierarchy = new Map();
209
- // Inicializar todas as entidades
210
- data.people.forEach((entity) => {
211
- hierarchy.set(entity.id, {
212
- person: entity,
213
- parents: [],
214
- children: [],
215
- adoptions: [],
216
- siblings: [],
217
- });
218
- });
219
- // Processar relacionamentos
220
- data.relationships.forEach((relationship) => {
221
- const fromHierarchy = hierarchy.get(relationship.from);
222
- const toHierarchy = hierarchy.get(relationship.to);
223
- if (!fromHierarchy || !toHierarchy)
224
- return;
225
- switch (relationship.type) {
226
- case "father":
227
- case "mother": {
228
- // from é pai/mãe de to
229
- if (!toHierarchy.parents.find((p) => p.id === relationship.from)) {
230
- toHierarchy.parents.push(fromHierarchy.person);
231
- }
232
- if (relationship.type === "father") {
233
- toHierarchy.father = fromHierarchy.person;
234
- }
235
- else if (relationship.type === "mother") {
236
- toHierarchy.mother = fromHierarchy.person;
237
- }
238
- if (!fromHierarchy.children.find((c) => c.id === relationship.to)) {
239
- fromHierarchy.children.push(toHierarchy.person);
240
- }
241
- break;
242
- }
243
- case "child": {
244
- // from é filho de to
245
- if (!toHierarchy.children.find((c) => c.id === relationship.from)) {
246
- toHierarchy.children.push(fromHierarchy.person);
247
- }
248
- if (!fromHierarchy.parents.find((p) => p.id === relationship.to)) {
249
- fromHierarchy.parents.push(toHierarchy.person);
250
- }
251
- break;
252
- }
253
- }
254
- });
255
- // Calcular irmãos (pessoas com os mesmos pais)
256
- hierarchy.forEach((personHierarchy) => {
257
- if (personHierarchy.parents.length > 0) {
258
- const parentIds = personHierarchy.parents.map((p) => p.id);
259
- hierarchy.forEach((otherHierarchy, otherId) => {
260
- if (otherId !== personHierarchy.person.id) {
261
- const hasCommonParent = otherHierarchy.parents.some((p) => parentIds.includes(p.id));
262
- if (hasCommonParent && !personHierarchy.siblings.find((s) => s.id === otherId)) {
263
- personHierarchy.siblings.push(otherHierarchy.person);
264
- }
265
- }
266
- });
267
- }
268
- });
269
- return hierarchy;
270
- }
271
- /**
272
- * Hook headless para gerenciar árvores genealógicas
273
- */
274
- export function useGenealogy({ data, initialExpanded = [], nodeWidth = 200, nodeHeight = 100, horizontalSpacing = 250, verticalSpacing = 150, onNodeSelect, onNodeExpand, onAddRelation, }) {
275
- // Estado de expansão dos nós
276
- const [expansionState, setExpansionState] = useState(() => {
277
- const state = {};
278
- initialExpanded.forEach((id) => {
279
- state[id] = true;
280
- });
281
- return state;
282
- });
283
- // Estado de seleção
284
- const [selectedNodeId, setSelectedNodeId] = useState(null);
285
- // Construir hierarquia
286
- const hierarchy = useMemo(() => buildHierarchy(data), [data]);
287
- // Gerar nodes e edges
288
- const nodes = useMemo(() => generateNodes(data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing), [data, hierarchy, expansionState, nodeWidth, nodeHeight, horizontalSpacing, verticalSpacing]);
289
- const edges = useMemo(() => generateEdges(data, hierarchy, expansionState), [data, hierarchy, expansionState]);
290
- // Função para selecionar um nó
291
- const selectNode = useCallback((nodeId) => {
292
- setSelectedNodeId(nodeId);
293
- const entity = data.people.find((p) => p.id === nodeId);
294
- if (entity && onNodeSelect) {
295
- onNodeSelect(nodeId, entity);
296
- }
297
- }, [data.people, onNodeSelect]);
298
- // Função para expandir/colapsar um nó
299
- const expandNode = useCallback((nodeId, expanded) => {
300
- setExpansionState((prev) => {
301
- const newState = { ...prev };
302
- const isCurrentlyExpanded = prev[nodeId] ?? false;
303
- newState[nodeId] = expanded ?? !isCurrentlyExpanded;
304
- if (onNodeExpand) {
305
- onNodeExpand(nodeId, newState[nodeId]);
306
- }
307
- return newState;
308
- });
309
- }, [onNodeExpand]);
310
- // Função para colapsar um nó
311
- const collapseNode = useCallback((nodeId) => {
312
- expandNode(nodeId, false);
313
- }, [expandNode]);
314
- // Função para adicionar relacionamento
315
- const addRelation = useCallback((fromId, toId, type) => {
316
- if (onAddRelation) {
317
- onAddRelation(fromId, toId, type);
318
- }
319
- }, [onAddRelation]);
320
- // Função para obter hierarquia de uma pessoa
321
- const getPersonHierarchy = useCallback((personId) => {
322
- return hierarchy.get(personId);
323
- }, [hierarchy]);
324
- // Função para obter relacionamentos de uma pessoa
325
- const getPersonRelations = useCallback((personId) => {
326
- return data.relationships.filter((r) => r.from === personId || r.to === personId);
327
- }, [data.relationships]);
328
- return {
329
- nodes,
330
- edges,
331
- hierarchy,
332
- selectedNodeId,
333
- expansionState,
334
- selectNode,
335
- expandNode,
336
- collapseNode,
337
- addRelation,
338
- getPersonHierarchy,
339
- getPersonRelations,
340
- };
341
- }
@@ -1 +0,0 @@
1
- export { useIsMobile } from "./useMobile";
@@ -1,15 +0,0 @@
1
- import * as React from "react";
2
- const MOBILE_BREAKPOINT = 768;
3
- export function useIsMobile() {
4
- const [isMobile, setIsMobile] = React.useState(undefined);
5
- React.useEffect(() => {
6
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
7
- const onChange = () => {
8
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
9
- };
10
- mql.addEventListener("change", onChange);
11
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12
- return () => mql.removeEventListener("change", onChange);
13
- }, []);
14
- return !!isMobile;
15
- }
@@ -1 +0,0 @@
1
- export { useProjectProgress } from "./useProjectProgress";
@@ -1,124 +0,0 @@
1
- /**
2
- * useProjectProgress - Headless UI Hook
3
- *
4
- * Fornece apenas: lógica, estado, processamento e API
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
- * @example
10
- * ```tsx
11
- * function MyProjectProgress() {
12
- * const { percentage, status, distribution } = useProjectProgress({
13
- * projects: [
14
- * { id: '1', status: 'completed', ... },
15
- * { id: '2', status: 'in-progress', ... },
16
- * { id: '3', status: 'pending', ... },
17
- * ],
18
- * })
19
- *
20
- * return (
21
- * <div>
22
- * <div>Progress: {percentage}%</div>
23
- * <div>Status: {status}</div>
24
- * </div>
25
- * )
26
- * }
27
- * ```
28
- */
29
- import { useMemo } from "react";
30
- /**
31
- * Calcula progresso padrão baseado no status
32
- */
33
- function getDefaultProgress(project) {
34
- if (project.progress !== undefined) {
35
- return project.progress;
36
- }
37
- switch (project.status) {
38
- case "completed":
39
- return 100;
40
- case "in-progress":
41
- return 50; // Valor padrão para projetos em progresso
42
- case "pending":
43
- return 0;
44
- case "on-hold":
45
- return 0;
46
- default:
47
- return 0;
48
- }
49
- }
50
- /**
51
- * Hook headless para calcular progresso de projetos
52
- */
53
- export function useProjectProgress(options) {
54
- const { projects, calculateProgress = getDefaultProgress } = options;
55
- const result = useMemo(() => {
56
- if (projects.length === 0) {
57
- return {
58
- percentage: 0,
59
- status: "pending",
60
- distribution: {
61
- completed: 0,
62
- inProgress: 0,
63
- pending: 0,
64
- onHold: 0,
65
- },
66
- total: 0,
67
- completed: 0,
68
- inProgress: 0,
69
- pending: 0,
70
- onHold: 0,
71
- };
72
- }
73
- // Calcular distribuição
74
- const distribution = {
75
- completed: 0,
76
- inProgress: 0,
77
- pending: 0,
78
- onHold: 0,
79
- };
80
- let totalProgress = 0;
81
- projects.forEach((project) => {
82
- const progress = calculateProgress(project);
83
- totalProgress += progress;
84
- switch (project.status) {
85
- case "completed":
86
- distribution.completed++;
87
- break;
88
- case "in-progress":
89
- distribution.inProgress++;
90
- break;
91
- case "pending":
92
- distribution.pending++;
93
- break;
94
- case "on-hold":
95
- distribution.onHold++;
96
- break;
97
- }
98
- });
99
- // Calcular porcentagem média
100
- const percentage = Math.round(totalProgress / projects.length);
101
- // Determinar status geral
102
- let status = "pending";
103
- if (distribution.completed === projects.length) {
104
- status = "completed";
105
- }
106
- else if (distribution.inProgress > 0 || distribution.completed > 0) {
107
- status = "in-progress";
108
- }
109
- else if (distribution.onHold > 0) {
110
- status = "on-hold";
111
- }
112
- return {
113
- percentage,
114
- status,
115
- distribution,
116
- total: projects.length,
117
- completed: distribution.completed,
118
- inProgress: distribution.inProgress,
119
- pending: distribution.pending,
120
- onHold: distribution.onHold,
121
- };
122
- }, [projects, calculateProgress]);
123
- return result;
124
- }
@@ -1 +0,0 @@
1
- export { useProjectStats } from "./useProjectStats";