@flowtomic/logic 0.1.0 → 0.1.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.
Files changed (46) hide show
  1. package/dist/hooks/useAnimatedIndicator/index.d.ts +3 -0
  2. package/dist/hooks/useAnimatedIndicator/index.d.ts.map +1 -0
  3. package/dist/hooks/useAnimatedIndicator/index.js +1 -0
  4. package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.d.ts +94 -0
  5. package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.d.ts.map +1 -0
  6. package/dist/hooks/useAnimatedIndicator/useAnimatedIndicator.js +228 -0
  7. package/dist/hooks/useGenealogy/index.d.ts +3 -0
  8. package/dist/hooks/useGenealogy/index.d.ts.map +1 -0
  9. package/dist/hooks/useGenealogy/index.js +1 -0
  10. package/dist/hooks/useGenealogy/useGenealogy.d.ts +96 -0
  11. package/dist/hooks/useGenealogy/useGenealogy.d.ts.map +1 -0
  12. package/dist/hooks/useGenealogy/useGenealogy.js +335 -0
  13. package/dist/hooks/useMoblile/index.js +1 -868
  14. package/dist/hooks/useProjectProgress/index.d.ts +3 -0
  15. package/dist/hooks/useProjectProgress/index.d.ts.map +1 -0
  16. package/dist/hooks/useProjectProgress/index.js +1 -0
  17. package/dist/hooks/useProjectProgress/useProjectProgress.d.ts +103 -0
  18. package/dist/hooks/useProjectProgress/useProjectProgress.d.ts.map +1 -0
  19. package/dist/hooks/useProjectProgress/useProjectProgress.js +124 -0
  20. package/dist/hooks/useProjectStats/index.d.ts +3 -0
  21. package/dist/hooks/useProjectStats/index.d.ts.map +1 -0
  22. package/dist/hooks/useProjectStats/index.js +1 -0
  23. package/dist/hooks/useProjectStats/useProjectStats.d.ts +130 -0
  24. package/dist/hooks/useProjectStats/useProjectStats.d.ts.map +1 -0
  25. package/dist/hooks/useProjectStats/useProjectStats.js +87 -0
  26. package/dist/hooks/useResizable/index.d.ts +1 -1
  27. package/dist/hooks/useResizable/index.d.ts.map +1 -1
  28. package/dist/hooks/useScriptEditor/index.d.ts +3 -0
  29. package/dist/hooks/useScriptEditor/index.d.ts.map +1 -0
  30. package/dist/hooks/useScriptEditor/index.js +1 -0
  31. package/dist/hooks/useScriptEditor/useScriptEditor.d.ts +147 -0
  32. package/dist/hooks/useScriptEditor/useScriptEditor.d.ts.map +1 -0
  33. package/dist/hooks/useScriptEditor/useScriptEditor.js +247 -0
  34. package/dist/hooks/useStatCard/useStatCard.d.ts +44 -5
  35. package/dist/hooks/useStatCard/useStatCard.d.ts.map +1 -1
  36. package/dist/hooks/useStatCard/useStatCard.js +81 -20
  37. package/dist/hooks/useTimeTracker/index.d.ts +3 -0
  38. package/dist/hooks/useTimeTracker/index.d.ts.map +1 -0
  39. package/dist/hooks/useTimeTracker/index.js +1 -0
  40. package/dist/hooks/useTimeTracker/useTimeTracker.d.ts +106 -0
  41. package/dist/hooks/useTimeTracker/useTimeTracker.d.ts.map +1 -0
  42. package/dist/hooks/useTimeTracker/useTimeTracker.js +143 -0
  43. package/dist/index.d.ts +8 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -5
  46. package/package.json +2 -2
@@ -0,0 +1,247 @@
1
+ /**
2
+ * useScriptEditor - 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 MyScriptEditor() {
12
+ * const {
13
+ * script,
14
+ * setScript,
15
+ * terminalLines,
16
+ * preview,
17
+ * isRunning,
18
+ * isConnected,
19
+ * executeScript,
20
+ * stopExecution,
21
+ * clearTerminal,
22
+ * } = useScriptEditor({
23
+ * wsUrl: "ws://localhost:8080/ws/terminal",
24
+ * executeScript: async (script) => {
25
+ * const response = await fetch("/api/scripts/execute", {
26
+ * method: "POST",
27
+ * body: JSON.stringify({ script }),
28
+ * });
29
+ * return response.json();
30
+ * },
31
+ * });
32
+ *
33
+ * return (
34
+ * <div>
35
+ * <textarea value={script} onChange={(e) => setScript(e.target.value)} />
36
+ * <button onClick={executeScript}>Executar</button>
37
+ * <div>
38
+ * {terminalLines.map((line) => (
39
+ * <div key={line.id}>{line.content}</div>
40
+ * ))}
41
+ * </div>
42
+ * </div>
43
+ * );
44
+ * }
45
+ * ```
46
+ */
47
+ import { useCallback, useEffect, useRef, useState } from "react";
48
+ /**
49
+ * Hook headless para gerenciar editor de scripts com terminal interativo
50
+ */
51
+ export function useScriptEditor(options = {}) {
52
+ const { wsUrl, executeScript: executeScriptFn, onOutput, onError, autoConnect = true, maxReconnectAttempts = 3, defaultScript = "", } = options;
53
+ const [script, setScript] = useState(defaultScript);
54
+ const [terminalLines, setTerminalLines] = useState([]);
55
+ const [preview, setPreview] = useState("");
56
+ const [activeTab, setActiveTab] = useState("terminal");
57
+ const [isRunning, setIsRunning] = useState(false);
58
+ const [isConnected, setIsConnected] = useState(false);
59
+ const wsRef = useRef(null);
60
+ const reconnectAttemptsRef = useRef(0);
61
+ const shouldReconnectRef = useRef(true);
62
+ // Adicionar linha ao terminal
63
+ const addTerminalLine = useCallback((type, content) => {
64
+ const line = {
65
+ id: `${Date.now()}-${Math.random()}`,
66
+ type,
67
+ content,
68
+ timestamp: new Date(),
69
+ };
70
+ setTerminalLines((prev) => [...prev, line]);
71
+ onOutput?.(line);
72
+ }, [onOutput]);
73
+ // Conectar WebSocket
74
+ const connectWebSocket = useCallback(() => {
75
+ if (!wsUrl)
76
+ return;
77
+ // Verificar se já existe uma conexão ativa
78
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
79
+ return;
80
+ }
81
+ // Fechar conexão anterior se existir
82
+ if (wsRef.current) {
83
+ try {
84
+ wsRef.current.close();
85
+ }
86
+ catch {
87
+ // Ignorar erros ao fechar
88
+ }
89
+ wsRef.current = null;
90
+ }
91
+ if (!shouldReconnectRef.current)
92
+ return;
93
+ try {
94
+ const ws = new WebSocket(wsUrl);
95
+ wsRef.current = ws;
96
+ ws.onopen = () => {
97
+ setIsConnected(true);
98
+ reconnectAttemptsRef.current = 0;
99
+ };
100
+ ws.onmessage = (event) => {
101
+ try {
102
+ const data = JSON.parse(event.data);
103
+ if (data.type === "output") {
104
+ addTerminalLine("output", data.content);
105
+ }
106
+ else if (data.type === "error") {
107
+ addTerminalLine("error", data.content);
108
+ }
109
+ else if (data.type === "result") {
110
+ setPreview(JSON.stringify(data.content, null, 2));
111
+ setActiveTab("preview");
112
+ }
113
+ else if (data.type === "system") {
114
+ // Mensagens de sistema não são exibidas no terminal
115
+ console.log("[useScriptEditor] Mensagem de sistema:", data.content);
116
+ }
117
+ }
118
+ catch {
119
+ addTerminalLine("output", event.data);
120
+ }
121
+ };
122
+ ws.onerror = (error) => {
123
+ console.error("[useScriptEditor] WebSocket error:", error);
124
+ setIsConnected(false);
125
+ onError?.(new Error("Erro na conexão WebSocket"));
126
+ };
127
+ ws.onclose = (event) => {
128
+ setIsConnected(false);
129
+ wsRef.current = null;
130
+ // Tentar reconectar apenas se não excedeu tentativas e ainda deve reconectar
131
+ if (event.code !== 1000 &&
132
+ shouldReconnectRef.current &&
133
+ reconnectAttemptsRef.current < maxReconnectAttempts) {
134
+ reconnectAttemptsRef.current += 1;
135
+ setTimeout(() => {
136
+ if (shouldReconnectRef.current) {
137
+ connectWebSocket();
138
+ }
139
+ }, 3000);
140
+ }
141
+ else if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
142
+ console.warn("[useScriptEditor] Máximo de tentativas de reconexão atingido");
143
+ }
144
+ };
145
+ }
146
+ catch (error) {
147
+ console.error("[useScriptEditor] Erro ao conectar WebSocket:", error);
148
+ setIsConnected(false);
149
+ onError?.(error instanceof Error ? error : new Error("Erro desconhecido ao conectar"));
150
+ }
151
+ }, [wsUrl, maxReconnectAttempts, addTerminalLine, onError]);
152
+ // Conectar WebSocket quando montar (se autoConnect)
153
+ useEffect(() => {
154
+ if (autoConnect && wsUrl) {
155
+ const connectTimeout = setTimeout(() => {
156
+ connectWebSocket();
157
+ }, 100);
158
+ return () => {
159
+ clearTimeout(connectTimeout);
160
+ };
161
+ }
162
+ }, [autoConnect, wsUrl, connectWebSocket]);
163
+ // Limpar conexão ao desmontar
164
+ useEffect(() => {
165
+ return () => {
166
+ shouldReconnectRef.current = false;
167
+ if (wsRef.current) {
168
+ try {
169
+ wsRef.current.close(1000, "Component unmounting");
170
+ }
171
+ catch {
172
+ // Ignorar erros ao fechar
173
+ }
174
+ wsRef.current = null;
175
+ }
176
+ };
177
+ }, []);
178
+ // Executar script
179
+ const executeScript = useCallback(async () => {
180
+ if (!script.trim()) {
181
+ addTerminalLine("error", "❌ Script vazio");
182
+ return;
183
+ }
184
+ setIsRunning(true);
185
+ addTerminalLine("input", script);
186
+ try {
187
+ // Enviar script via WebSocket se conectado
188
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
189
+ wsRef.current.send(JSON.stringify({ type: "execute", script }));
190
+ }
191
+ else if (executeScriptFn) {
192
+ // Fallback para HTTP se WebSocket não estiver disponível
193
+ try {
194
+ const result = await executeScriptFn(script);
195
+ if (result.output) {
196
+ addTerminalLine("output", result.output);
197
+ }
198
+ if (result.error) {
199
+ addTerminalLine("error", result.error);
200
+ }
201
+ if (result.result) {
202
+ setPreview(JSON.stringify(result.result, null, 2));
203
+ setActiveTab("preview");
204
+ }
205
+ }
206
+ catch (error) {
207
+ addTerminalLine("error", `❌ Erro na requisição: ${error instanceof Error ? error.message : "Erro desconhecido"}`);
208
+ }
209
+ }
210
+ else {
211
+ addTerminalLine("error", "❌ Nenhuma forma de execução disponível (WebSocket ou HTTP)");
212
+ }
213
+ }
214
+ catch (error) {
215
+ addTerminalLine("error", `❌ Erro ao executar: ${error instanceof Error ? error.message : "Erro desconhecido"}`);
216
+ }
217
+ finally {
218
+ setIsRunning(false);
219
+ }
220
+ }, [script, executeScriptFn, addTerminalLine]);
221
+ // Parar execução
222
+ const stopExecution = useCallback(() => {
223
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
224
+ wsRef.current.send(JSON.stringify({ type: "stop" }));
225
+ }
226
+ setIsRunning(false);
227
+ }, []);
228
+ // Limpar terminal
229
+ const clearTerminal = useCallback(() => {
230
+ setTerminalLines([]);
231
+ setPreview("");
232
+ }, []);
233
+ return {
234
+ script,
235
+ setScript,
236
+ terminalLines,
237
+ preview,
238
+ activeTab,
239
+ setActiveTab,
240
+ isRunning,
241
+ isConnected,
242
+ executeScript,
243
+ stopExecution,
244
+ clearTerminal,
245
+ addTerminalLine,
246
+ };
247
+ }
@@ -8,11 +8,11 @@
8
8
  *
9
9
  * @example
10
10
  * ```tsx
11
+ * // Cálculo automático do delta a partir de lastMonth
11
12
  * function MyCustomStatCard() {
12
13
  * const { formattedValue, trend, getCardProps } = useStatCard({
13
14
  * value: 122380,
14
- * delta: 15.1,
15
- * lastMonth: 105922,
15
+ * lastMonth: 105922, // delta será calculado automaticamente: +15.5%
16
16
  * })
17
17
  *
18
18
  * return (
@@ -22,35 +22,65 @@
22
22
  * </div>
23
23
  * )
24
24
  * }
25
+ *
26
+ * // Ou forneça delta explicitamente para casos especiais
27
+ * function CustomDeltaCard() {
28
+ * const { formattedValue, trend } = useStatCard({
29
+ * value: 122380,
30
+ * delta: 15.1, // delta explícito tem prioridade
31
+ * lastMonth: 105922,
32
+ * })
33
+ * }
25
34
  * ```
26
35
  */
27
36
  export interface StatCardData {
28
37
  /**
29
38
  * Valor principal do card
30
39
  */
31
- value: number;
40
+ value: number | string;
32
41
  /**
33
42
  * Percentual de variação (positivo ou negativo)
43
+ * Se não fornecido, será calculado automaticamente quando `lastMonth` estiver disponível.
44
+ * Fórmula: ((value - lastMonth) / lastMonth) * 100
34
45
  */
35
46
  delta?: number;
36
47
  /**
37
48
  * Valor do mês anterior para comparação
49
+ * Se fornecido e `delta` não estiver definido, o delta será calculado automaticamente.
38
50
  */
39
51
  lastMonth?: number;
40
52
  /**
41
53
  * Prefixo para formatação do valor (ex: '$', 'R$')
54
+ * Se `locale` e `currency` forem fornecidos, será ignorado em favor da formatação de moeda
42
55
  */
43
56
  prefix?: string;
44
57
  /**
45
58
  * Sufixo para formatação do valor (ex: 'M', 'K')
46
59
  */
47
60
  suffix?: string;
61
+ /**
62
+ * Locale para formatação numérica (ex: 'pt-BR', 'en-US', 'de-DE')
63
+ * Padrão: 'pt-BR' (formata com ponto como separador de milhares)
64
+ */
65
+ locale?: string;
66
+ /**
67
+ * Código da moeda para formatação (ex: 'BRL', 'USD', 'EUR')
68
+ * Se fornecido, usa formatação de moeda do locale
69
+ */
70
+ currency?: string;
71
+ /**
72
+ * Como exibir a moeda ('symbol' | 'narrowSymbol' | 'code' | 'name')
73
+ * Padrão: 'symbol' (ex: R$ para BRL, $ para USD)
74
+ */
75
+ currencyDisplay?: "symbol" | "narrowSymbol" | "code" | "name";
48
76
  /**
49
77
  * Função customizada de formatação do valor principal
78
+ * Tem prioridade sobre locale e currency
50
79
  */
51
80
  format?: (value: number) => string;
52
81
  /**
53
82
  * Função customizada de formatação do valor do mês anterior
83
+ * Tem prioridade sobre locale e currency
54
84
  */
55
85
  lastFormat?: (value: number) => string;
56
86
  /**
@@ -124,11 +154,11 @@ export interface UseStatCardReturn {
124
154
  *
125
155
  * @example
126
156
  * ```tsx
157
+ * // Cálculo automático do delta a partir de lastMonth
127
158
  * function MyCustomStatCard() {
128
159
  * const { formattedValue, trend, getCardProps } = useStatCard({
129
160
  * value: 122380,
130
- * delta: 15.1,
131
- * lastMonth: 105922,
161
+ * lastMonth: 105922, // delta será calculado automaticamente: +15.5%
132
162
  * })
133
163
  *
134
164
  * return (
@@ -138,6 +168,15 @@ export interface UseStatCardReturn {
138
168
  * </div>
139
169
  * )
140
170
  * }
171
+ *
172
+ * // Ou forneça delta explicitamente para casos especiais
173
+ * function CustomDeltaCard() {
174
+ * const { formattedValue, trend } = useStatCard({
175
+ * value: 122380,
176
+ * delta: 15.1, // delta explícito tem prioridade
177
+ * lastMonth: 105922,
178
+ * })
179
+ * }
141
180
  * ```
142
181
  */
143
182
  export declare function useStatCard(data: StatCardData): UseStatCardReturn;
@@ -1 +1 @@
1
- {"version":3,"file":"useStatCard.d.ts","sourceRoot":"","sources":["../../../src/hooks/useStatCard/useStatCard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAEnC;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAEvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAEjD;;OAEG;IACH,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,YAAY,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QAC7F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IAEF;;OAEG;IACH,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CAkFjE"}
1
+ {"version":3,"file":"useStatCard.d.ts","sourceRoot":"","sources":["../../../src/hooks/useStatCard/useStatCard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAEvB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,eAAe,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;IAE9D;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAEnC;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAEvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAEjD;;OAEG;IACH,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,YAAY,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QAC7F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IAEF;;OAEG;IACH,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAwCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CAwHjE"}
@@ -8,11 +8,11 @@
8
8
  *
9
9
  * @example
10
10
  * ```tsx
11
+ * // Cálculo automático do delta a partir de lastMonth
11
12
  * function MyCustomStatCard() {
12
13
  * const { formattedValue, trend, getCardProps } = useStatCard({
13
14
  * value: 122380,
14
- * delta: 15.1,
15
- * lastMonth: 105922,
15
+ * lastMonth: 105922, // delta será calculado automaticamente: +15.5%
16
16
  * })
17
17
  *
18
18
  * return (
@@ -22,23 +22,48 @@
22
22
  * </div>
23
23
  * )
24
24
  * }
25
+ *
26
+ * // Ou forneça delta explicitamente para casos especiais
27
+ * function CustomDeltaCard() {
28
+ * const { formattedValue, trend } = useStatCard({
29
+ * value: 122380,
30
+ * delta: 15.1, // delta explícito tem prioridade
31
+ * lastMonth: 105922,
32
+ * })
33
+ * }
25
34
  * ```
26
35
  */
27
36
  import { useCallback, useMemo } from "react";
28
37
  /**
29
- * Formata um número seguindo padrões comuns
30
- * - Valores >= 1.000.000: formata como "X.XM"
31
- * - Valores >= 1.000: formata com separador de milhares
32
- * - Valores < 1.000: retorna como string
38
+ * Formata um número seguindo padrões comuns com suporte a locale
39
+ * - Valores >= 1.000.000: formata como "X.XM" (respeitando locale para decimais)
40
+ * - Valores >= 1.000: formata com separador de milhares conforme locale
41
+ * - Valores < 1.000: retorna formatado conforme locale
33
42
  */
34
- function formatNumberDefault(n) {
43
+ function formatNumberDefault(n, locale = "pt-BR") {
35
44
  if (n >= 1_000_000) {
36
- return `${(n / 1_000_000).toFixed(1)}M`;
45
+ const formatter = new Intl.NumberFormat(locale, {
46
+ minimumFractionDigits: 1,
47
+ maximumFractionDigits: 1,
48
+ });
49
+ return `${formatter.format(n / 1_000_000)}M`;
37
50
  }
38
51
  if (n >= 1_000) {
39
- return n.toLocaleString();
52
+ return new Intl.NumberFormat(locale).format(n);
40
53
  }
41
- return n.toString();
54
+ return new Intl.NumberFormat(locale).format(n);
55
+ }
56
+ /**
57
+ * Formata um número como moeda usando locale
58
+ */
59
+ function formatCurrency(n, locale = "pt-BR", currency = "BRL", currencyDisplay = "symbol") {
60
+ return new Intl.NumberFormat(locale, {
61
+ style: "currency",
62
+ currency,
63
+ currencyDisplay,
64
+ minimumFractionDigits: 0,
65
+ maximumFractionDigits: 2,
66
+ }).format(n);
42
67
  }
43
68
  /**
44
69
  * Hook Headless UI para StatCard
@@ -48,11 +73,11 @@ function formatNumberDefault(n) {
48
73
  *
49
74
  * @example
50
75
  * ```tsx
76
+ * // Cálculo automático do delta a partir de lastMonth
51
77
  * function MyCustomStatCard() {
52
78
  * const { formattedValue, trend, getCardProps } = useStatCard({
53
79
  * value: 122380,
54
- * delta: 15.1,
55
- * lastMonth: 105922,
80
+ * lastMonth: 105922, // delta será calculado automaticamente: +15.5%
56
81
  * })
57
82
  *
58
83
  * return (
@@ -62,10 +87,32 @@ function formatNumberDefault(n) {
62
87
  * </div>
63
88
  * )
64
89
  * }
90
+ *
91
+ * // Ou forneça delta explicitamente para casos especiais
92
+ * function CustomDeltaCard() {
93
+ * const { formattedValue, trend } = useStatCard({
94
+ * value: 122380,
95
+ * delta: 15.1, // delta explícito tem prioridade
96
+ * lastMonth: 105922,
97
+ * })
98
+ * }
65
99
  * ```
66
100
  */
67
101
  export function useStatCard(data) {
68
- const { value, delta = 0, lastMonth, prefix = "", suffix = "", format, lastFormat, positive: explicitPositive, } = data;
102
+ const { value, delta: explicitDelta, lastMonth, prefix = "", suffix = "", locale = "pt-BR", currency, currencyDisplay = "symbol", format, lastFormat, positive: explicitPositive, } = data;
103
+ // Calcula delta automaticamente se não fornecido mas lastMonth estiver disponível
104
+ const delta = useMemo(() => {
105
+ // Se delta foi fornecido explicitamente, usa ele (prioridade)
106
+ if (explicitDelta !== undefined) {
107
+ return explicitDelta;
108
+ }
109
+ // Se lastMonth foi fornecido, calcula o delta automaticamente
110
+ if (lastMonth !== undefined && lastMonth !== 0) {
111
+ return ((Number(value) - Number(lastMonth)) / Number(lastMonth)) * 100;
112
+ }
113
+ // Caso contrário, retorna 0
114
+ return 0;
115
+ }, [value, lastMonth, explicitDelta]);
69
116
  // Calcula se a tendência é positiva
70
117
  const isPositive = useMemo(() => {
71
118
  if (explicitPositive !== undefined) {
@@ -89,10 +136,16 @@ export function useStatCard(data) {
89
136
  // Formata o valor principal
90
137
  const formattedValue = useMemo(() => {
91
138
  if (format) {
92
- return format(value);
139
+ return format(Number(value));
140
+ }
141
+ const numValue = Number(value);
142
+ if (currency) {
143
+ // Se currency está definido, usa formatação de moeda (ignora prefix)
144
+ return (formatCurrency(numValue, locale, currency, currencyDisplay) + (suffix ? ` ${suffix}` : ""));
93
145
  }
94
- return prefix + formatNumberDefault(value) + suffix;
95
- }, [value, prefix, suffix, format]);
146
+ // Caso contrário, usa formatação numérica padrão com prefix/suffix
147
+ return prefix + formatNumberDefault(numValue, locale) + suffix;
148
+ }, [value, prefix, suffix, locale, currency, currencyDisplay, format]);
96
149
  // Formata o valor do mês anterior
97
150
  const formattedLastMonth = useMemo(() => {
98
151
  if (!lastMonth)
@@ -100,8 +153,13 @@ export function useStatCard(data) {
100
153
  if (lastFormat) {
101
154
  return lastFormat(lastMonth);
102
155
  }
103
- return prefix + formatNumberDefault(lastMonth) + suffix;
104
- }, [lastMonth, prefix, suffix, lastFormat]);
156
+ if (currency) {
157
+ // Se currency está definido, usa formatação de moeda (ignora prefix)
158
+ return formatCurrency(lastMonth, locale, currency, currencyDisplay);
159
+ }
160
+ // Caso contrário, usa formatação numérica padrão com prefix/suffix
161
+ return prefix + formatNumberDefault(lastMonth, locale) + suffix;
162
+ }, [lastMonth, prefix, suffix, locale, currency, currencyDisplay, lastFormat]);
105
163
  // Props helper para o card
106
164
  const getCardProps = useCallback((props = {}) => {
107
165
  return {
@@ -113,8 +171,11 @@ export function useStatCard(data) {
113
171
  }, [formattedValue]);
114
172
  // Função utilitária de formatação
115
173
  const formatNumber = useCallback((n) => {
116
- return formatNumberDefault(n);
117
- }, []);
174
+ if (currency) {
175
+ return formatCurrency(n, locale, currency, currencyDisplay);
176
+ }
177
+ return formatNumberDefault(n, locale);
178
+ }, [locale, currency, currencyDisplay]);
118
179
  return {
119
180
  formattedValue,
120
181
  formattedLastMonth,
@@ -0,0 +1,3 @@
1
+ export type { UseTimeTrackerOptions, UseTimeTrackerReturn } from "./useTimeTracker";
2
+ export { useTimeTracker } from "./useTimeTracker";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/useTimeTracker/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1 @@
1
+ export { useTimeTracker } from "./useTimeTracker";
@@ -0,0 +1,106 @@
1
+ /**
2
+ * useTimeTracker - 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 MyTimer() {
12
+ * const { time, formattedTime, isRunning, start, pause, stop, reset } = useTimeTracker({
13
+ * initialTime: 0,
14
+ * })
15
+ *
16
+ * return (
17
+ * <div>
18
+ * <span>{formattedTime}</span>
19
+ * <button onClick={start}>Start</button>
20
+ * <button onClick={pause}>Pause</button>
21
+ * <button onClick={stop}>Stop</button>
22
+ * <button onClick={reset}>Reset</button>
23
+ * </div>
24
+ * )
25
+ * }
26
+ * ```
27
+ */
28
+ export interface UseTimeTrackerOptions {
29
+ /**
30
+ * Tempo inicial em segundos
31
+ * @default 0
32
+ */
33
+ initialTime?: number;
34
+ /**
35
+ * Callback quando o timer é pausado
36
+ */
37
+ onPause?: (time: number) => void;
38
+ /**
39
+ * Callback quando o timer é parado
40
+ */
41
+ onStop?: (time: number) => void;
42
+ /**
43
+ * Callback quando o timer é iniciado
44
+ */
45
+ onStart?: (time: number) => void;
46
+ /**
47
+ * Callback quando o timer é retomado
48
+ */
49
+ onResume?: (time: number) => void;
50
+ /**
51
+ * Callback quando o timer é resetado
52
+ */
53
+ onReset?: () => void;
54
+ /**
55
+ * Formato de tempo customizado
56
+ * @default "HH:mm:ss"
57
+ */
58
+ format?: "HH:mm:ss" | "mm:ss" | "ss" | ((seconds: number) => string);
59
+ }
60
+ export interface UseTimeTrackerReturn {
61
+ /**
62
+ * Tempo atual em segundos
63
+ */
64
+ time: number;
65
+ /**
66
+ * Tempo formatado como string (HH:mm:ss por padrão)
67
+ */
68
+ formattedTime: string;
69
+ /**
70
+ * Se o timer está rodando
71
+ */
72
+ isRunning: boolean;
73
+ /**
74
+ * Se o timer está pausado
75
+ */
76
+ isPaused: boolean;
77
+ /**
78
+ * Se o timer está parado (não iniciado ou resetado)
79
+ */
80
+ isStopped: boolean;
81
+ /**
82
+ * Inicia o timer
83
+ */
84
+ start: () => void;
85
+ /**
86
+ * Pausa o timer
87
+ */
88
+ pause: () => void;
89
+ /**
90
+ * Para o timer (pausa e mantém o tempo)
91
+ */
92
+ stop: () => void;
93
+ /**
94
+ * Retoma o timer após pausa
95
+ */
96
+ resume: () => void;
97
+ /**
98
+ * Reseta o timer para o tempo inicial
99
+ */
100
+ reset: () => void;
101
+ }
102
+ /**
103
+ * Hook headless para gerenciar timer
104
+ */
105
+ export declare function useTimeTracker(options?: UseTimeTrackerOptions): UseTimeTrackerReturn;
106
+ //# sourceMappingURL=useTimeTracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTimeTracker.d.ts","sourceRoot":"","sources":["../../../src/hooks/useTimeTracker/useTimeTracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;;OAGG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAElB;;OAEG;IACH,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,IAAI,CAAC;IAEnB;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,oBAAoB,CAyIxF"}