@blackcode_sa/metaestetics-api 1.14.57 → 1.14.58

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.
@@ -1505,10 +1505,98 @@ export class ProcedureService extends BaseService {
1505
1505
  }
1506
1506
  }
1507
1507
 
1508
+ /**
1509
+ * Creates a serializable cursor from a DocumentSnapshot or returns the cursor values.
1510
+ * This format can be passed through React Native state/Redux without losing data.
1511
+ *
1512
+ * @param doc - The Firestore DocumentSnapshot
1513
+ * @param orderByField - The field used in orderBy clause
1514
+ * @returns Serializable cursor object with values needed for startAfter
1515
+ */
1516
+ private createSerializableCursor(
1517
+ doc: any,
1518
+ orderByField: string = 'createdAt',
1519
+ ): { __cursor: true; values: any[]; id: string; orderByField: string } | null {
1520
+ if (!doc) return null;
1521
+
1522
+ const data = typeof doc.data === 'function' ? doc.data() : doc;
1523
+ const docId = doc.id || data?.id;
1524
+
1525
+ if (!docId) return null;
1526
+
1527
+ // Get the value of the orderBy field
1528
+ let orderByValue = data?.[orderByField];
1529
+
1530
+ // Handle Firestore Timestamp
1531
+ if (orderByValue && typeof orderByValue.toDate === 'function') {
1532
+ orderByValue = orderByValue.toMillis();
1533
+ } else if (orderByValue && orderByValue.seconds) {
1534
+ // Serialized Timestamp
1535
+ orderByValue = orderByValue.seconds * 1000 + (orderByValue.nanoseconds || 0) / 1000000;
1536
+ }
1537
+
1538
+ return {
1539
+ __cursor: true,
1540
+ values: [orderByValue],
1541
+ id: docId,
1542
+ orderByField,
1543
+ };
1544
+ }
1545
+
1546
+ /**
1547
+ * Converts a serializable cursor back to values for startAfter.
1548
+ * Handles both native DocumentSnapshots and serialized cursor objects.
1549
+ *
1550
+ * @param lastDoc - Either a DocumentSnapshot or a serializable cursor object
1551
+ * @param orderByField - The field used in orderBy clause (for validation)
1552
+ * @returns Values to spread into startAfter, or null if invalid
1553
+ */
1554
+ private getCursorValuesForStartAfter(
1555
+ lastDoc: any,
1556
+ orderByField: string = 'createdAt',
1557
+ ): any[] | null {
1558
+ if (!lastDoc) return null;
1559
+
1560
+ // If it's a native DocumentSnapshot with data() method
1561
+ if (typeof lastDoc.data === 'function') {
1562
+ return [lastDoc];
1563
+ }
1564
+
1565
+ // If it's our serializable cursor format
1566
+ if (lastDoc.__cursor && Array.isArray(lastDoc.values)) {
1567
+ // Reconstruct Timestamp if needed for createdAt
1568
+ if (orderByField === 'createdAt' && typeof lastDoc.values[0] === 'number') {
1569
+ const timestamp = Timestamp.fromMillis(lastDoc.values[0]);
1570
+ return [timestamp];
1571
+ }
1572
+ return lastDoc.values;
1573
+ }
1574
+
1575
+ // If it's an array of values directly
1576
+ if (Array.isArray(lastDoc)) {
1577
+ return lastDoc;
1578
+ }
1579
+
1580
+ // Fallback: try to use the object's orderByField value
1581
+ if (lastDoc[orderByField]) {
1582
+ let value = lastDoc[orderByField];
1583
+ if (typeof value === 'number' && orderByField === 'createdAt') {
1584
+ value = Timestamp.fromMillis(value);
1585
+ } else if (value.seconds && orderByField === 'createdAt') {
1586
+ value = new Timestamp(value.seconds, value.nanoseconds || 0);
1587
+ }
1588
+ return [value];
1589
+ }
1590
+
1591
+ console.warn('[PROCEDURE_SERVICE] Could not parse lastDoc cursor:', typeof lastDoc);
1592
+ return null;
1593
+ }
1594
+
1508
1595
  /**
1509
1596
  * Searches and filters procedures based on multiple criteria
1510
1597
  *
1511
- * @note Frontend MORA da šalje ceo snapshot (ili barem sva polja po kojima sortiraš, npr. nameLower) kao lastDoc za paginaciju, a ne samo id!
1598
+ * @note Frontend can now send either a DocumentSnapshot or a serializable cursor object.
1599
+ * The serializable cursor format is: { __cursor: true, values: [...], id: string, orderByField: string }
1512
1600
  *
1513
1601
  * @param filters - Various filters to apply
1514
1602
  * @param filters.nameSearch - Optional search text for procedure name
@@ -1645,13 +1733,12 @@ export class ProcedureService extends BaseService {
1645
1733
  constraints.push(where('nameLower', '<=', searchTerm + '\uf8ff'));
1646
1734
  constraints.push(orderBy('nameLower'));
1647
1735
 
1736
+ // Handle lastDoc cursor - supports both native DocumentSnapshot and serializable cursor
1648
1737
  if (filters.lastDoc) {
1649
- if (typeof filters.lastDoc.data === 'function') {
1650
- constraints.push(startAfter(filters.lastDoc));
1651
- } else if (Array.isArray(filters.lastDoc)) {
1652
- constraints.push(startAfter(...filters.lastDoc));
1653
- } else {
1654
- constraints.push(startAfter(filters.lastDoc));
1738
+ const cursorValues = this.getCursorValuesForStartAfter(filters.lastDoc, 'nameLower');
1739
+ if (cursorValues) {
1740
+ constraints.push(startAfter(...cursorValues));
1741
+ console.log('[PROCEDURE_SERVICE] Strategy 1: Using cursor for pagination');
1655
1742
  }
1656
1743
  }
1657
1744
  constraints.push(limit(filters.pagination || 10));
@@ -1679,14 +1766,13 @@ export class ProcedureService extends BaseService {
1679
1766
  return { procedures, lastDoc: null };
1680
1767
  }
1681
1768
 
1682
- // If we filtered out some procedures but got full query results, use last doc for pagination
1683
- // This allows fetching more pages to get enough filtered results
1684
- const lastDoc =
1685
- querySnapshot.docs.length > 0
1686
- ? querySnapshot.docs[querySnapshot.docs.length - 1]
1687
- : null;
1769
+ // Return serializable cursor for pagination
1770
+ const lastDocSnapshot = querySnapshot.docs.length > 0
1771
+ ? querySnapshot.docs[querySnapshot.docs.length - 1]
1772
+ : null;
1773
+ const serializableCursor = this.createSerializableCursor(lastDocSnapshot, 'nameLower');
1688
1774
 
1689
- return { procedures, lastDoc };
1775
+ return { procedures, lastDoc: serializableCursor };
1690
1776
  } catch (error) {
1691
1777
  console.log('[PROCEDURE_SERVICE] Strategy 1 failed:', error);
1692
1778
  }
@@ -1710,13 +1796,12 @@ export class ProcedureService extends BaseService {
1710
1796
  constraints.push(where('name', '<=', searchTerm + '\uf8ff'));
1711
1797
  constraints.push(orderBy('name'));
1712
1798
 
1799
+ // Handle lastDoc cursor - supports both native DocumentSnapshot and serializable cursor
1713
1800
  if (filters.lastDoc) {
1714
- if (typeof filters.lastDoc.data === 'function') {
1715
- constraints.push(startAfter(filters.lastDoc));
1716
- } else if (Array.isArray(filters.lastDoc)) {
1717
- constraints.push(startAfter(...filters.lastDoc));
1718
- } else {
1719
- constraints.push(startAfter(filters.lastDoc));
1801
+ const cursorValues = this.getCursorValuesForStartAfter(filters.lastDoc, 'name');
1802
+ if (cursorValues) {
1803
+ constraints.push(startAfter(...cursorValues));
1804
+ console.log('[PROCEDURE_SERVICE] Strategy 2: Using cursor for pagination');
1720
1805
  }
1721
1806
  }
1722
1807
  constraints.push(limit(filters.pagination || 10));
@@ -1744,13 +1829,13 @@ export class ProcedureService extends BaseService {
1744
1829
  return { procedures, lastDoc: null };
1745
1830
  }
1746
1831
 
1747
- // If we filtered out some procedures but got full query results, use last doc for pagination
1748
- const lastDoc =
1749
- querySnapshot.docs.length > 0
1750
- ? querySnapshot.docs[querySnapshot.docs.length - 1]
1751
- : null;
1832
+ // Return serializable cursor for pagination
1833
+ const lastDocSnapshot = querySnapshot.docs.length > 0
1834
+ ? querySnapshot.docs[querySnapshot.docs.length - 1]
1835
+ : null;
1836
+ const serializableCursor = this.createSerializableCursor(lastDocSnapshot, 'name');
1752
1837
 
1753
- return { procedures, lastDoc };
1838
+ return { procedures, lastDoc: serializableCursor };
1754
1839
  } catch (error) {
1755
1840
  console.log('[PROCEDURE_SERVICE] Strategy 2 failed:', error);
1756
1841
  }
@@ -1819,13 +1904,12 @@ export class ProcedureService extends BaseService {
1819
1904
  );
1820
1905
  constraints.push(orderBy('createdAt', 'desc'));
1821
1906
 
1907
+ // Handle lastDoc cursor - supports both native DocumentSnapshot and serializable cursor
1822
1908
  if (filters.lastDoc) {
1823
- if (typeof filters.lastDoc.data === 'function') {
1824
- constraints.push(startAfter(filters.lastDoc));
1825
- } else if (Array.isArray(filters.lastDoc)) {
1826
- constraints.push(startAfter(...filters.lastDoc));
1827
- } else {
1828
- constraints.push(startAfter(filters.lastDoc));
1909
+ const cursorValues = this.getCursorValuesForStartAfter(filters.lastDoc, 'createdAt');
1910
+ if (cursorValues) {
1911
+ constraints.push(startAfter(...cursorValues));
1912
+ console.log('[PROCEDURE_SERVICE] Strategy 3: Using cursor for pagination');
1829
1913
  }
1830
1914
  }
1831
1915
  constraints.push(limit(filters.pagination || 10));
@@ -1866,11 +1950,13 @@ export class ProcedureService extends BaseService {
1866
1950
  return { procedures, lastDoc: null };
1867
1951
  }
1868
1952
 
1869
- // If we filtered out some procedures but got full query results, use last doc for pagination
1870
- const lastDoc =
1871
- querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1953
+ // Return serializable cursor for pagination
1954
+ const lastDocSnapshot = querySnapshot.docs.length > 0
1955
+ ? querySnapshot.docs[querySnapshot.docs.length - 1]
1956
+ : null;
1957
+ const serializableCursor = this.createSerializableCursor(lastDocSnapshot, 'createdAt');
1872
1958
 
1873
- return { procedures, lastDoc };
1959
+ return { procedures, lastDoc: serializableCursor };
1874
1960
  } catch (error) {
1875
1961
  console.log('[PROCEDURE_SERVICE] Strategy 3 failed:', error);
1876
1962
  }
@@ -1888,6 +1974,15 @@ export class ProcedureService extends BaseService {
1888
1974
  if (filters.clinicId) {
1889
1975
  constraints.push(where('clinicBranchId', '==', filters.clinicId));
1890
1976
  }
1977
+
1978
+ // Handle lastDoc cursor - supports both native DocumentSnapshot and serializable cursor
1979
+ if (filters.lastDoc) {
1980
+ const cursorValues = this.getCursorValuesForStartAfter(filters.lastDoc, 'createdAt');
1981
+ if (cursorValues) {
1982
+ constraints.push(startAfter(...cursorValues));
1983
+ console.log('[PROCEDURE_SERVICE] Strategy 4: Using cursor for pagination');
1984
+ }
1985
+ }
1891
1986
  constraints.push(limit(filters.pagination || 10));
1892
1987
 
1893
1988
  const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
@@ -1911,11 +2006,13 @@ export class ProcedureService extends BaseService {
1911
2006
  return { procedures, lastDoc: null };
1912
2007
  }
1913
2008
 
1914
- // If we filtered out some procedures but got full query results, use last doc for pagination
1915
- const lastDoc =
1916
- querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
2009
+ // Return serializable cursor for pagination
2010
+ const lastDocSnapshot = querySnapshot.docs.length > 0
2011
+ ? querySnapshot.docs[querySnapshot.docs.length - 1]
2012
+ : null;
2013
+ const serializableCursor = this.createSerializableCursor(lastDocSnapshot, 'createdAt');
1917
2014
 
1918
- return { procedures, lastDoc };
2015
+ return { procedures, lastDoc: serializableCursor };
1919
2016
  } catch (error) {
1920
2017
  console.log('[PROCEDURE_SERVICE] Strategy 4 failed:', error);
1921
2018
  }