@blackcode_sa/metaestetics-api 1.8.11 → 1.8.13

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.
package/dist/index.mjs CHANGED
@@ -6257,6 +6257,7 @@ var PractitionerService = class extends BaseService {
6257
6257
  trustworthiness: 0,
6258
6258
  recommendationPercentage: 0
6259
6259
  };
6260
+ const fullNameLower = `${validData.basicInfo.firstName} ${validData.basicInfo.lastName}`.toLowerCase();
6260
6261
  const practitioner = {
6261
6262
  id: practitionerId,
6262
6263
  userRef: validData.userRef,
@@ -6264,6 +6265,8 @@ var PractitionerService = class extends BaseService {
6264
6265
  validData.basicInfo,
6265
6266
  practitionerId
6266
6267
  ),
6268
+ fullNameLower,
6269
+ // Ensure this is present
6267
6270
  certification: validData.certification,
6268
6271
  clinics: validData.clinics || [],
6269
6272
  clinicWorkingHours: validData.clinicWorkingHours || [],
@@ -6359,6 +6362,7 @@ var PractitionerService = class extends BaseService {
6359
6362
  }
6360
6363
  const finalClinicsInfo = validatedData.clinicsInfo && validatedData.clinicsInfo.length > 0 ? validatedData.clinicsInfo : clinicsInfo;
6361
6364
  const proceduresInfo = [];
6365
+ const fullNameLowerDraft = `${validatedData.basicInfo.firstName} ${validatedData.basicInfo.lastName}`.toLowerCase();
6362
6366
  const practitionerData = {
6363
6367
  id: practitionerId,
6364
6368
  userRef: "",
@@ -6367,6 +6371,8 @@ var PractitionerService = class extends BaseService {
6367
6371
  validatedData.basicInfo,
6368
6372
  practitionerId
6369
6373
  ),
6374
+ fullNameLower: fullNameLowerDraft,
6375
+ // Ensure this is present
6370
6376
  certification: validatedData.certification,
6371
6377
  clinics,
6372
6378
  clinicWorkingHours: validatedData.clinicWorkingHours || [],
@@ -6872,10 +6878,6 @@ var PractitionerService = class extends BaseService {
6872
6878
  */
6873
6879
  async getPractitionersByFilters(filters) {
6874
6880
  try {
6875
- console.log(
6876
- "[PRACTITIONER_SERVICE] Starting practitioner filtering with criteria:",
6877
- filters
6878
- );
6879
6881
  const constraints = [];
6880
6882
  if (!filters.includeDraftPractitioners) {
6881
6883
  constraints.push(where10("status", "==", "active" /* ACTIVE */));
@@ -6890,60 +6892,40 @@ var PractitionerService = class extends BaseService {
6890
6892
  )
6891
6893
  );
6892
6894
  }
6893
- constraints.push(orderBy4("basicInfo.lastName", "asc"));
6894
- constraints.push(orderBy4("basicInfo.firstName", "asc"));
6895
- if (filters.pagination && filters.pagination > 0) {
6896
- if (filters.lastDoc) {
6897
- constraints.push(startAfter6(filters.lastDoc));
6898
- }
6899
- constraints.push(limit7(filters.pagination));
6895
+ if (filters.nameSearch && filters.nameSearch.trim()) {
6896
+ const searchTerm = filters.nameSearch.trim().toLowerCase();
6897
+ constraints.push(where10("fullNameLower", ">=", searchTerm));
6898
+ constraints.push(where10("fullNameLower", "<=", searchTerm + "\uF8FF"));
6900
6899
  }
6901
- const q = query10(
6902
- collection10(this.db, PRACTITIONERS_COLLECTION),
6903
- ...constraints
6904
- );
6905
- const querySnapshot = await getDocs10(q);
6906
- console.log(
6907
- `[PRACTITIONER_SERVICE] Found ${querySnapshot.docs.length} practitioners with base query`
6908
- );
6909
- let practitioners = querySnapshot.docs.map((doc37) => {
6910
- return { ...doc37.data(), id: doc37.id };
6911
- });
6912
- const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
6913
- if (filters.nameSearch && filters.nameSearch.trim() !== "") {
6914
- const searchTerm = filters.nameSearch.toLowerCase().trim();
6915
- practitioners = practitioners.filter((practitioner) => {
6916
- const fullName = `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`.toLowerCase();
6917
- return fullName.includes(searchTerm);
6918
- });
6900
+ if (filters.procedureTechnology) {
6901
+ constraints.push(where10("proceduresInfo.technologyName", "==", filters.procedureTechnology));
6902
+ } else if (filters.procedureSubcategory) {
6903
+ constraints.push(where10("proceduresInfo.subcategoryName", "==", filters.procedureSubcategory));
6904
+ } else if (filters.procedureCategory) {
6905
+ constraints.push(where10("proceduresInfo.categoryName", "==", filters.procedureCategory));
6906
+ } else if (filters.procedureFamily) {
6907
+ constraints.push(where10("proceduresInfo.family", "==", filters.procedureFamily));
6919
6908
  }
6920
- if (filters.specialties && filters.specialties.length > 0) {
6921
- practitioners = practitioners.filter((practitioner) => {
6922
- return filters.specialties.every(
6923
- (specialty) => practitioner.certification.specialties.includes(specialty)
6924
- );
6925
- });
6909
+ if (filters.minRating !== void 0) {
6910
+ constraints.push(where10("reviewInfo.averageRating", ">=", filters.minRating));
6926
6911
  }
6927
- if (filters.procedureTechnology || filters.procedureSubcategory || filters.procedureCategory || filters.procedureFamily) {
6928
- practitioners = practitioners.filter((practitioner) => {
6929
- const procedures = practitioner.proceduresInfo || [];
6930
- return procedures.some((procedure) => {
6931
- if (filters.procedureTechnology) {
6932
- return procedure.technologyName === filters.procedureTechnology;
6933
- }
6934
- if (filters.procedureSubcategory) {
6935
- return procedure.subcategoryName === filters.procedureSubcategory;
6936
- }
6937
- if (filters.procedureCategory) {
6938
- return procedure.categoryName === filters.procedureCategory;
6939
- }
6940
- if (filters.procedureFamily) {
6941
- return procedure.family === filters.procedureFamily;
6942
- }
6943
- return false;
6944
- });
6945
- });
6912
+ if (filters.maxRating !== void 0) {
6913
+ constraints.push(where10("reviewInfo.averageRating", "<=", filters.maxRating));
6914
+ }
6915
+ constraints.push(orderBy4("fullNameLower"));
6916
+ if (filters.lastDoc) {
6917
+ if (typeof filters.lastDoc.data === "function") {
6918
+ constraints.push(startAfter6(filters.lastDoc));
6919
+ } else if (Array.isArray(filters.lastDoc)) {
6920
+ constraints.push(startAfter6(...filters.lastDoc));
6921
+ } else {
6922
+ constraints.push(startAfter6(filters.lastDoc));
6923
+ }
6946
6924
  }
6925
+ constraints.push(limit7(filters.pagination || 5));
6926
+ const q = query10(collection10(this.db, PRACTITIONERS_COLLECTION), ...constraints);
6927
+ const querySnapshot = await getDocs10(q);
6928
+ let practitioners = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
6947
6929
  if (filters.location && filters.radiusInKm && filters.radiusInKm > 0) {
6948
6930
  const location = filters.location;
6949
6931
  const radiusInKm = filters.radiusInKm;
@@ -6959,22 +6941,7 @@ var PractitionerService = class extends BaseService {
6959
6941
  });
6960
6942
  });
6961
6943
  }
6962
- if (filters.minRating !== void 0) {
6963
- practitioners = practitioners.filter(
6964
- (p) => p.reviewInfo.averageRating >= filters.minRating
6965
- );
6966
- }
6967
- if (filters.maxRating !== void 0) {
6968
- practitioners = practitioners.filter(
6969
- (p) => p.reviewInfo.averageRating <= filters.maxRating
6970
- );
6971
- }
6972
- console.log(
6973
- `[PRACTITIONER_SERVICE] Filtered to ${practitioners.length} practitioners`
6974
- );
6975
- if (filters.pagination && filters.pagination > 0) {
6976
- practitioners = practitioners.slice(0, filters.pagination);
6977
- }
6944
+ const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
6978
6945
  return {
6979
6946
  practitioners,
6980
6947
  lastDoc
@@ -7039,6 +7006,7 @@ var PractitionerService = class extends BaseService {
7039
7006
  }
7040
7007
  const consultationData = {
7041
7008
  name: "Free Consultation",
7009
+ nameLower: "free consultation",
7042
7010
  description: "Free initial consultation to discuss treatment options and assess patient needs.",
7043
7011
  family: "aesthetics" /* AESTHETICS */,
7044
7012
  categoryId: "consultation",
@@ -8189,8 +8157,10 @@ var ClinicGroupService = class extends BaseService {
8189
8157
 
8190
8158
  // src/services/clinic/clinic.service.ts
8191
8159
  import {
8160
+ collection as collection16,
8192
8161
  doc as doc15,
8193
8162
  getDoc as getDoc18,
8163
+ getDocs as getDocs16,
8194
8164
  updateDoc as updateDoc15,
8195
8165
  serverTimestamp as serverTimestamp14,
8196
8166
  writeBatch as writeBatch4,
@@ -8664,170 +8634,66 @@ import {
8664
8634
  limit as limit9,
8665
8635
  orderBy as orderBy5
8666
8636
  } from "firebase/firestore";
8667
- import { geohashQueryBounds as geohashQueryBounds3, distanceBetween as distanceBetween4 } from "geofire-common";
8637
+ import { distanceBetween as distanceBetween4 } from "geofire-common";
8668
8638
  async function getClinicsByFilters(db, filters) {
8669
- console.log(
8670
- "[FILTER_UTILS] Starting clinic filtering with criteria:",
8671
- filters
8672
- );
8673
- const isGeoQuery = filters.center && filters.radiusInKm && filters.radiusInKm > 0;
8639
+ var _a;
8674
8640
  const constraints = [];
8675
- if (filters.isActive !== void 0) {
8676
- constraints.push(where15("isActive", "==", filters.isActive));
8677
- } else {
8678
- constraints.push(where15("isActive", "==", true));
8679
- }
8641
+ constraints.push(where15("isActive", "==", (_a = filters.isActive) != null ? _a : true));
8680
8642
  if (filters.tags && filters.tags.length > 0) {
8681
8643
  constraints.push(where15("tags", "array-contains", filters.tags[0]));
8682
8644
  }
8683
8645
  if (filters.procedureTechnology) {
8684
- constraints.push(
8685
- where15("servicesInfo.technology", "==", filters.procedureTechnology)
8686
- );
8646
+ constraints.push(where15("servicesInfo.technology", "==", filters.procedureTechnology));
8687
8647
  } else if (filters.procedureSubcategory) {
8688
- constraints.push(
8689
- where15("servicesInfo.subCategory", "==", filters.procedureSubcategory)
8690
- );
8648
+ constraints.push(where15("servicesInfo.subCategory", "==", filters.procedureSubcategory));
8691
8649
  } else if (filters.procedureCategory) {
8692
- constraints.push(
8693
- where15("servicesInfo.category", "==", filters.procedureCategory)
8694
- );
8650
+ constraints.push(where15("servicesInfo.category", "==", filters.procedureCategory));
8695
8651
  } else if (filters.procedureFamily) {
8696
- constraints.push(
8697
- where15("servicesInfo.procedureFamily", "==", filters.procedureFamily)
8698
- );
8699
- }
8700
- if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
8701
- constraints.push(startAfter8(filters.lastDoc));
8702
- constraints.push(limit9(filters.pagination));
8703
- } else if (filters.pagination && filters.pagination > 0) {
8704
- constraints.push(limit9(filters.pagination));
8705
- }
8706
- constraints.push(orderBy5("location.geohash"));
8707
- let clinicsResult = [];
8708
- let lastVisibleDoc = null;
8709
- if (isGeoQuery) {
8710
- const center = filters.center;
8711
- const radiusInKm = filters.radiusInKm;
8712
- const bounds = geohashQueryBounds3(
8713
- [center.latitude, center.longitude],
8714
- radiusInKm * 1e3
8715
- // Convert to meters
8716
- );
8717
- const matchingClinics = [];
8718
- for (const bound of bounds) {
8719
- const geoConstraints = [
8720
- ...constraints,
8721
- where15("location.geohash", ">=", bound[0]),
8722
- where15("location.geohash", "<=", bound[1])
8723
- ];
8724
- const q = query15(collection15(db, CLINICS_COLLECTION), ...geoConstraints);
8725
- const querySnapshot = await getDocs15(q);
8726
- console.log(
8727
- `[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
8728
- );
8729
- for (const doc37 of querySnapshot.docs) {
8730
- const clinic = { ...doc37.data(), id: doc37.id };
8731
- const distance = distanceBetween4(
8732
- [center.latitude, center.longitude],
8733
- [clinic.location.latitude, clinic.location.longitude]
8734
- );
8735
- const distanceInKm = distance / 1e3;
8736
- if (distanceInKm <= radiusInKm) {
8737
- matchingClinics.push({
8738
- ...clinic,
8739
- distance: distanceInKm
8740
- });
8741
- }
8742
- }
8743
- }
8744
- let filteredClinics = matchingClinics;
8745
- if (filters.tags && filters.tags.length > 1) {
8746
- filteredClinics = filteredClinics.filter((clinic) => {
8747
- return filters.tags.every((tag) => clinic.tags.includes(tag));
8748
- });
8749
- }
8750
- if (filters.minRating !== void 0) {
8751
- filteredClinics = filteredClinics.filter(
8752
- (clinic) => clinic.reviewInfo.averageRating >= filters.minRating
8753
- );
8754
- }
8755
- if (filters.maxRating !== void 0) {
8756
- filteredClinics = filteredClinics.filter(
8757
- (clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
8758
- );
8759
- }
8760
- filteredClinics.sort((a, b) => a.distance - b.distance);
8761
- if (filters.pagination && filters.pagination > 0) {
8762
- let startIndex = 0;
8763
- if (filters.lastDoc) {
8764
- const lastDocIndex = filteredClinics.findIndex(
8765
- (clinic) => clinic.id === filters.lastDoc.id
8766
- );
8767
- if (lastDocIndex !== -1) {
8768
- startIndex = lastDocIndex + 1;
8769
- }
8770
- }
8771
- const paginatedClinics = filteredClinics.slice(
8772
- startIndex,
8773
- startIndex + filters.pagination
8774
- );
8775
- lastVisibleDoc = paginatedClinics.length > 0 ? paginatedClinics[paginatedClinics.length - 1] : null;
8776
- clinicsResult = paginatedClinics;
8652
+ constraints.push(where15("servicesInfo.procedureFamily", "==", filters.procedureFamily));
8653
+ }
8654
+ let useNameLower = false;
8655
+ let searchTerm = "";
8656
+ if (filters.nameSearch && filters.nameSearch.trim()) {
8657
+ searchTerm = filters.nameSearch.trim().toLowerCase();
8658
+ constraints.push(where15("nameLower", ">=", searchTerm));
8659
+ constraints.push(where15("nameLower", "<=", searchTerm + "\uF8FF"));
8660
+ useNameLower = true;
8661
+ }
8662
+ if (filters.minRating !== void 0) {
8663
+ constraints.push(where15("reviewInfo.averageRating", ">=", filters.minRating));
8664
+ }
8665
+ if (filters.maxRating !== void 0) {
8666
+ constraints.push(where15("reviewInfo.averageRating", "<=", filters.maxRating));
8667
+ }
8668
+ constraints.push(orderBy5("nameLower"));
8669
+ if (filters.lastDoc) {
8670
+ if (typeof filters.lastDoc.data === "function") {
8671
+ constraints.push(startAfter8(filters.lastDoc));
8672
+ } else if (Array.isArray(filters.lastDoc)) {
8673
+ constraints.push(startAfter8(...filters.lastDoc));
8777
8674
  } else {
8778
- clinicsResult = filteredClinics;
8675
+ constraints.push(startAfter8(filters.lastDoc));
8779
8676
  }
8780
- } else {
8781
- const q = query15(collection15(db, CLINICS_COLLECTION), ...constraints);
8782
- const querySnapshot = await getDocs15(q);
8783
- console.log(
8784
- `[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
8785
- );
8786
- const clinics = querySnapshot.docs.map((doc37) => {
8787
- return { ...doc37.data(), id: doc37.id };
8677
+ }
8678
+ constraints.push(limit9(filters.pagination || 5));
8679
+ const q = query15(collection15(db, CLINICS_COLLECTION), ...constraints);
8680
+ const querySnapshot = await getDocs15(q);
8681
+ let clinics = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
8682
+ if (filters.tags && filters.tags.length > 1) {
8683
+ clinics = clinics.filter((clinic) => filters.tags.every((tag) => clinic.tags.includes(tag)));
8684
+ }
8685
+ if (filters.center && filters.radiusInKm) {
8686
+ clinics = clinics.filter((clinic) => {
8687
+ const distance = distanceBetween4(
8688
+ [filters.center.latitude, filters.center.longitude],
8689
+ [clinic.location.latitude, clinic.location.longitude]
8690
+ ) / 1e3;
8691
+ clinic.distance = distance;
8692
+ return distance <= filters.radiusInKm;
8788
8693
  });
8789
- let filteredClinics = clinics;
8790
- if (filters.center) {
8791
- const center = filters.center;
8792
- const clinicsWithDistance = [];
8793
- filteredClinics.forEach((clinic) => {
8794
- const distance = distanceBetween4(
8795
- [center.latitude, center.longitude],
8796
- [clinic.location.latitude, clinic.location.longitude]
8797
- );
8798
- clinicsWithDistance.push({
8799
- ...clinic,
8800
- distance: distance / 1e3
8801
- // Convert to kilometers
8802
- });
8803
- });
8804
- filteredClinics = clinicsWithDistance;
8805
- filteredClinics.sort(
8806
- (a, b) => a.distance - b.distance
8807
- );
8808
- }
8809
- if (filters.tags && filters.tags.length > 1) {
8810
- filteredClinics = filteredClinics.filter((clinic) => {
8811
- return filters.tags.every((tag) => clinic.tags.includes(tag));
8812
- });
8813
- }
8814
- if (filters.minRating !== void 0) {
8815
- filteredClinics = filteredClinics.filter(
8816
- (clinic) => clinic.reviewInfo.averageRating >= filters.minRating
8817
- );
8818
- }
8819
- if (filters.maxRating !== void 0) {
8820
- filteredClinics = filteredClinics.filter(
8821
- (clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
8822
- );
8823
- }
8824
- lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
8825
- clinicsResult = filteredClinics;
8826
8694
  }
8827
- return {
8828
- clinics: clinicsResult,
8829
- lastDoc: lastVisibleDoc
8830
- };
8695
+ const lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
8696
+ return { clinics, lastDoc: lastVisibleDoc };
8831
8697
  }
8832
8698
 
8833
8699
  // src/services/clinic/clinic.service.ts
@@ -8963,6 +8829,8 @@ var ClinicService = class extends BaseService {
8963
8829
  id: clinicId,
8964
8830
  clinicGroupId: validatedData.clinicGroupId,
8965
8831
  name: validatedData.name,
8832
+ nameLower: validatedData.name.toLowerCase(),
8833
+ // Add this line
8966
8834
  description: validatedData.description,
8967
8835
  location: { ...location, geohash: hash },
8968
8836
  contactInfo: validatedData.contactInfo,
@@ -9063,6 +8931,9 @@ var ClinicService = class extends BaseService {
9063
8931
  updatePayload[field] = validatedData[field];
9064
8932
  }
9065
8933
  }
8934
+ if (validatedData.name) {
8935
+ updatePayload.nameLower = validatedData.name.toLowerCase();
8936
+ }
9066
8937
  if (validatedData.location) {
9067
8938
  const loc = validatedData.location;
9068
8939
  updatePayload.location = {
@@ -9238,6 +9109,27 @@ var ClinicService = class extends BaseService {
9238
9109
  async getClinicsByFilters(filters) {
9239
9110
  return getClinicsByFilters(this.db, filters);
9240
9111
  }
9112
+ /**
9113
+ * Gets all clinics with minimal info for map display (id, name, address, latitude, longitude)
9114
+ * This is optimized for mobile map usage to reduce payload size.
9115
+ * @returns Array of minimal clinic info for map
9116
+ */
9117
+ async getClinicsForMap() {
9118
+ const clinicsRef = collection16(this.db, CLINICS_COLLECTION);
9119
+ const snapshot = await getDocs16(clinicsRef);
9120
+ const clinicsForMap = snapshot.docs.map((doc37) => {
9121
+ var _a, _b, _c;
9122
+ const data = doc37.data();
9123
+ return {
9124
+ id: doc37.id,
9125
+ name: data.name,
9126
+ address: ((_a = data.location) == null ? void 0 : _a.address) || "",
9127
+ latitude: (_b = data.location) == null ? void 0 : _b.latitude,
9128
+ longitude: (_c = data.location) == null ? void 0 : _c.longitude
9129
+ };
9130
+ });
9131
+ return clinicsForMap;
9132
+ }
9241
9133
  };
9242
9134
 
9243
9135
  // src/services/auth/utils/firebase.utils.ts
@@ -14568,6 +14460,7 @@ import {
14568
14460
  import { z as z24 } from "zod";
14569
14461
  var createProcedureSchema = z24.object({
14570
14462
  name: z24.string().min(1).max(200),
14463
+ nameLower: z24.string().min(1).max(200),
14571
14464
  description: z24.string().min(1).max(2e3),
14572
14465
  family: z24.nativeEnum(ProcedureFamily),
14573
14466
  categoryId: z24.string().min(1),
@@ -14585,6 +14478,7 @@ var createProcedureSchema = z24.object({
14585
14478
  });
14586
14479
  var updateProcedureSchema = z24.object({
14587
14480
  name: z24.string().min(3).max(100).optional(),
14481
+ nameLower: z24.string().min(1).max(200).optional(),
14588
14482
  description: z24.string().min(3).max(1e3).optional(),
14589
14483
  price: z24.number().min(0).optional(),
14590
14484
  currency: z24.nativeEnum(Currency).optional(),
@@ -14601,6 +14495,7 @@ var updateProcedureSchema = z24.object({
14601
14495
  });
14602
14496
  var procedureSchema = createProcedureSchema.extend({
14603
14497
  id: z24.string().min(1),
14498
+ nameLower: z24.string().min(1).max(200),
14604
14499
  category: z24.any(),
14605
14500
  // We'll validate the full category object separately
14606
14501
  subcategory: z24.any(),
@@ -14769,6 +14664,7 @@ var ProcedureService = class extends BaseService {
14769
14664
  const newProcedure = {
14770
14665
  id: procedureId,
14771
14666
  ...validatedData,
14667
+ nameLower: validatedData.nameLower || validatedData.name.toLowerCase(),
14772
14668
  photos: processedPhotos,
14773
14669
  category,
14774
14670
  // Embed full objects
@@ -14902,6 +14798,7 @@ var ProcedureService = class extends BaseService {
14902
14798
  const newProcedure = {
14903
14799
  id: procedureId,
14904
14800
  ...validatedData,
14801
+ nameLower: validatedData.nameLower || validatedData.name.toLowerCase(),
14905
14802
  practitionerId,
14906
14803
  // Override practitionerId with the correct one
14907
14804
  photos: processedPhotos,
@@ -15080,6 +14977,9 @@ var ProcedureService = class extends BaseService {
15080
14977
  };
15081
14978
  }
15082
14979
  let finalCategoryId = existingProcedure.category.id;
14980
+ if (validatedData.name) {
14981
+ updatedProcedureData.nameLower = validatedData.name.toLowerCase();
14982
+ }
15083
14983
  if (validatedData.categoryId) {
15084
14984
  const category = await this.categoryService.getById(
15085
14985
  validatedData.categoryId
@@ -15237,6 +15137,8 @@ var ProcedureService = class extends BaseService {
15237
15137
  /**
15238
15138
  * Searches and filters procedures based on multiple criteria
15239
15139
  *
15140
+ * @note Frontend MORA da šalje ceo snapshot (ili barem sva polja po kojima sortiraš, npr. nameLower) kao lastDoc za paginaciju, a ne samo id!
15141
+ *
15240
15142
  * @param filters - Various filters to apply
15241
15143
  * @param filters.nameSearch - Optional search text for procedure name
15242
15144
  * @param filters.treatmentBenefits - Optional array of treatment benefits to filter by
@@ -15257,10 +15159,6 @@ var ProcedureService = class extends BaseService {
15257
15159
  */
15258
15160
  async getProceduresByFilters(filters) {
15259
15161
  try {
15260
- console.log(
15261
- "[PROCEDURE_SERVICE] Starting procedure filtering with criteria:",
15262
- filters
15263
- );
15264
15162
  const isGeoQuery = filters.location && filters.radiusInKm && filters.radiusInKm > 0;
15265
15163
  const constraints = [];
15266
15164
  if (filters.isActive !== void 0) {
@@ -15271,15 +15169,53 @@ var ProcedureService = class extends BaseService {
15271
15169
  if (filters.procedureFamily) {
15272
15170
  constraints.push(where29("family", "==", filters.procedureFamily));
15273
15171
  }
15274
- constraints.push(orderBy17("clinicInfo.location.geohash"));
15275
- if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
15276
- constraints.push(startAfter13(filters.lastDoc));
15277
- constraints.push(limit15(filters.pagination));
15278
- } else if (filters.pagination && filters.pagination > 0) {
15172
+ if (filters.procedureCategory) {
15173
+ constraints.push(where29("category.id", "==", filters.procedureCategory));
15174
+ }
15175
+ if (filters.procedureSubcategory) {
15176
+ constraints.push(where29("subcategory.id", "==", filters.procedureSubcategory));
15177
+ }
15178
+ if (filters.procedureTechnology) {
15179
+ constraints.push(where29("technology.id", "==", filters.procedureTechnology));
15180
+ }
15181
+ if (filters.minPrice !== void 0) {
15182
+ constraints.push(where29("price", ">=", filters.minPrice));
15183
+ }
15184
+ if (filters.maxPrice !== void 0) {
15185
+ constraints.push(where29("price", "<=", filters.maxPrice));
15186
+ }
15187
+ if (filters.minRating !== void 0) {
15188
+ constraints.push(where29("reviewInfo.averageRating", ">=", filters.minRating));
15189
+ }
15190
+ if (filters.maxRating !== void 0) {
15191
+ constraints.push(where29("reviewInfo.averageRating", "<=", filters.maxRating));
15192
+ }
15193
+ if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
15194
+ constraints.push(where29("treatmentBenefits", "array-contains-any", filters.treatmentBenefits));
15195
+ }
15196
+ let useNameLower = false;
15197
+ let searchTerm = "";
15198
+ if (filters.nameSearch && filters.nameSearch.trim() !== "") {
15199
+ searchTerm = filters.nameSearch.trim().toLowerCase();
15200
+ useNameLower = true;
15201
+ constraints.push(where29("nameLower", ">=", searchTerm));
15202
+ constraints.push(where29("nameLower", "<=", searchTerm + "\uF8FF"));
15203
+ constraints.push(orderBy17("nameLower"));
15204
+ } else {
15205
+ constraints.push(orderBy17("nameLower"));
15206
+ }
15207
+ if (filters.lastDoc) {
15208
+ if (typeof filters.lastDoc.data === "function") {
15209
+ constraints.push(startAfter13(filters.lastDoc));
15210
+ } else if (Array.isArray(filters.lastDoc)) {
15211
+ constraints.push(startAfter13(...filters.lastDoc));
15212
+ } else {
15213
+ constraints.push(startAfter13(filters.lastDoc));
15214
+ }
15215
+ }
15216
+ if (filters.pagination && filters.pagination > 0) {
15279
15217
  constraints.push(limit15(filters.pagination));
15280
15218
  }
15281
- let proceduresResult = [];
15282
- let lastVisibleDoc = null;
15283
15219
  if (isGeoQuery) {
15284
15220
  const center = filters.location;
15285
15221
  const radiusInKm = filters.radiusInKm;
@@ -15288,178 +15224,63 @@ var ProcedureService = class extends BaseService {
15288
15224
  radiusInKm * 1e3
15289
15225
  // Convert to meters
15290
15226
  );
15291
- const matchingProcedures = [];
15227
+ let allDocs = [];
15292
15228
  for (const bound of bounds) {
15293
15229
  const geoConstraints = [
15294
- ...constraints,
15230
+ ...constraints.filter((c) => !c.fieldPath || c.fieldPath !== "name"),
15231
+ // Remove name orderBy for geo
15295
15232
  where29("clinicInfo.location.geohash", ">=", bound[0]),
15296
- where29("clinicInfo.location.geohash", "<=", bound[1])
15233
+ where29("clinicInfo.location.geohash", "<=", bound[1]),
15234
+ orderBy17("clinicInfo.location.geohash")
15297
15235
  ];
15298
- const q = query29(
15299
- collection29(this.db, PROCEDURES_COLLECTION),
15300
- ...geoConstraints
15301
- );
15236
+ const q = query29(collection29(this.db, PROCEDURES_COLLECTION), ...geoConstraints);
15302
15237
  const querySnapshot = await getDocs29(q);
15303
- console.log(
15304
- `[PROCEDURE_SERVICE] Found ${querySnapshot.docs.length} procedures in geo bound`
15305
- );
15306
15238
  for (const doc37 of querySnapshot.docs) {
15307
15239
  const procedure = { ...doc37.data(), id: doc37.id };
15308
15240
  const distance = distanceBetween6(
15309
15241
  [center.latitude, center.longitude],
15310
- [
15311
- procedure.clinicInfo.location.latitude,
15312
- procedure.clinicInfo.location.longitude
15313
- ]
15242
+ [procedure.clinicInfo.location.latitude, procedure.clinicInfo.location.longitude]
15314
15243
  );
15315
15244
  const distanceInKm = distance / 1e3;
15316
15245
  if (distanceInKm <= radiusInKm) {
15317
- matchingProcedures.push({
15318
- ...procedure,
15319
- distance: distanceInKm
15320
- });
15246
+ allDocs.push({ ...procedure, distance: distanceInKm });
15321
15247
  }
15322
15248
  }
15323
15249
  }
15324
- let filteredProcedures = matchingProcedures;
15325
- filteredProcedures = this.applyInMemoryFilters(
15326
- filteredProcedures,
15327
- filters
15328
- );
15329
- filteredProcedures.sort((a, b) => a.distance - b.distance);
15250
+ allDocs.sort((a, b) => a.distance - b.distance);
15251
+ let paginated = allDocs;
15330
15252
  if (filters.pagination && filters.pagination > 0) {
15331
15253
  let startIndex = 0;
15332
15254
  if (filters.lastDoc) {
15333
- const lastDocIndex = filteredProcedures.findIndex(
15334
- (procedure) => procedure.id === filters.lastDoc.id
15335
- );
15336
- if (lastDocIndex !== -1) {
15337
- startIndex = lastDocIndex + 1;
15338
- }
15255
+ const lastDocIndex = allDocs.findIndex((p) => p.id === filters.lastDoc.id);
15256
+ if (lastDocIndex !== -1) startIndex = lastDocIndex + 1;
15339
15257
  }
15340
- const paginatedProcedures = filteredProcedures.slice(
15341
- startIndex,
15342
- startIndex + filters.pagination
15343
- );
15344
- lastVisibleDoc = paginatedProcedures.length > 0 ? paginatedProcedures[paginatedProcedures.length - 1] : null;
15345
- proceduresResult = paginatedProcedures;
15346
- } else {
15347
- proceduresResult = filteredProcedures;
15258
+ paginated = allDocs.slice(startIndex, startIndex + filters.pagination);
15348
15259
  }
15260
+ const lastVisibleDoc = paginated.length > 0 ? paginated[paginated.length - 1] : null;
15261
+ return { procedures: paginated, lastDoc: lastVisibleDoc };
15349
15262
  } else {
15350
- const q = query29(
15351
- collection29(this.db, PROCEDURES_COLLECTION),
15352
- ...constraints
15353
- );
15354
- const querySnapshot = await getDocs29(q);
15355
- console.log(
15356
- `[PROCEDURE_SERVICE] Found ${querySnapshot.docs.length} procedures with regular query`
15357
- );
15358
- const procedures = querySnapshot.docs.map((doc37) => {
15359
- return { ...doc37.data(), id: doc37.id };
15360
- });
15361
- if (filters.location) {
15362
- const center = filters.location;
15363
- const proceduresWithDistance = [];
15364
- procedures.forEach((procedure) => {
15365
- const distance = distanceBetween6(
15366
- [center.latitude, center.longitude],
15367
- [
15368
- procedure.clinicInfo.location.latitude,
15369
- procedure.clinicInfo.location.longitude
15370
- ]
15371
- );
15372
- proceduresWithDistance.push({
15373
- ...procedure,
15374
- distance: distance / 1e3
15375
- // Convert to kilometers
15376
- });
15377
- });
15378
- let filteredProcedures = proceduresWithDistance;
15379
- filteredProcedures = this.applyInMemoryFilters(
15380
- filteredProcedures,
15381
- filters
15382
- );
15383
- filteredProcedures.sort((a, b) => a.distance - b.distance);
15384
- proceduresResult = filteredProcedures;
15385
- } else {
15386
- let filteredProcedures = this.applyInMemoryFilters(
15387
- procedures,
15388
- filters
15389
- );
15390
- proceduresResult = filteredProcedures;
15263
+ let q = query29(collection29(this.db, PROCEDURES_COLLECTION), ...constraints);
15264
+ let querySnapshot = await getDocs29(q);
15265
+ if (useNameLower && querySnapshot.empty && searchTerm) {
15266
+ constraints.pop();
15267
+ constraints.pop();
15268
+ constraints.pop();
15269
+ constraints.push(where29("name", ">=", searchTerm));
15270
+ constraints.push(where29("name", "<=", searchTerm + "\uF8FF"));
15271
+ constraints.push(orderBy17("name"));
15272
+ q = query29(collection29(this.db, PROCEDURES_COLLECTION), ...constraints);
15273
+ querySnapshot = await getDocs29(q);
15391
15274
  }
15392
- lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
15275
+ const procedures = querySnapshot.docs.map((doc37) => ({ ...doc37.data(), id: doc37.id }));
15276
+ const lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
15277
+ return { procedures, lastDoc: lastVisibleDoc };
15393
15278
  }
15394
- return {
15395
- procedures: proceduresResult,
15396
- lastDoc: lastVisibleDoc
15397
- };
15398
15279
  } catch (error) {
15399
15280
  console.error("[PROCEDURE_SERVICE] Error filtering procedures:", error);
15400
15281
  throw error;
15401
15282
  }
15402
15283
  }
15403
- /**
15404
- * Helper method to apply in-memory filters to procedures
15405
- * Used by getProceduresByFilters to apply filters that can't be done in Firestore queries
15406
- *
15407
- * @param procedures - The procedures to filter
15408
- * @param filters - The filters to apply
15409
- * @returns Filtered procedures
15410
- */
15411
- applyInMemoryFilters(procedures, filters) {
15412
- let filteredProcedures = procedures;
15413
- if (filters.nameSearch && filters.nameSearch.trim() !== "") {
15414
- const searchTerm = filters.nameSearch.toLowerCase().trim();
15415
- filteredProcedures = filteredProcedures.filter((procedure) => {
15416
- return procedure.name.toLowerCase().includes(searchTerm);
15417
- });
15418
- }
15419
- if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
15420
- filteredProcedures = filteredProcedures.filter((procedure) => {
15421
- return filters.treatmentBenefits.every(
15422
- (benefit) => procedure.treatmentBenefits.includes(benefit)
15423
- );
15424
- });
15425
- }
15426
- if (filters.procedureCategory) {
15427
- filteredProcedures = filteredProcedures.filter(
15428
- (procedure) => procedure.category.id === filters.procedureCategory
15429
- );
15430
- }
15431
- if (filters.procedureSubcategory) {
15432
- filteredProcedures = filteredProcedures.filter(
15433
- (procedure) => procedure.subcategory.id === filters.procedureSubcategory
15434
- );
15435
- }
15436
- if (filters.procedureTechnology) {
15437
- filteredProcedures = filteredProcedures.filter(
15438
- (procedure) => procedure.technology.id === filters.procedureTechnology
15439
- );
15440
- }
15441
- if (filters.minPrice !== void 0) {
15442
- filteredProcedures = filteredProcedures.filter(
15443
- (procedure) => procedure.price >= filters.minPrice
15444
- );
15445
- }
15446
- if (filters.maxPrice !== void 0) {
15447
- filteredProcedures = filteredProcedures.filter(
15448
- (procedure) => procedure.price <= filters.maxPrice
15449
- );
15450
- }
15451
- if (filters.minRating !== void 0) {
15452
- filteredProcedures = filteredProcedures.filter(
15453
- (procedure) => procedure.reviewInfo.averageRating >= filters.minRating
15454
- );
15455
- }
15456
- if (filters.maxRating !== void 0) {
15457
- filteredProcedures = filteredProcedures.filter(
15458
- (procedure) => procedure.reviewInfo.averageRating <= filters.maxRating
15459
- );
15460
- }
15461
- return filteredProcedures;
15462
- }
15463
15284
  /**
15464
15285
  * Creates a consultation procedure without requiring a product
15465
15286
  * This is a special method for consultation procedures that don't use products
@@ -15532,6 +15353,7 @@ var ProcedureService = class extends BaseService {
15532
15353
  const newProcedure = {
15533
15354
  id: procedureId,
15534
15355
  ...data,
15356
+ nameLower: data.nameLower || data.name.toLowerCase(),
15535
15357
  photos: processedPhotos,
15536
15358
  category,
15537
15359
  subcategory,
@@ -15568,6 +15390,29 @@ var ProcedureService = class extends BaseService {
15568
15390
  const savedDoc = await getDoc32(procedureRef);
15569
15391
  return savedDoc.data();
15570
15392
  }
15393
+ /**
15394
+ * Gets all procedures with minimal info for map display (id, name, clinicId, clinicName, address, latitude, longitude)
15395
+ * This is optimized for mobile map usage to reduce payload size.
15396
+ * @returns Array of minimal procedure info for map
15397
+ */
15398
+ async getProceduresForMap() {
15399
+ const proceduresRef = collection29(this.db, PROCEDURES_COLLECTION);
15400
+ const snapshot = await getDocs29(proceduresRef);
15401
+ const proceduresForMap = snapshot.docs.map((doc37) => {
15402
+ var _a, _b, _c, _d, _e, _f, _g, _h;
15403
+ const data = doc37.data();
15404
+ return {
15405
+ id: doc37.id,
15406
+ name: data.name,
15407
+ clinicId: (_a = data.clinicInfo) == null ? void 0 : _a.id,
15408
+ clinicName: (_b = data.clinicInfo) == null ? void 0 : _b.name,
15409
+ address: ((_d = (_c = data.clinicInfo) == null ? void 0 : _c.location) == null ? void 0 : _d.address) || "",
15410
+ latitude: (_f = (_e = data.clinicInfo) == null ? void 0 : _e.location) == null ? void 0 : _f.latitude,
15411
+ longitude: (_h = (_g = data.clinicInfo) == null ? void 0 : _g.location) == null ? void 0 : _h.longitude
15412
+ };
15413
+ });
15414
+ return proceduresForMap;
15415
+ }
15571
15416
  };
15572
15417
 
15573
15418
  // src/services/reviews/reviews.service.ts