@pensasystems/pensa-react-native 0.1.0-beta.1 → 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.
@@ -21,5 +21,5 @@ Pod::Spec.new do |s|
21
21
  s.dependency "React-Core"
22
22
  end
23
23
 
24
- s.dependency "PensaSdk", "~> 1.0.4"
24
+ s.dependency "PensaSdk"
25
25
  end
package/README.md CHANGED
@@ -7,17 +7,18 @@ The Pensa Mobile App SDK is a developer toolkit designed to simplify adding Pens
7
7
  Integrating the Pensa SDK into your React Native project is simple. Follow the steps below to add the SDK and enable advanced shelf recognition features in your app:
8
8
 
9
9
  ### 1- Install the SDK Package
10
+ Registry: https://www.npmjs.com/package/@pensasystems/pensa-react-native
10
11
 
11
12
  Add the npm package to your project:
12
13
 
13
14
  ```bash
14
- npm install pensa-sdk-react-native
15
+ npm install @pensasystems/pensa-react-native
15
16
  ```
16
17
 
17
18
  or
18
19
 
19
20
  ```bash
20
- yarn add pensa-sdk-react-native
21
+ yarn add @pensasystems/pensa-react-native
21
22
  ```
22
23
 
23
24
  ### 2- Install Native Dependencies (iOS)
@@ -44,7 +45,7 @@ Initializing the Pensa SDK is straightforward. Follow the steps below to set up
44
45
  Call `initPensa()` at the startup of your app, preferably in a root file like `index.js` before calling `AppRegistry.registerComponent`.
45
46
 
46
47
  ```tsx
47
- import {initPensa} from 'pensa-sdk-react-native';
48
+ import {initPensa} from '@pensasystems/pensa-react-native';
48
49
 
49
50
  initPensa({
50
51
  clientId: 'YOUR_CLIENT_ID',
@@ -71,7 +72,7 @@ To listen to SDK events such as upload progress, success, or failure, you can cr
71
72
  ```tsx
72
73
  // hooks/usePensaListeners.ts
73
74
  import {useEffect} from 'react';
74
- import {PensaEvents} from 'pensa-sdk-react-native';
75
+ import {PensaEvents} from '@pensasystems/pensa-react-native';
75
76
 
76
77
  export const usePensaListeners = () => {
77
78
  useEffect(() => {
@@ -151,7 +152,7 @@ import {
151
152
  showScanArea,
152
153
  showStockingScreen,
153
154
  showStoreChecklist,
154
- } from 'pensa-sdk-react-native';
155
+ } from '@pensasystems/pensa-react-native';
155
156
  ```
156
157
 
157
158
  ### Searching stores
@@ -273,6 +274,99 @@ showScanArea(scanId, storeId, globalStoreId)
273
274
 
274
275
  This method allows you to navigate directly to a scan area without going through the store selection flow.
275
276
 
277
+ # Troubleshooting (Android)
278
+
279
+ ## 1. Core Library Desugaring Required
280
+
281
+ If you see an error mentioning that `core library desugaring` must be enabled (typically referencing `AAR metadata` or `desugar_jdk_libs`), update your Android project as follows:
282
+
283
+ ### **Step 1 — Add desugaring dependency**
284
+
285
+ In your app-level `build.gradle`:
286
+
287
+ ```gradle
288
+ dependencies {
289
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
290
+ }
291
+ ```
292
+
293
+ ### **Step 2 — Enable desugaring & set Java 17**
294
+
295
+ Inside `android { compileOptions { ... } }`:
296
+
297
+ ```gradle
298
+ compileOptions {
299
+ sourceCompatibility JavaVersion.VERSION_17
300
+ targetCompatibility JavaVersion.VERSION_17
301
+ coreLibraryDesugaringEnabled true
302
+ }
303
+ ```
304
+
305
+ This configuration is required because the native Pensa Android SDK uses Java 17 language features.
306
+
307
+ ---
308
+
309
+ ## 2. Min SDK Version Requirement
310
+
311
+ If your Android build fails with a message indicating:
312
+
313
+ > *minSdkVersion cannot be smaller than 26 for library com.pensasystems:pensasdk*
314
+
315
+ Then your `minSdkVersion` must be updated.
316
+
317
+ ### **Fix**
318
+
319
+ In your app-level `build.gradle`:
320
+
321
+ ```gradle
322
+ defaultConfig {
323
+ minSdkVersion 26
324
+ }
325
+ ```
326
+
327
+ The native Pensa Android SDK requires **minSdk 26**.
328
+
329
+ ---
330
+
331
+ ## 3. Metro Bundler Cannot Connect in Debug Mode (Android Only)
332
+
333
+ If the Android app cannot connect to Metro (e.g., blank screen, “Unable to load script from assets”), update your debug network rules.
334
+
335
+ ### **Step 1 — Create `network_security_config.xml`**
336
+
337
+ Create a file:
338
+
339
+ ```
340
+ android/app/src/main/res/xml/network_security_config.xml
341
+ ```
342
+
343
+ With the following content:
344
+
345
+ ```xml
346
+ <?xml version="1.0" encoding="utf-8"?>
347
+ <network-security-config>
348
+ <domain-config cleartextTrafficPermitted="true">
349
+ <domain includeSubdomains="true">10.0.2.2</domain>
350
+ <domain includeSubdomains="true">localhost</domain>
351
+ <domain includeSubdomains="true">127.0.0.1</domain>
352
+ </domain-config>
353
+ </network-security-config>
354
+ ```
355
+
356
+ ### **Step 2 — Reference it in `AndroidManifest.xml`**
357
+
358
+ Inside your `<application>` tag:
359
+
360
+ ```xml
361
+ <application
362
+ android:networkSecurityConfig="@xml/network_security_config"
363
+ ... >
364
+ ```
365
+
366
+ This allows the Android emulator to access the React Native bundler during development.
367
+
368
+ ---
369
+
276
370
  ### Example Project
277
371
 
278
372
  For code examples and integration best practices, refer to our GitHub repository.
@@ -1,3 +1,4 @@
1
+
1
2
  buildscript {
2
3
  ext.getExtOrDefault = {name ->
3
4
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['PensaSdkReactNative_' + name]
@@ -83,7 +84,7 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
83
84
 
84
85
  dependencies {
85
86
  coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
86
- implementation "com.pensasystems:pensasdk:1.0.8"
87
+ implementation "com.pensasystems:pensasdk:1.0.23"
87
88
  implementation "com.facebook.react:react-android"
88
89
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
89
90
  }
@@ -2,9 +2,10 @@ package com.pensasdkreactnative
2
2
 
3
3
  import com.facebook.react.bridge.ReactApplicationContext
4
4
  import com.pensasystems.pensasdk.listener.CantScanEventListener
5
+ import com.pensasystems.pensasdk.listener.CompletedScanWithGuidListener
5
6
  import com.pensasystems.pensasdk.listener.ScanUploadListener
6
7
 
7
- object PensaListeners : ScanUploadListener, CantScanEventListener {
8
+ object PensaListeners : ScanUploadListener, CantScanEventListener, CompletedScanWithGuidListener {
8
9
 
9
10
  private var emitter: PensaEventEmitterModule? = null
10
11
 
@@ -41,4 +42,12 @@ object PensaListeners : ScanUploadListener, CantScanEventListener {
41
42
  "reason" to cantScanReason
42
43
  ))
43
44
  }
45
+
46
+ override fun onScanCompletedWithGuid(guid: String?, tdlinxId: String, scanId: Int) {
47
+ emitter?.sendEvent("onScanCompletedWithGuid", mapOf(
48
+ "guid" to guid,
49
+ "tdlinxId" to tdlinxId,
50
+ "scanId" to scanId
51
+ ))
52
+ }
44
53
  }
@@ -1,14 +1,17 @@
1
1
  package com.pensasdkreactnative
2
2
 
3
+ import com.facebook.react.bridge.Arguments
3
4
  import com.facebook.react.bridge.ReactApplicationContext
4
5
  import com.facebook.react.bridge.ReactContextBaseJavaModule
5
6
  import com.facebook.react.bridge.ReactMethod
6
7
  import com.facebook.react.bridge.Promise
8
+ import com.facebook.react.bridge.ReadableArray
7
9
  import com.facebook.react.bridge.ReadableMap
10
+ import com.facebook.react.bridge.ReadableType
8
11
  import com.pensasystems.pensasdk.PensaSdk
9
12
  import com.pensasystems.pensasdk.PensaSdkConfiguration
10
- import com.pensasystems.pensasdk.listener.CantScanEventListener
11
- import com.pensasystems.pensasdk.listener.ScanUploadListener
13
+ import com.pensasystems.pensasdk.model.PensaReportType
14
+ import com.pensasystems.pensasdk.model.PensaShelfItem
12
15
 
13
16
  class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
14
17
  ReactContextBaseJavaModule(reactContext) {
@@ -40,6 +43,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
40
43
  .enableLogging(isLoggingEnabled)
41
44
  .setScanUploadListener(PensaListeners)
42
45
  .setCantScanEventListener(PensaListeners)
46
+ .setCompletedScanWithGuidListener(PensaListeners)
43
47
  .build()
44
48
 
45
49
  PensaSdk.initPensa(
@@ -69,7 +73,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
69
73
 
70
74
  @ReactMethod
71
75
  fun showShelfScans(promise: Promise) {
72
- val activity = currentActivity
76
+ val activity = reactApplicationContext.currentActivity
73
77
  if (activity != null) {
74
78
  PensaSdk.showShelfScans(
75
79
  context = activity,
@@ -82,7 +86,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
82
86
  }
83
87
  @ReactMethod
84
88
  fun showProductScans(promise: Promise) {
85
- val activity = currentActivity
89
+ val activity = reactApplicationContext.currentActivity
86
90
  if (activity != null) {
87
91
  PensaSdk.showProductScans(
88
92
  context = activity,
@@ -96,7 +100,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
96
100
 
97
101
  @ReactMethod
98
102
  fun showStoreSearchView(promise: Promise) {
99
- val activity = currentActivity
103
+ val activity = reactApplicationContext.currentActivity
100
104
  if (activity != null) {
101
105
  PensaSdk.showStoreSearchView(
102
106
  context = activity,
@@ -110,7 +114,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
110
114
 
111
115
  @ReactMethod
112
116
  fun showStoresScreen(promise: Promise) {
113
- val activity = currentActivity
117
+ val activity = reactApplicationContext.currentActivity
114
118
  if (activity != null) {
115
119
  PensaSdk.showStoresScreen(
116
120
  context = activity,
@@ -124,7 +128,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
124
128
 
125
129
  @ReactMethod
126
130
  fun showScanArea(scanId: Int, storeId: Int?, globalStoreId: String?, promise: Promise) {
127
- val activity = currentActivity
131
+ val activity = reactApplicationContext.currentActivity
128
132
  if (activity != null) {
129
133
  PensaSdk.showScanArea(
130
134
  context = activity,
@@ -141,7 +145,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
141
145
 
142
146
  @ReactMethod
143
147
  fun showStockingScreen(promise: Promise) {
144
- val activity = currentActivity
148
+ val activity = reactApplicationContext.currentActivity
145
149
  if (activity != null) {
146
150
  PensaSdk.showStockingScreen(
147
151
  context = activity,
@@ -155,7 +159,7 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
155
159
 
156
160
  @ReactMethod
157
161
  fun showStoreChecklist(globalStoreId: String, guid: String?, sectionKey: String?, promise: Promise) {
158
- val activity = currentActivity
162
+ val activity = reactApplicationContext.currentActivity
159
163
  if (activity != null) {
160
164
  PensaSdk.showStoreChecklist(
161
165
  context = activity,
@@ -169,4 +173,350 @@ class PensaSdkReactNativeModule(reactContext: ReactApplicationContext) :
169
173
  promise.reject("NO_ACTIVITY", "Current activity is null")
170
174
  }
171
175
  }
176
+
177
+ @ReactMethod
178
+ fun showStoreChecklistWithGuid(globalStoreId: String, guid: String, promise: Promise) {
179
+ val activity = reactApplicationContext.currentActivity
180
+ if (activity != null) {
181
+ PensaSdk.showStoreChecklistWithGuid(
182
+ context = activity,
183
+ globalStoreId = globalStoreId,
184
+ guid = guid,
185
+ onError = { error -> promise.reject("SHOW_STORE_CHECKLIST_WITH_GUID_FAILED", error) }
186
+ )
187
+ promise.resolve(null)
188
+ } else {
189
+ promise.reject("NO_ACTIVITY", "Current activity is null")
190
+ }
191
+ }
192
+
193
+ @ReactMethod
194
+ fun fetchScanStatuses(scanIds: ReadableArray, promise: Promise) {
195
+ try {
196
+ val ids = mutableListOf<Int>()
197
+ for (i in 0 until scanIds.size()) {
198
+ ids.add(scanIds.getInt(i))
199
+ }
200
+
201
+ PensaSdk.fetchScanStatuses(
202
+ scanIds = ids,
203
+ onSuccess = { statuses ->
204
+ val rnList = statuses.map { status ->
205
+ val map = Arguments.createMap()
206
+ map.putInt("id", status.id)
207
+ map.putString("created", status.created)
208
+ map.putString("fastStatus", status.fastStatus)
209
+ map.putString("scanTimestamp", status.scanTimestamp)
210
+ map.putString("scannerId", status.scannerId)
211
+ map.putString("status", status.status)
212
+
213
+ val metadata = Arguments.createMap()
214
+ metadata.putString("guid", status.metadata?.guid)
215
+ metadata.putString("serverUploadStarted", status.metadata?.serverUploadStarted)
216
+ map.putMap("metadata", metadata)
217
+
218
+ map
219
+ }
220
+
221
+ val result = Arguments.createArray()
222
+ rnList.forEach { result.pushMap(it) }
223
+ promise.resolve(result)
224
+
225
+ },
226
+ onError = { error ->
227
+ promise.reject("FETCH_SCAN_STATUSES_FAILED", error)
228
+ }
229
+ )
230
+ } catch (e: Exception) {
231
+ promise.reject("FETCH_SCAN_STATUSES_EXCEPTION", e)
232
+ }
233
+ }
234
+
235
+ @ReactMethod
236
+ fun fetchVisitScansStatus(guid: String, promise: Promise) {
237
+ try {
238
+ PensaSdk.fetchVisitScansStatus(
239
+ guid = guid,
240
+ onSuccess = { result ->
241
+ val scans = result.scans
242
+ val map = Arguments.createMap().apply {
243
+ result.guid?.let { putString("guid", it) }
244
+ result.visitStatus?.let { putString("visitStatus", it) }
245
+ if (scans == null) {
246
+ } else {
247
+ val scansArray = Arguments.createArray()
248
+ scans.forEach { scan ->
249
+ val uploadCompleted = scan.uploadCompleted
250
+ scansArray.pushMap(
251
+ Arguments.createMap().apply {
252
+ scan.created?.let { putString("created", it) }
253
+ scan.fastStatus?.let { putString("fastStatus", it) }
254
+ scan.scanId?.let { putInt("scanId", it) }
255
+ scan.status?.let { putString("status", it) }
256
+ when (uploadCompleted) {
257
+ null -> {}
258
+ else -> putBoolean("uploadCompleted", uploadCompleted)
259
+ }
260
+ }
261
+ )
262
+ }
263
+ putArray("scans", scansArray)
264
+ }
265
+ }
266
+
267
+ promise.resolve(map)
268
+ },
269
+ onError = { error ->
270
+ promise.reject("FETCH_VISIT_SCANS_STATUS_FAILED", error)
271
+ }
272
+ )
273
+ } catch (e: Exception) {
274
+ promise.reject("FETCH_VISIT_SCANS_STATUS_EXCEPTION", e)
275
+ }
276
+ }
277
+
278
+ @ReactMethod
279
+ fun fetchOnDemandReports(
280
+ scanId: Double?,
281
+ guid: String?,
282
+ projectId: Double?,
283
+ reportTypes: ReadableArray?,
284
+ promise: Promise
285
+ ) {
286
+ try {
287
+ val finalReportTypes = parseReportTypes(reportTypes)
288
+ val normalizedScanId = scanId?.toInt()
289
+ val normalizedProjectId = projectId?.toInt()
290
+
291
+ PensaSdk.fetchOnDemandReports(
292
+ scanId = normalizedScanId,
293
+ guid = guid,
294
+ projectId = normalizedProjectId,
295
+ reportTypes = finalReportTypes,
296
+ onSuccess = { report ->
297
+ val result = Arguments.createMap()
298
+
299
+ fun toProductMap(p: com.pensasystems.pensasdk.model.PensaOnDemandProduct) =
300
+ Arguments.createMap().apply {
301
+ p.brand?.let { putString("brand", it) }
302
+ p.brandId?.let { putInt("brandId", it) }
303
+
304
+ p.category?.let { putString("category", it) }
305
+ p.categoryId?.let { putInt("categoryId", it) }
306
+
307
+ p.expectedFacings?.let { putInt("expectedFacings", it) }
308
+ p.facings?.let { putInt("facings", it) }
309
+
310
+ p.manufacturer?.let { putString("manufacturer", it) }
311
+ p.manufacturerId?.let { putInt("manufacturerId", it) }
312
+
313
+ p.product?.let { putString("product", it) }
314
+ p.productId?.let { putInt("productId", it) }
315
+
316
+ p.upc?.let { putString("upc", it) }
317
+ }
318
+
319
+ fun toBucketMap(b: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket) =
320
+ Arguments.createMap().apply {
321
+ val arr = Arguments.createArray()
322
+ b.productList.forEach { arr.pushMap(toProductMap(it)) }
323
+ putArray("productList", arr)
324
+ }
325
+
326
+ fun putBucket(target: com.facebook.react.bridge.WritableMap, key: String, bucket: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?) {
327
+ if (bucket != null) target.putMap(key, toBucketMap(bucket))
328
+ }
329
+
330
+ fun fillReportBuckets(
331
+ target: com.facebook.react.bridge.WritableMap,
332
+ facingsSeen: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
333
+ fullFacings: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
334
+ fullFacingsAndProductPositions: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
335
+ itemsSeen: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
336
+ newProducts: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
337
+ newProductPackaging: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
338
+ missingPriceTag: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
339
+ noOos: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
340
+ oos: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
341
+ productPositions: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
342
+ scanFailed: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
343
+ scanRejected: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
344
+ scanSkipped: com.pensasystems.pensasdk.model.PensaOnDemandReportBucket?,
345
+ ) {
346
+ finalReportTypes.distinct().forEach { t ->
347
+ when (t) {
348
+ PensaReportType.FACINGS_SEEN -> putBucket(target, "facingsSeen", facingsSeen)
349
+ PensaReportType.FULL_FACINGS -> putBucket(target, "fullFacings", fullFacings)
350
+ PensaReportType.FULL_FACINGS_AND_PRODUCT_POSITIONS ->
351
+ putBucket(target, "fullFacingsAndProductPositions", fullFacingsAndProductPositions)
352
+ PensaReportType.ITEMS_SEEN -> putBucket(target, "itemsSeen", itemsSeen)
353
+ PensaReportType.NEW_PRODUCTS -> putBucket(target, "newProducts", newProducts)
354
+ PensaReportType.NEW_PRODUCT_PACKAGING -> putBucket(target, "newProductPackaging", newProductPackaging)
355
+ PensaReportType.MISSING_PRICE_TAG -> putBucket(target, "missingPriceTag", missingPriceTag)
356
+ PensaReportType.NO_OOS -> putBucket(target, "noOos", noOos)
357
+ PensaReportType.OOS -> putBucket(target, "oos", oos)
358
+ PensaReportType.PRODUCT_POSITIONS -> putBucket(target, "productPositions", productPositions)
359
+ PensaReportType.SCAN_FAILED -> putBucket(target, "scanFailed", scanFailed)
360
+ PensaReportType.SCAN_REJECTED -> putBucket(target, "scanRejected", scanRejected)
361
+ PensaReportType.SCAN_SKIPPED -> putBucket(target, "scanSkipped", scanSkipped)
362
+ }
363
+ }
364
+ }
365
+
366
+ fillReportBuckets(
367
+ result,
368
+ report.facingsSeen,
369
+ report.fullFacings,
370
+ report.fullFacingsAndProductPositions,
371
+ report.itemsSeen,
372
+ report.newProducts,
373
+ report.newProductPackaging,
374
+ report.missingPriceTag,
375
+ report.noOos,
376
+ report.oos,
377
+ report.productPositions,
378
+ report.scanFailed,
379
+ report.scanRejected,
380
+ report.scanSkipped
381
+ )
382
+
383
+ // Other fields
384
+ report.projectId?.let { result.putInt("projectId", it) }
385
+ report.projectName?.let { result.putString("projectName", it) }
386
+ report.projectReportId?.let { result.putInt("projectReportId", it) }
387
+ report.projectReportName?.let { result.putString("projectReportName", it) }
388
+ report.realogramSignedUrl?.let { result.putString("realogramSignedUrl", it) }
389
+ report.shelfId?.let { result.putInt("shelfId", it) }
390
+ report.shelfName?.let { result.putString("shelfName", it) }
391
+ report.status?.let { result.putString("status", it) }
392
+ val scans = report.scans
393
+ if (scans != null) {
394
+ val scansArray = Arguments.createArray()
395
+ scans.forEach { scan ->
396
+ scansArray.pushMap(
397
+ Arguments.createMap().apply {
398
+ fillReportBuckets(
399
+ this,
400
+ scan.facingsSeen,
401
+ scan.fullFacings,
402
+ scan.fullFacingsAndProductPositions,
403
+ scan.itemsSeen,
404
+ scan.newProducts,
405
+ scan.newProductPackaging,
406
+ scan.missingPriceTag,
407
+ scan.noOos,
408
+ scan.oos,
409
+ scan.productPositions,
410
+ scan.scanFailed,
411
+ scan.scanRejected,
412
+ scan.scanSkipped
413
+ )
414
+ scan.projectId?.let { putInt("projectId", it) }
415
+ scan.projectName?.let { putString("projectName", it) }
416
+ scan.projectReportId?.let { putInt("projectReportId", it) }
417
+ scan.projectReportName?.let { putString("projectReportName", it) }
418
+ scan.realogramSignedUrl?.let { putString("realogramSignedUrl", it) }
419
+ scan.scanId?.let { putInt("scanId", it) }
420
+ scan.shelfId?.let { putInt("shelfId", it) }
421
+ scan.shelfName?.let { putString("shelfName", it) }
422
+ scan.status?.let { putString("status", it) }
423
+ }
424
+ )
425
+ }
426
+ result.putArray("scans", scansArray)
427
+ }
428
+
429
+ val visit = report.visit
430
+ if (visit != null) {
431
+ result.putMap(
432
+ "visit",
433
+ Arguments.createMap().apply {
434
+ visit.endTime?.let { putString("endTime", it) }
435
+ visit.identifier?.let { putString("identifier", it) }
436
+ visit.startTime?.let { putString("startTime", it) }
437
+ visit.storeName?.let { putString("storeName", it) }
438
+ visit.storeTdlinx?.let { putString("storeTdlinx", it) }
439
+ }
440
+ )
441
+ }
442
+
443
+ promise.resolve(result)
444
+ },
445
+ onError = { error ->
446
+ promise.reject("FETCH_ON_DEMAND_REPORTS_FAILED", error)
447
+ }
448
+ )
449
+ } catch (e: Exception) {
450
+ promise.reject("ON_DEMAND_EXCEPTION", e)
451
+ }
452
+ }
453
+
454
+ @ReactMethod
455
+ fun fetchProductImageBase64(productId: Int, promise: Promise) {
456
+ try {
457
+ val activity = reactApplicationContext.currentActivity
458
+ if (activity != null) {
459
+ PensaSdk.fetchProductImageBase64(
460
+ context = activity,
461
+ productId = productId,
462
+ onSuccess = { base64 ->
463
+ promise.resolve(base64)
464
+ },
465
+ onError = { error ->
466
+ promise.reject("FETCH_PRODUCT_IMAGE_FAILED", error)
467
+ }
468
+ )
469
+ } else {
470
+ promise.reject("NO_ACTIVITY", "Current activity is null")
471
+ }
472
+ } catch (e: Exception) {
473
+ promise.reject("FETCH_PRODUCT_IMAGE_EXCEPTION", e)
474
+ }
475
+ }
476
+
477
+ @ReactMethod
478
+ fun fetchShelves(globalStoreId: String, promise: Promise) {
479
+ try {
480
+ PensaSdk.fetchShelves(
481
+ globalStoreId = globalStoreId,
482
+ onSuccess = { shelves ->
483
+ val result = Arguments.createArray()
484
+
485
+ shelves.forEach { shelf ->
486
+ val map = Arguments.createMap()
487
+ map.putInt("id", shelf.id)
488
+ map.putString("name", shelf.name)
489
+ result.pushMap(map)
490
+ }
491
+
492
+ promise.resolve(result)
493
+ },
494
+ onError = { error ->
495
+ promise.reject("FETCH_SHELVES_FAILED", error)
496
+ }
497
+ )
498
+ } catch (e: Exception) {
499
+ promise.reject("FETCH_SHELVES_EXCEPTION", e)
500
+ }
501
+ }
502
+
503
+ // Helpers
504
+
505
+ private fun parseReportTypes(reportTypes: ReadableArray?): List<PensaReportType> {
506
+ if (reportTypes == null || reportTypes.size() == 0) return listOf(PensaReportType.ITEMS_SEEN)
507
+
508
+ val list = mutableListOf<PensaReportType>()
509
+ for (i in 0 until reportTypes.size()) {
510
+ if (reportTypes.getType(i) != ReadableType.String) continue
511
+ val raw = reportTypes.getString(i) ?: continue
512
+ val parsed = try {
513
+ PensaReportType.valueOf(raw)
514
+ } catch (e: Exception) {
515
+ null
516
+ }
517
+ if (parsed != null) list.add(parsed)
518
+ }
519
+
520
+ return if (list.isEmpty()) listOf(PensaReportType.ITEMS_SEEN) else list.distinct()
521
+ }
172
522
  }
@@ -20,7 +20,8 @@ class PensaEventEmitter: RCTEventEmitter {
20
20
  "onScanUploadProgressUpdate",
21
21
  "onScanUploadCompleted",
22
22
  "onScanUploadFailed",
23
- "onCantScanReported"
23
+ "onCantScanReported",
24
+ "onScanCompletedWithGuid"
24
25
  ]
25
26
  }
26
27
  }
@@ -1,7 +1,7 @@
1
1
  import Foundation
2
2
  import PensaSdk
3
3
 
4
- class PensaListeners: NSObject, ScanUploadListener, CantScanEventListener {
4
+ class PensaListeners: NSObject, ScanUploadListener, CantScanEventListener, CompletedScanWithGuidListener {
5
5
 
6
6
  static let shared = PensaListeners()
7
7
 
@@ -34,4 +34,15 @@ class PensaListeners: NSObject, ScanUploadListener, CantScanEventListener {
34
34
  body: ["tdlinxId": tdlinxId, "scanAreaId": scanAreaId, "reason": cantScanReason]
35
35
  )
36
36
  }
37
+
38
+ func onScanCompletedWithGuid(guid: String?, tdlinxId: String, scanId: Int) {
39
+ PensaEventEmitter.shared?.sendEvent(
40
+ withName: "onScanCompletedWithGuid",
41
+ body: [
42
+ "guid": guid ?? NSNull(),
43
+ "tdlinxId": tdlinxId,
44
+ "scanId": scanId
45
+ ]
46
+ )
47
+ }
37
48
  }
@@ -37,6 +37,10 @@ RCT_EXTERN_METHOD(showStoreChecklist:(NSString *)globalStoreId
37
37
  withResolver:(RCTPromiseResolveBlock)resolve
38
38
  withRejecter:(RCTPromiseRejectBlock)reject)
39
39
 
40
+ RCT_EXTERN_METHOD(fetchShelves:(NSString *)globalStoreId
41
+ withResolver:(RCTPromiseResolveBlock)resolve
42
+ withRejecter:(RCTPromiseRejectBlock)reject)
43
+
40
44
  @end
41
45
 
42
46
  @interface RCT_EXTERN_MODULE(PensaEventEmitter, RCTEventEmitter)