@sincpro/mobile 0.4.3 → 1.0.0

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 (197) hide show
  1. package/README.md +3 -3
  2. package/dist/adapters/JsonSerializer.adapter.d.ts +1 -1
  3. package/dist/adapters/JsonSerializer.adapter.js +2 -2
  4. package/dist/adapters/ReceiptExporter.adapter.js +10 -2
  5. package/dist/adapters/repositories/database_table.repository.d.ts +3 -2
  6. package/dist/adapters/repositories/database_table.repository.js +1 -1
  7. package/dist/adapters/repositories/document.repository.d.ts +81 -0
  8. package/dist/adapters/repositories/document.repository.js +653 -0
  9. package/dist/adapters/repositories/domain_event.repository.d.ts +17 -17
  10. package/dist/adapters/repositories/domain_event.repository.js +25 -31
  11. package/dist/adapters/repositories/domain_event_dead_letter.repository.d.ts +10 -10
  12. package/dist/adapters/repositories/domain_event_dead_letter.repository.js +14 -13
  13. package/dist/adapters/repositories/event_row.d.ts +13 -0
  14. package/dist/adapters/repositories/event_row.js +22 -0
  15. package/dist/adapters/repositories/sqlite_document_store.d.ts +33 -0
  16. package/dist/adapters/repositories/sqlite_document_store.js +201 -0
  17. package/dist/domain/connectivity/events.d.ts +5 -5
  18. package/dist/domain/connectivity/events.js +3 -3
  19. package/dist/domain/database/criteria.d.ts +19 -0
  20. package/dist/domain/database/criteria.js +36 -0
  21. package/dist/domain/database/criteria_clock.d.ts +8 -0
  22. package/dist/domain/database/criteria_clock.js +305 -0
  23. package/dist/domain/database/criteria_sql.d.ts +61 -0
  24. package/dist/domain/database/criteria_sql.js +476 -0
  25. package/dist/domain/database/database.d.ts +1 -0
  26. package/dist/domain/database/index.d.ts +3 -1
  27. package/dist/domain/database/index.js +3 -1
  28. package/dist/domain/database/migration_plan.d.ts +14 -0
  29. package/dist/domain/database/migration_plan.js +44 -0
  30. package/dist/domain/entity/change_tracking.d.ts +21 -0
  31. package/dist/domain/entity/change_tracking.js +71 -0
  32. package/dist/domain/entity/checks.d.ts +8 -0
  33. package/dist/domain/entity/checks.js +82 -0
  34. package/dist/domain/entity/entity.d.ts +39 -32
  35. package/dist/domain/entity/entity.js +94 -105
  36. package/dist/domain/entity/entity_collection.d.ts +15 -30
  37. package/dist/domain/entity/entity_collection.js +30 -121
  38. package/dist/domain/entity/index.d.ts +5 -0
  39. package/dist/domain/entity/index.js +5 -0
  40. package/dist/domain/entity/reconcile.d.ts +7 -0
  41. package/dist/domain/entity/reconcile.js +12 -0
  42. package/dist/domain/entity/relations.d.ts +27 -0
  43. package/dist/domain/entity/relations.js +69 -0
  44. package/dist/domain/entity/remote_sync.d.ts +4 -0
  45. package/dist/domain/entity/remote_sync.js +19 -0
  46. package/dist/domain/entity/values.d.ts +6 -0
  47. package/dist/domain/entity/values.js +13 -0
  48. package/dist/domain/events/catalog.d.ts +29 -0
  49. package/dist/domain/{events.js → events/catalog.js} +7 -11
  50. package/dist/domain/events/chain.d.ts +5 -0
  51. package/dist/domain/events/chain.js +6 -0
  52. package/dist/domain/{event_sourcing → events}/domain_event.d.ts +20 -22
  53. package/dist/domain/events/domain_event.js +227 -0
  54. package/dist/domain/events/index.d.ts +8 -0
  55. package/dist/domain/events/index.js +8 -0
  56. package/dist/domain/events/integration_event.d.ts +103 -0
  57. package/dist/domain/events/integration_event.js +72 -0
  58. package/dist/domain/events/label.d.ts +2 -0
  59. package/dist/domain/events/label.js +26 -0
  60. package/dist/domain/index.d.ts +0 -1
  61. package/dist/domain/index.js +0 -1
  62. package/dist/domain/print/events.d.ts +8 -4
  63. package/dist/domain/print/events.js +9 -4
  64. package/dist/domain/repositories/document_store.d.ts +33 -0
  65. package/dist/domain/repositories/document_store.js +5 -0
  66. package/dist/domain/repositories/hooks.d.ts +23 -0
  67. package/dist/domain/repositories/hooks.js +32 -0
  68. package/dist/domain/repositories/index.d.ts +4 -0
  69. package/dist/domain/repositories/index.js +4 -0
  70. package/dist/domain/{database → repositories}/repository.d.ts +9 -3
  71. package/dist/domain/settings.d.ts +4 -1
  72. package/dist/domain/settings.js +3 -0
  73. package/dist/domain/webview/events.d.ts +2 -2
  74. package/dist/domain/webview/events.js +2 -2
  75. package/dist/entrypoints/db/migrations.js +10 -10
  76. package/dist/entrypoints/queue/QueueProcessor.d.ts +1 -1
  77. package/dist/entrypoints/queue/activateDomain.subscriber.d.ts +2 -2
  78. package/dist/entrypoints/queue/activateDomain.subscriber.js +2 -2
  79. package/dist/entrypoints/queue/newAppSettings.handler.d.ts +2 -2
  80. package/dist/entrypoints/queue/newAppSettings.handler.js +1 -1
  81. package/dist/entrypoints/queue/printImage.subscriber.d.ts +1 -1
  82. package/dist/entrypoints/queue/printImage.subscriber.js +2 -2
  83. package/dist/entrypoints/queue/processWebViewMessage.subscriber.d.ts +1 -1
  84. package/dist/entrypoints/queue/processWebViewMessage.subscriber.js +1 -1
  85. package/dist/entrypoints/ui/AppShell.js +2 -2
  86. package/dist/entrypoints/ui/common_provider.d.ts +1 -13
  87. package/dist/entrypoints/ui/common_provider.js +4 -133
  88. package/dist/entrypoints/ui/domain_switcher.d.ts +2 -2
  89. package/dist/event_driven/index.d.ts +1 -0
  90. package/dist/event_driven/index.js +1 -0
  91. package/dist/{domain/event_sourcing/event_handler.d.ts → event_driven/subscriber.d.ts} +1 -1
  92. package/dist/exceptions.d.ts +21 -0
  93. package/dist/exceptions.js +30 -0
  94. package/dist/framework/base_module.d.ts +1 -1
  95. package/dist/framework/createApp.d.ts +3 -3
  96. package/dist/framework/domain_module.d.ts +1 -1
  97. package/dist/framework/feature.d.ts +26 -0
  98. package/dist/framework/feature.js +36 -0
  99. package/dist/framework/kernel.d.ts +1 -1
  100. package/dist/framework/kernel.js +17 -8
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/infrastructure/database/connector.d.ts +2 -0
  104. package/dist/infrastructure/database/connector.js +22 -0
  105. package/dist/infrastructure/database/index.d.ts +1 -0
  106. package/dist/infrastructure/database/index.js +1 -0
  107. package/dist/infrastructure/database/schema.d.ts +9 -0
  108. package/dist/infrastructure/database/schema.js +61 -0
  109. package/dist/infrastructure/database/utils.d.ts +2 -0
  110. package/dist/infrastructure/database/utils.js +12 -4
  111. package/dist/infrastructure/event_chain.d.ts +6 -0
  112. package/dist/infrastructure/event_chain.js +25 -0
  113. package/dist/infrastructure/interceptor/intercept.d.ts +7 -2
  114. package/dist/infrastructure/interceptor/intercept.js +5 -2
  115. package/dist/infrastructure/telemetry/tracing/tracing_hooks.d.ts +9 -6
  116. package/dist/infrastructure/telemetry/tracing/tracing_hooks.js +61 -35
  117. package/dist/infrastructure/ui/UIEventBus.d.ts +3 -1
  118. package/dist/infrastructure/ui/UIEventBus.js +8 -1
  119. package/dist/infrastructure/ui/activity.d.ts +37 -0
  120. package/dist/infrastructure/ui/activity.js +84 -0
  121. package/dist/infrastructure/workers/CronWorker.d.ts +6 -4
  122. package/dist/infrastructure/workers/CronWorker.js +24 -18
  123. package/dist/infrastructure/workers/EventBus.d.ts +9 -5
  124. package/dist/infrastructure/workers/EventBus.js +68 -33
  125. package/dist/services/bluetooth.service.js +16 -14
  126. package/dist/services/dead_letter_queue.service.d.ts +7 -7
  127. package/dist/services/dead_letter_queue.service.js +0 -2
  128. package/dist/services/event.service.d.ts +3 -3
  129. package/dist/services/event.service.js +1 -1
  130. package/dist/services/network.service.d.ts +1 -1
  131. package/dist/services/network.service.js +17 -15
  132. package/dist/services/printer.service.d.ts +1 -1
  133. package/dist/services/printer.service.js +50 -44
  134. package/dist/tools/utils/date.js +4 -3
  135. package/dist/tools/utils/monetary.d.ts +6 -3
  136. package/dist/tools/utils/monetary.js +33 -21
  137. package/dist/tools/utils/serializer.d.ts +1 -1
  138. package/dist/tools/utils/serializer.js +32 -35
  139. package/dist/ui/activity/DeviceNotices.d.ts +1 -0
  140. package/dist/ui/activity/DeviceNotices.js +29 -0
  141. package/dist/ui/activity/ProcessToast.d.ts +2 -0
  142. package/dist/ui/activity/ProcessToast.js +71 -0
  143. package/dist/ui/{components/molecules → activity}/ProcessToastProvider.d.ts +1 -1
  144. package/dist/ui/activity/index.d.ts +4 -0
  145. package/dist/ui/activity/index.js +4 -0
  146. package/dist/ui/activity/useActivity.d.ts +9 -0
  147. package/dist/ui/activity/useActivity.js +48 -0
  148. package/dist/ui/components/atoms/DebugBanner.d.ts +1 -1
  149. package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.d.ts +1 -1
  150. package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.js +8 -2
  151. package/dist/ui/components/molecules/DeadLetterErrorBlock.d.ts +1 -1
  152. package/dist/ui/components/molecules/EventTimelineItem.d.ts +3 -3
  153. package/dist/ui/components/molecules/EventTimelineItem.js +1 -1
  154. package/dist/ui/components/molecules/GeoPermissionCard.d.ts +1 -1
  155. package/dist/ui/components/molecules/TimeZoneSelector.d.ts +1 -1
  156. package/dist/ui/components/molecules/TimeZoneSelectorModal.d.ts +1 -1
  157. package/dist/ui/components/molecules/TimeZoneSelectorModal.js +5 -3
  158. package/dist/ui/components/molecules/index.d.ts +1 -2
  159. package/dist/ui/components/molecules/index.js +1 -2
  160. package/dist/ui/components/organisms/BluetoothPrinterSelector.d.ts +1 -1
  161. package/dist/ui/components/organisms/BluetoothPrinterSelector.js +3 -0
  162. package/dist/ui/components/organisms/DatabaseInfoRow.d.ts +1 -1
  163. package/dist/ui/components/organisms/DeadLetterQueueRow.d.ts +5 -4
  164. package/dist/ui/components/organisms/EventRow.d.ts +4 -4
  165. package/dist/ui/components/organisms/EventRow.js +1 -1
  166. package/dist/ui/components/organisms/InjectableWebView.d.ts +2 -2
  167. package/dist/ui/components/organisms/InjectableWebView.js +6 -3
  168. package/dist/ui/components/organisms/Receipt.d.ts +2 -1
  169. package/dist/ui/components/organisms/TableInfoInfoRow.d.ts +1 -1
  170. package/dist/ui/screens/database/database.context.d.ts +2 -2
  171. package/dist/ui/screens/database/database.context.js +9 -18
  172. package/dist/ui/screens/database/database.json_detail.d.ts +1 -1
  173. package/dist/ui/screens/database/database.list.d.ts +1 -1
  174. package/dist/ui/screens/database/database.table_rows.d.ts +1 -1
  175. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.context.d.ts +7 -7
  176. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.d.ts +1 -1
  177. package/dist/ui/screens/events/events.context.d.ts +6 -6
  178. package/dist/ui/screens/events/events.context.js +20 -25
  179. package/dist/ui/screens/events/events.list.d.ts +1 -1
  180. package/package.json +51 -40
  181. package/dist/domain/event_sourcing/domain_event.js +0 -164
  182. package/dist/domain/event_sourcing/event.d.ts +0 -25
  183. package/dist/domain/event_sourcing/event.js +0 -25
  184. package/dist/domain/event_sourcing/index.d.ts +0 -2
  185. package/dist/domain/event_sourcing/index.js +0 -2
  186. package/dist/domain/events.d.ts +0 -33
  187. package/dist/infrastructure/ui/ToastHost.d.ts +0 -6
  188. package/dist/infrastructure/ui/ToastHost.js +0 -28
  189. package/dist/infrastructure/ui/events.d.ts +0 -1
  190. package/dist/infrastructure/ui/events.js +0 -1
  191. package/dist/ui/components/molecules/ProcessToast.d.ts +0 -8
  192. package/dist/ui/components/molecules/ProcessToast.js +0 -52
  193. /package/dist/domain/{repositories.d.ts → repositories/catalog.d.ts} +0 -0
  194. /package/dist/domain/{repositories.js → repositories/catalog.js} +0 -0
  195. /package/dist/domain/{database → repositories}/repository.js +0 -0
  196. /package/dist/{domain/event_sourcing/event_handler.js → event_driven/subscriber.js} +0 -0
  197. /package/dist/ui/{components/molecules → activity}/ProcessToastProvider.js +0 -0
@@ -3,8 +3,7 @@ import { SettingsRepository } from "../adapters/repositories/setting.repository"
3
3
  import { getPrinterDriver, hasPrinterDriver, setPrinterDriver, } from "../domain/print";
4
4
  import { ECommonSetting } from "../domain/settings";
5
5
  import { loggerUseCases } from "../infrastructure/logger";
6
- import { UI_NOTIFICATION_EVENT } from "../infrastructure/ui/events";
7
- import { UIEventBus } from "../infrastructure/ui/UIEventBus";
6
+ import { notifyDevice } from "../infrastructure/ui/activity";
8
7
  import { File } from "expo-file-system";
9
8
  import * as Print from "expo-print";
10
9
  import { bluetoothService } from "./bluetooth.service";
@@ -26,7 +25,7 @@ class PrinterService {
26
25
  isConnected() {
27
26
  return getPrinterDriver().isConnected();
28
27
  }
29
- async connect(address, name) {
28
+ async connect(address, name, notify = true) {
30
29
  const hasPermissions = await this.bluetooth.ensurePermissionsForPrinter();
31
30
  if (!hasPermissions) {
32
31
  return false;
@@ -39,30 +38,37 @@ class PrinterService {
39
38
  selectedAt: new Date().toISOString(),
40
39
  };
41
40
  await this.saveSelectedPrinter(printer);
42
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
43
- type: "success",
44
- text1: "Impresora conectada",
45
- text2: `Conectado a ${name}`,
46
- });
41
+ if (notify) {
42
+ notifyDevice({
43
+ name: "printer.connected",
44
+ title: "Impresora conectada",
45
+ message: `Conectado a ${name}`,
46
+ variant: "success",
47
+ });
48
+ }
47
49
  return true;
48
50
  }
49
51
  catch (error) {
50
52
  loggerUseCases.error(`Failed to connect to printer: ${name}`, error);
51
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
52
- type: "error",
53
- text1: "Error de conexión",
54
- text2: `No se pudo conectar a ${name}`,
55
- });
53
+ if (notify) {
54
+ notifyDevice({
55
+ name: "printer.connect_failed",
56
+ title: "Error de conexión",
57
+ message: `No se pudo conectar a ${name}`,
58
+ variant: "danger",
59
+ });
60
+ }
56
61
  return false;
57
62
  }
58
63
  }
59
64
  async disconnect() {
60
65
  try {
61
66
  await getPrinterDriver().disconnect();
62
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
63
- type: "info",
64
- text1: "Desconectado",
65
- text2: "Impresora desconectada",
67
+ notifyDevice({
68
+ name: "printer.disconnected",
69
+ title: "Desconectado",
70
+ message: "Impresora desconectada",
71
+ variant: "info",
66
72
  });
67
73
  }
68
74
  catch (error) {
@@ -87,10 +93,11 @@ class PrinterService {
87
93
  }
88
94
  async printTestPage() {
89
95
  if (!this.isConnected()) {
90
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
91
- type: "error",
92
- text1: "Sin conexión",
93
- text2: "Conecta una impresora primero",
96
+ notifyDevice({
97
+ name: "printer.not_connected",
98
+ title: "Sin conexión",
99
+ message: "Conecta una impresora primero",
100
+ variant: "danger",
94
101
  });
95
102
  return false;
96
103
  }
@@ -108,19 +115,21 @@ class PrinterService {
108
115
  alignment: "center",
109
116
  fontSize: "small",
110
117
  });
111
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
112
- type: "success",
113
- text1: "Impresión exitosa",
114
- text2: "Página de prueba impresa",
118
+ notifyDevice({
119
+ name: "printer.test_ok",
120
+ title: "Impresión exitosa",
121
+ message: "Página de prueba impresa",
122
+ variant: "success",
115
123
  });
116
124
  return true;
117
125
  }
118
126
  catch (error) {
119
127
  loggerUseCases.error("Error printing test page", error);
120
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
121
- type: "error",
122
- text1: "Error de impresión",
123
- text2: "No se pudo imprimir la página de prueba",
128
+ notifyDevice({
129
+ name: "printer.test_failed",
130
+ title: "Error de impresión",
131
+ message: "No se pudo imprimir la página de prueba",
132
+ variant: "danger",
124
133
  });
125
134
  return false;
126
135
  }
@@ -137,18 +146,13 @@ class PrinterService {
137
146
  loggerUseCases.warn("Print failed — attempting grace reconnect", firstError);
138
147
  const reconnected = await this.forceReconnect();
139
148
  if (!reconnected)
140
- return false; // connect() ya notificó al usuario
149
+ return false;
141
150
  try {
142
151
  await getPrinterDriver().printImageBase64(base64Data);
143
152
  return true;
144
153
  }
145
154
  catch (retryError) {
146
155
  loggerUseCases.error("Print retry after reconnect also failed", retryError);
147
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
148
- type: "error",
149
- text1: "Error de impresión",
150
- text2: "No se pudo imprimir incluso tras reconectar la impresora",
151
- });
152
156
  return false;
153
157
  }
154
158
  }
@@ -164,10 +168,11 @@ class PrinterService {
164
168
  return false;
165
169
  }
166
170
  await getPrinterDriver().printImageBase64(imageBase64);
167
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
168
- type: "success",
169
- text1: "Impresión enviada",
170
- text2: "Documento enviado a la impresora",
171
+ notifyDevice({
172
+ name: "printer.sent",
173
+ title: "Impresión enviada",
174
+ message: "Documento enviado a la impresora",
175
+ variant: "success",
171
176
  });
172
177
  return true;
173
178
  }
@@ -233,7 +238,7 @@ class PrinterService {
233
238
  if (!printer)
234
239
  return false;
235
240
  loggerUseCases.info(`Grace reconnect: reconnecting to ${printer.name} (${printer.address})`);
236
- return this.connect(printer.address, printer.name);
241
+ return this.connect(printer.address, printer.name, false);
237
242
  }
238
243
  async ensureConnected() {
239
244
  if (this.isConnected()) {
@@ -241,10 +246,11 @@ class PrinterService {
241
246
  }
242
247
  const printer = await this.getSelectedPrinter();
243
248
  if (!printer) {
244
- UIEventBus.emit(UI_NOTIFICATION_EVENT, {
245
- type: "error",
246
- text1: "Sin impresora",
247
- text2: "Selecciona una impresora primero",
249
+ notifyDevice({
250
+ name: "printer.missing",
251
+ title: "Sin impresora",
252
+ message: "Selecciona una impresora primero",
253
+ variant: "danger",
248
254
  });
249
255
  return false;
250
256
  }
@@ -1,3 +1,4 @@
1
+ import { dayOf, isDay } from "@sincpro/client-framework";
1
2
  import dayjs from "dayjs";
2
3
  import timezone from "dayjs/plugin/timezone";
3
4
  import utc from "dayjs/plugin/utc";
@@ -53,7 +54,7 @@ export function formatDate(input, timezone, options) {
53
54
  const usedLocale = options?.locale ?? deviceLocale;
54
55
  try {
55
56
  const rawStr = typeof input === "string" ? input : input.toISOString();
56
- const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(rawStr);
57
+ const isDateOnly = typeof input === "string" && isDay(input);
57
58
  // Auto-detect showTime si no se pasa explicitamente
58
59
  const effectiveShowTime = options?.showTime !== undefined
59
60
  ? options.showTime
@@ -100,11 +101,11 @@ export function formatDateWithDefaultTimezone(input, options) {
100
101
  export function getCurrentDateInTimezone(timezone) {
101
102
  const effectiveTimezone = timezone || DEFAULT_TIMEZONE;
102
103
  try {
103
- return dayjs().tz(effectiveTimezone).format("YYYY-MM-DD");
104
+ return dayOf(new Date().toISOString(), effectiveTimezone) ?? dayjs().format("YYYY-MM-DD");
104
105
  }
105
106
  catch (error) {
106
107
  console.warn("Error obteniendo fecha actual en timezone:", error);
107
- return dayjs().format("YYYY-MM-DD"); // Fallback to local
108
+ return dayjs().format("YYYY-MM-DD");
108
109
  }
109
110
  }
110
111
  /**
@@ -1,3 +1,6 @@
1
- export declare function parseToMonetaryNumber(input?: string | number | null): number;
2
- export declare function formatTwoDecimals(value?: number | string | null): string;
3
- export declare function calculateDiscountedTotal(total: number, discount: number): number;
1
+ import { Decimal, type DecimalLike } from "@sincpro/client-framework";
2
+ export declare function toDecimal(input?: DecimalLike | null): Decimal;
3
+ export declare function parseToMonetaryNumber(input?: DecimalLike | null): Decimal;
4
+ export declare function formatTwoDecimals(value?: DecimalLike | null): string;
5
+ export declare function discountOf(total: DecimalLike, discount: DecimalLike): Decimal;
6
+ export declare function calculateDiscountedTotal(total: DecimalLike, discount: DecimalLike): Decimal;
@@ -1,31 +1,43 @@
1
- import { Big } from "big.js";
2
- export function parseToMonetaryNumber(input) {
1
+ import { Decimal } from "@sincpro/client-framework";
2
+ export function toDecimal(input) {
3
3
  if (input == null)
4
- return 0;
5
- const s = String(input).trim();
6
- if (!/[0-9]/.test(s))
7
- return 0;
8
- const cleaned = s.replace(/[^0-9.-]+/g, "");
4
+ return new Decimal(0);
5
+ if (typeof input === "number" && !Number.isFinite(input))
6
+ return new Decimal(0);
7
+ if (typeof input === "string") {
8
+ const text = input.trim();
9
+ if (!/[0-9]/.test(text))
10
+ return new Decimal(0);
11
+ try {
12
+ return new Decimal(text.replace(/[^0-9.-]+/g, ""));
13
+ }
14
+ catch {
15
+ return new Decimal(0);
16
+ }
17
+ }
9
18
  try {
10
- return Number(new Big(cleaned).toString());
19
+ return new Decimal(input);
11
20
  }
12
21
  catch {
13
- return 0;
22
+ return new Decimal(0);
14
23
  }
15
24
  }
25
+ export function parseToMonetaryNumber(input) {
26
+ return toDecimal(input);
27
+ }
16
28
  export function formatTwoDecimals(value) {
17
- const raw = parseToMonetaryNumber(value);
18
- const rounded = new Big(raw).toFixed(2);
19
- const num = Number(rounded);
20
- return new Intl.NumberFormat("de-DE", {
21
- minimumFractionDigits: 2,
22
- maximumFractionDigits: 2,
23
- }).format(num);
29
+ const text = toDecimal(value).rounded(2).toString();
30
+ const negative = text.startsWith("-");
31
+ const [whole, fraction = "00"] = (negative ? text.slice(1) : text).split(".");
32
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
33
+ return `${negative ? "-" : ""}${grouped},${fraction}`;
34
+ }
35
+ export function discountOf(total, discount) {
36
+ const rate = toDecimal(discount);
37
+ if (rate.isZero)
38
+ return new Decimal(0);
39
+ return toDecimal(total).times(rate).dividedBy(100, 8);
24
40
  }
25
41
  export function calculateDiscountedTotal(total, discount) {
26
- if (discount === 0) {
27
- return 0;
28
- }
29
- const result = (parseToMonetaryNumber(total) * discount) / 100;
30
- return parseToMonetaryNumber(result);
42
+ return discountOf(total, discount);
31
43
  }
@@ -1,2 +1,2 @@
1
- export declare function safeJsonStringify(obj: any, pretty?: boolean): string;
1
+ export declare function safeJsonStringify(obj: unknown, pretty?: boolean): string;
2
2
  export declare function safeJsonParse(json: string): any;
@@ -1,44 +1,41 @@
1
- export function safeJsonStringify(obj, pretty = false) {
2
- const seen = new WeakSet();
3
- if (pretty) {
4
- return JSON.stringify(obj, (key, value) => {
5
- if (key.startsWith("_")) {
6
- return undefined;
7
- }
8
- if (value instanceof Map) {
9
- return Object.fromEntries(value);
10
- }
11
- if (value instanceof Set) {
12
- return Array.from(value);
13
- }
14
- if (typeof value === "object" && value !== null) {
15
- if (seen.has(value)) {
16
- return "[Circular]";
17
- }
18
- seen.add(value);
19
- }
20
- return value;
21
- }, 2);
1
+ import { readValues, valuesOf } from "@sincpro/client-framework";
2
+ function publishable(value, seen) {
3
+ if (value === null || typeof value !== "object")
4
+ return value;
5
+ if (seen.has(value))
6
+ return "[Circular]";
7
+ if (value instanceof Uint8Array ||
8
+ value instanceof ArrayBuffer ||
9
+ value instanceof DataView ||
10
+ typeof value.toJSON === "function") {
11
+ return value;
22
12
  }
23
- return JSON.stringify(obj, (key, value) => {
24
- if (key.startsWith("_")) {
25
- return undefined;
26
- }
13
+ seen.add(value);
14
+ try {
27
15
  if (value instanceof Map) {
28
- return Object.fromEntries(value);
16
+ return new Map([...value].map(([key, item]) => [key, publishable(item, seen)]));
29
17
  }
30
18
  if (value instanceof Set) {
31
- return Array.from(value);
19
+ return new Set([...value].map((item) => publishable(item, seen)));
32
20
  }
33
- if (typeof value === "object" && value !== null) {
34
- if (seen.has(value)) {
35
- return "[Circular]";
36
- }
37
- seen.add(value);
21
+ if (Array.isArray(value))
22
+ return value.map((item) => publishable(item, seen));
23
+ const published = {};
24
+ for (const [key, item] of Object.entries(value)) {
25
+ if (key.startsWith("_") || typeof item === "function")
26
+ continue;
27
+ published[key] = publishable(item, seen);
38
28
  }
39
- return value;
40
- });
29
+ return published;
30
+ }
31
+ finally {
32
+ seen.delete(value);
33
+ }
34
+ }
35
+ export function safeJsonStringify(obj, pretty = false) {
36
+ const wire = valuesOf(publishable(obj, new WeakSet()));
37
+ return JSON.stringify(wire, null, pretty ? 2 : undefined);
41
38
  }
42
39
  export function safeJsonParse(json) {
43
- return JSON.parse(json);
40
+ return readValues(json);
44
41
  }
@@ -0,0 +1 @@
1
+ export declare function DeviceNotices(): null;
@@ -0,0 +1,29 @@
1
+ import { textOf } from "../../domain/events/label";
2
+ import { useCommon } from "../../entrypoints/ui/common_provider";
3
+ import { useToast } from "@sincpro/mobile-ui/Feedback";
4
+ import { useEffect, useRef } from "react";
5
+ import { useActivity } from "./useActivity";
6
+ const VARIANT = {
7
+ success: "success",
8
+ danger: "danger",
9
+ info: "info",
10
+ };
11
+ export function DeviceNotices() {
12
+ const { notices } = useActivity();
13
+ const { locale } = useCommon();
14
+ const toast = useToast();
15
+ const shownRef = useRef(new Set());
16
+ useEffect(() => {
17
+ for (const notice of notices) {
18
+ if (shownRef.current.has(notice.id))
19
+ continue;
20
+ shownRef.current.add(notice.id);
21
+ toast.show({
22
+ title: textOf(notice.label, locale),
23
+ message: textOf(notice.detail ?? notice.label, locale),
24
+ variant: VARIANT[notice.variant ?? "info"],
25
+ });
26
+ }
27
+ }, [locale, notices, toast]);
28
+ return null;
29
+ }
@@ -0,0 +1,2 @@
1
+ declare function ProcessToast(): null;
2
+ export default ProcessToast;
@@ -0,0 +1,71 @@
1
+ import { textOf } from "../../domain/events/label";
2
+ import { useCommon } from "../../entrypoints/ui/common_provider";
3
+ import { useToast } from "@sincpro/mobile-ui/Feedback";
4
+ import { useEffect, useRef } from "react";
5
+ import { useActivity } from "./useActivity";
6
+ const MAX_ERROR_LEN = 150;
7
+ const MIN_VISIBLE_MS = 1200;
8
+ function ProcessToast() {
9
+ const { queue, crons, failure } = useActivity();
10
+ const { locale } = useCommon();
11
+ const toast = useToast();
12
+ const loadingIdRef = useRef(null);
13
+ const shownErrorRef = useRef(null);
14
+ const startedAtRef = useRef(null);
15
+ const hideTimerRef = useRef(null);
16
+ const progress = queue ?? crons[0];
17
+ const progressLabel = textOf(progress?.label, locale);
18
+ useEffect(() => {
19
+ if (hideTimerRef.current) {
20
+ clearTimeout(hideTimerRef.current);
21
+ hideTimerRef.current = null;
22
+ }
23
+ const hideLoading = () => {
24
+ if (!loadingIdRef.current)
25
+ return;
26
+ toast.hide(loadingIdRef.current);
27
+ loadingIdRef.current = null;
28
+ startedAtRef.current = null;
29
+ };
30
+ if (progress && !failure) {
31
+ if (startedAtRef.current == null)
32
+ startedAtRef.current = Date.now();
33
+ if (loadingIdRef.current) {
34
+ toast.update(loadingIdRef.current, { message: progressLabel, variant: "loading" });
35
+ }
36
+ else {
37
+ loadingIdRef.current = toast.loading(progressLabel);
38
+ }
39
+ }
40
+ else if (loadingIdRef.current) {
41
+ const elapsed = startedAtRef.current
42
+ ? Date.now() - startedAtRef.current
43
+ : MIN_VISIBLE_MS;
44
+ const wait = failure ? 0 : Math.max(0, MIN_VISIBLE_MS - elapsed);
45
+ hideTimerRef.current = setTimeout(hideLoading, wait);
46
+ }
47
+ return () => {
48
+ if (hideTimerRef.current)
49
+ clearTimeout(hideTimerRef.current);
50
+ };
51
+ }, [failure, progress, progressLabel, toast]);
52
+ useEffect(() => {
53
+ if (!failure) {
54
+ shownErrorRef.current = null;
55
+ return;
56
+ }
57
+ if (shownErrorRef.current === failure.id)
58
+ return;
59
+ shownErrorRef.current = failure.id;
60
+ const raw = failure.detail != null ? textOf(failure.detail, locale) : undefined;
61
+ const detail = raw
62
+ ? raw.length > MAX_ERROR_LEN
63
+ ? `${raw.slice(0, MAX_ERROR_LEN)}...`
64
+ : raw
65
+ : undefined;
66
+ const title = textOf(failure.label, locale);
67
+ toast.danger(detail ?? title, { title: `Error: ${title}` });
68
+ }, [failure, locale, toast]);
69
+ return null;
70
+ }
71
+ export default ProcessToast;
@@ -1,4 +1,4 @@
1
1
  import type { ReactNode } from "react";
2
2
  export declare function ProcessToastProvider({ children }: {
3
3
  children: ReactNode;
4
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,4 @@
1
+ export { DeviceNotices } from "./DeviceNotices";
2
+ export { default as ProcessToast } from "./ProcessToast";
3
+ export { ProcessToastProvider } from "./ProcessToastProvider";
4
+ export { useActivity } from "./useActivity";
@@ -0,0 +1,4 @@
1
+ export { DeviceNotices } from "./DeviceNotices";
2
+ export { default as ProcessToast } from "./ProcessToast";
3
+ export { ProcessToastProvider } from "./ProcessToastProvider";
4
+ export { useActivity } from "./useActivity";
@@ -0,0 +1,9 @@
1
+ import { type ActivityFact } from "../../infrastructure/ui/activity";
2
+ export declare function useActivity(options?: {
3
+ all?: boolean;
4
+ }): {
5
+ queue: ActivityFact;
6
+ crons: ActivityFact[];
7
+ failure: ActivityFact | undefined;
8
+ notices: ActivityFact[];
9
+ };
@@ -0,0 +1,48 @@
1
+ import { ACTIVITY_EVENT, ACTIVITY_SETTING_EVENT, activityVisible, applyActivity, emptyActivity, loadActivityEnabled, } from "../../infrastructure/ui/activity";
2
+ import { UIEventBus } from "../../infrastructure/ui/UIEventBus";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ const CHANNELS_ON = {
5
+ queue: true,
6
+ cron: true,
7
+ device: true,
8
+ };
9
+ export function useActivity(options) {
10
+ const all = options?.all ?? false;
11
+ const [state, setState] = useState(emptyActivity);
12
+ const [enabled, setEnabled] = useState(CHANNELS_ON);
13
+ useEffect(() => {
14
+ let alive = true;
15
+ loadActivityEnabled().then((next) => {
16
+ if (alive)
17
+ setEnabled(next);
18
+ });
19
+ return () => {
20
+ alive = false;
21
+ };
22
+ }, []);
23
+ useEffect(() => {
24
+ const offFact = UIEventBus.on(ACTIVITY_EVENT, (payload) => {
25
+ setState((current) => applyActivity(current, payload));
26
+ }, { debounce: false });
27
+ const offSetting = UIEventBus.on(ACTIVITY_SETTING_EVENT, (payload) => {
28
+ const change = payload;
29
+ setEnabled((current) => ({ ...current, [change.channel]: change.enabled }));
30
+ }, { debounce: false });
31
+ return () => {
32
+ offFact();
33
+ offSetting();
34
+ };
35
+ }, []);
36
+ return useMemo(() => {
37
+ const visible = state.running.filter((fact) => activityVisible(fact, enabled, all));
38
+ const queues = visible.filter((fact) => fact.channel === "queue");
39
+ return {
40
+ queue: queues[queues.length - 1],
41
+ crons: visible.filter((fact) => fact.channel === "cron"),
42
+ failure: state.failure && activityVisible(state.failure, enabled, all)
43
+ ? state.failure
44
+ : undefined,
45
+ notices: state.notices.filter((fact) => activityVisible(fact, enabled, all)),
46
+ };
47
+ }, [all, enabled, state]);
48
+ }
@@ -1,2 +1,2 @@
1
- declare function DebugBanner(): import("react/jsx-runtime").JSX.Element | null;
1
+ declare function DebugBanner(): import("react").JSX.Element | null;
2
2
  export { DebugBanner };
@@ -4,5 +4,5 @@ interface BluetoothDeviceSelectorModalProps {
4
4
  onClose: () => void;
5
5
  onSelect: (device: BluetoothDevice) => void;
6
6
  }
7
- declare function BluetoothDeviceSelectorModal({ visible, onClose, onSelect, }: BluetoothDeviceSelectorModalProps): import("react/jsx-runtime").JSX.Element;
7
+ declare function BluetoothDeviceSelectorModal({ visible, onClose, onSelect, }: BluetoothDeviceSelectorModalProps): import("react").JSX.Element;
8
8
  export { BluetoothDeviceSelectorModal };
@@ -23,6 +23,13 @@ function BluetoothDeviceSelectorModal({ visible, onClose, onSelect, }) {
23
23
  const [loading, setLoading] = useState(false);
24
24
  const [selectedDevice, setSelectedDevice] = useState(null);
25
25
  const [connecting, setConnecting] = useState(false);
26
+ const [prevVisible, setPrevVisible] = useState(visible);
27
+ if (visible !== prevVisible) {
28
+ setPrevVisible(visible);
29
+ if (visible) {
30
+ setSelectedDevice(null);
31
+ }
32
+ }
26
33
  const loadDevices = useCallback(async () => {
27
34
  setLoading(true);
28
35
  try {
@@ -38,8 +45,7 @@ function BluetoothDeviceSelectorModal({ visible, onClose, onSelect, }) {
38
45
  }, []);
39
46
  useEffect(() => {
40
47
  if (visible) {
41
- loadDevices();
42
- setSelectedDevice(null);
48
+ Promise.resolve().then(() => loadDevices());
43
49
  }
44
50
  }, [visible, loadDevices]);
45
51
  async function handleConfirm() {
@@ -4,5 +4,5 @@ interface DeadLetterErrorBlockProps {
4
4
  onToggle: () => void;
5
5
  previewChars?: number;
6
6
  }
7
- declare function DeadLetterErrorBlock({ errorMessage, expanded, onToggle, previewChars, }: DeadLetterErrorBlockProps): import("react/jsx-runtime").JSX.Element | null;
7
+ declare function DeadLetterErrorBlock({ errorMessage, expanded, onToggle, previewChars, }: DeadLetterErrorBlockProps): import("react").JSX.Element | null;
8
8
  export default DeadLetterErrorBlock;
@@ -1,7 +1,7 @@
1
- import { DomainEvent } from "../../../domain/event_sourcing";
1
+ import { IntegrationEvent } from "../../../domain/events";
2
2
  interface EventTimelineItemProps {
3
- event: DomainEvent;
3
+ event: IntegrationEvent;
4
4
  isLast?: boolean;
5
5
  }
6
- declare function EventTimelineItem({ event, isLast }: EventTimelineItemProps): import("react/jsx-runtime").JSX.Element;
6
+ declare function EventTimelineItem({ event, isLast }: EventTimelineItemProps): import("react").JSX.Element;
7
7
  export default EventTimelineItem;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
- import { EEventStatus } from "../../../domain/event_sourcing";
2
+ import { EEventStatus } from "../../../domain/events";
3
3
  import { Display } from "@sincpro/mobile-ui/Display";
4
4
  import CopyableText from "@sincpro/mobile-ui/Display/Display.CopyableText";
5
5
  import { useTheme } from "@sincpro/mobile-ui/theme";
@@ -4,5 +4,5 @@ interface GeoPermissionCardProps {
4
4
  loading?: boolean;
5
5
  handleRequestPermission?: () => void;
6
6
  }
7
- declare function GeoPermissionCard({ title, loading, handleRequestPermission, hasPermission, }: GeoPermissionCardProps): import("react/jsx-runtime").JSX.Element;
7
+ declare function GeoPermissionCard({ title, loading, handleRequestPermission, hasPermission, }: GeoPermissionCardProps): import("react").JSX.Element;
8
8
  export default GeoPermissionCard;
@@ -3,5 +3,5 @@ interface TimeZoneSelectorProps {
3
3
  onSelect?: (tz: TimezoneLocale) => void;
4
4
  timezone?: string | null;
5
5
  }
6
- export declare function TimeZoneSelector({ onSelect, timezone }: TimeZoneSelectorProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function TimeZoneSelector({ onSelect, timezone }: TimeZoneSelectorProps): import("react").JSX.Element;
7
7
  export {};
@@ -5,5 +5,5 @@ interface TimeZoneSelectorModalProps {
5
5
  onSelect: (tz: TimezoneLocale) => void;
6
6
  currentTimezone?: string | null;
7
7
  }
8
- declare function TimeZoneSelectorModal({ visible, onClose, onSelect, currentTimezone, }: TimeZoneSelectorModalProps): import("react/jsx-runtime").JSX.Element;
8
+ declare function TimeZoneSelectorModal({ visible, onClose, onSelect, currentTimezone, }: TimeZoneSelectorModalProps): import("react").JSX.Element;
9
9
  export { TimeZoneSelectorModal };