@blackcode_sa/metaestetics-api 1.8.18 β†’ 1.10.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.
@@ -9,9 +9,9 @@ import {
9
9
  limit,
10
10
  documentId,
11
11
  orderBy,
12
- } from "firebase/firestore";
13
- import { Clinic, ClinicTag, CLINICS_COLLECTION } from "../../../types/clinic";
14
- import { geohashQueryBounds, distanceBetween } from "geofire-common";
12
+ } from 'firebase/firestore';
13
+ import { Clinic, ClinicTag, CLINICS_COLLECTION } from '../../../types/clinic';
14
+ import { geohashQueryBounds, distanceBetween } from 'geofire-common';
15
15
 
16
16
  /**
17
17
  * Get clinics based on multiple filtering criteria with fallback strategies
@@ -36,22 +36,22 @@ export async function getClinicsByFilters(
36
36
  pagination?: number;
37
37
  lastDoc?: any;
38
38
  isActive?: boolean;
39
- }
39
+ },
40
40
  ): Promise<{
41
41
  clinics: (Clinic & { distance?: number })[];
42
42
  lastDoc: any;
43
43
  }> {
44
44
  try {
45
- console.log("[CLINIC_SERVICE] Starting clinic filtering with multiple strategies");
46
-
45
+ console.log('[CLINIC_SERVICE] Starting clinic filtering with multiple strategies');
46
+
47
47
  // Geo query debug i validacija
48
48
  if (filters.center && filters.radiusInKm) {
49
49
  console.log('[CLINIC_SERVICE] Executing geo query:', {
50
50
  center: filters.center,
51
51
  radius: filters.radiusInKm,
52
- serviceName: 'ClinicService'
52
+ serviceName: 'ClinicService',
53
53
  });
54
-
54
+
55
55
  // Validacija location podataka
56
56
  if (!filters.center.latitude || !filters.center.longitude) {
57
57
  console.warn('[CLINIC_SERVICE] Invalid location data:', filters.center);
@@ -60,39 +60,95 @@ export async function getClinicsByFilters(
60
60
  }
61
61
  }
62
62
 
63
+ // Strategy 0: Geohash-bounds prefilter + in-memory refinement when geo radius is provided
64
+ if (filters.center && filters.radiusInKm) {
65
+ try {
66
+ console.log('[CLINIC_SERVICE] Strategy 0: Geohash bounds prefilter');
67
+ const bounds = geohashQueryBounds(
68
+ [filters.center.latitude, filters.center.longitude],
69
+ (filters.radiusInKm || 0) * 1000,
70
+ );
71
+
72
+ const collected: Clinic[] = [];
73
+ for (const b of bounds) {
74
+ const constraints: QueryConstraint[] = [
75
+ where('location.geohash', '>=', b[0]),
76
+ where('location.geohash', '<=', b[1]),
77
+ where('isActive', '==', filters.isActive ?? true),
78
+ ];
79
+
80
+ // Single tag in query if provided; remaining tag logic is applied in-memory
81
+ if (filters.tags && filters.tags.length > 0) {
82
+ constraints.push(where('tags', 'array-contains', filters.tags[0]));
83
+ }
84
+
85
+ const q0 = query(collection(db, CLINICS_COLLECTION), ...constraints);
86
+ const snap = await getDocs(q0);
87
+ snap.docs.forEach((d) => collected.push({ ...(d.data() as Clinic), id: d.id }));
88
+ }
89
+
90
+ // Deduplicate
91
+ const uniqueMap = new Map<string, Clinic>();
92
+ for (const c of collected) {
93
+ uniqueMap.set(c.id, c);
94
+ }
95
+ let clinics = Array.from(uniqueMap.values());
96
+
97
+ // Apply all remaining filters and compute exact distance + sorting
98
+ clinics = applyInMemoryFilters(clinics, filters);
99
+
100
+ // Manual pagination over materialized results
101
+ const pageSize = filters.pagination || 5;
102
+ let startIndex = 0;
103
+ if (filters.lastDoc && typeof filters.lastDoc === 'object' && (filters.lastDoc as any).id) {
104
+ const idx = clinics.findIndex((c) => c.id === (filters.lastDoc as any).id);
105
+ if (idx >= 0) startIndex = idx + 1;
106
+ }
107
+ const page = clinics.slice(startIndex, startIndex + pageSize);
108
+ const newLastDoc = page.length === pageSize ? page[page.length - 1] : null;
109
+
110
+ console.log(
111
+ `[CLINIC_SERVICE] Strategy 0 success: ${page.length} clinics (of ${clinics.length})`,
112
+ );
113
+ return { clinics: page, lastDoc: newLastDoc };
114
+ } catch (geoErr) {
115
+ console.log('[CLINIC_SERVICE] Strategy 0 failed:', geoErr);
116
+ }
117
+ }
118
+
63
119
  // Base constraints function (used in all strategies)
64
120
  const getBaseConstraints = () => {
65
121
  const constraints: QueryConstraint[] = [];
66
- constraints.push(where("isActive", "==", filters.isActive ?? true));
67
-
122
+ constraints.push(where('isActive', '==', filters.isActive ?? true));
123
+
68
124
  // Tag (only first, due to Firestore limitation)
69
125
  if (filters.tags && filters.tags.length > 0) {
70
- constraints.push(where("tags", "array-contains", filters.tags[0]));
126
+ constraints.push(where('tags', 'array-contains', filters.tags[0]));
71
127
  }
72
-
128
+
73
129
  // Rating filters
74
130
  if (filters.minRating !== undefined) {
75
- constraints.push(where("reviewInfo.averageRating", ">=", filters.minRating));
131
+ constraints.push(where('reviewInfo.averageRating', '>=', filters.minRating));
76
132
  }
77
133
  if (filters.maxRating !== undefined) {
78
- constraints.push(where("reviewInfo.averageRating", "<=", filters.maxRating));
134
+ constraints.push(where('reviewInfo.averageRating', '<=', filters.maxRating));
79
135
  }
80
-
136
+
81
137
  return constraints;
82
138
  };
83
139
 
84
140
  // Strategy 1: Try nameLower search if nameSearch exists
85
141
  if (filters.nameSearch && filters.nameSearch.trim()) {
86
142
  try {
87
- console.log("[CLINIC_SERVICE] Strategy 1: Trying nameLower search");
143
+ console.log('[CLINIC_SERVICE] Strategy 1: Trying nameLower search');
88
144
  const searchTerm = filters.nameSearch.trim().toLowerCase();
89
145
  const constraints = getBaseConstraints();
90
- constraints.push(where("nameLower", ">=", searchTerm));
91
- constraints.push(where("nameLower", "<=", searchTerm + "\uf8ff"));
92
- constraints.push(orderBy("nameLower"));
146
+ constraints.push(where('nameLower', '>=', searchTerm));
147
+ constraints.push(where('nameLower', '<=', searchTerm + '\uf8ff'));
148
+ constraints.push(orderBy('nameLower'));
93
149
 
94
150
  if (filters.lastDoc) {
95
- if (typeof filters.lastDoc.data === "function") {
151
+ if (typeof filters.lastDoc.data === 'function') {
96
152
  constraints.push(startAfter(filters.lastDoc));
97
153
  } else if (Array.isArray(filters.lastDoc)) {
98
154
  constraints.push(startAfter(...filters.lastDoc));
@@ -104,37 +160,38 @@ export async function getClinicsByFilters(
104
160
 
105
161
  const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
106
162
  const querySnapshot = await getDocs(q);
107
- let clinics = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Clinic));
108
-
163
+ let clinics = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id } as Clinic));
164
+
109
165
  // Apply in-memory filters
110
166
  clinics = applyInMemoryFilters(clinics, filters);
111
-
112
- const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
113
-
167
+
168
+ const lastDoc =
169
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
170
+
114
171
  console.log(`[CLINIC_SERVICE] Strategy 1 success: ${clinics.length} clinics`);
115
-
172
+
116
173
  // Fix Load More - ako je broj rezultata manji od pagination, nema viΕ‘e
117
174
  if (clinics.length < (filters.pagination || 5)) {
118
175
  return { clinics, lastDoc: null };
119
176
  }
120
177
  return { clinics, lastDoc };
121
178
  } catch (error) {
122
- console.log("[CLINIC_SERVICE] Strategy 1 failed:", error);
179
+ console.log('[CLINIC_SERVICE] Strategy 1 failed:', error);
123
180
  }
124
181
  }
125
182
 
126
183
  // Strategy 2: Try name field search as fallback
127
184
  if (filters.nameSearch && filters.nameSearch.trim()) {
128
185
  try {
129
- console.log("[CLINIC_SERVICE] Strategy 2: Trying name field search");
186
+ console.log('[CLINIC_SERVICE] Strategy 2: Trying name field search');
130
187
  const searchTerm = filters.nameSearch.trim().toLowerCase();
131
188
  const constraints = getBaseConstraints();
132
- constraints.push(where("name", ">=", searchTerm));
133
- constraints.push(where("name", "<=", searchTerm + "\uf8ff"));
134
- constraints.push(orderBy("name"));
189
+ constraints.push(where('name', '>=', searchTerm));
190
+ constraints.push(where('name', '<=', searchTerm + '\uf8ff'));
191
+ constraints.push(orderBy('name'));
135
192
 
136
193
  if (filters.lastDoc) {
137
- if (typeof filters.lastDoc.data === "function") {
194
+ if (typeof filters.lastDoc.data === 'function') {
138
195
  constraints.push(startAfter(filters.lastDoc));
139
196
  } else if (Array.isArray(filters.lastDoc)) {
140
197
  constraints.push(startAfter(...filters.lastDoc));
@@ -146,33 +203,36 @@ export async function getClinicsByFilters(
146
203
 
147
204
  const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
148
205
  const querySnapshot = await getDocs(q);
149
- let clinics = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Clinic));
150
-
206
+ let clinics = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id } as Clinic));
207
+
151
208
  // Apply in-memory filters
152
209
  clinics = applyInMemoryFilters(clinics, filters);
153
-
154
- const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
155
-
210
+
211
+ const lastDoc =
212
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
213
+
156
214
  console.log(`[CLINIC_SERVICE] Strategy 2 success: ${clinics.length} clinics`);
157
-
215
+
158
216
  // Fix Load More - ako je broj rezultata manji od pagination, nema viΕ‘e
159
217
  if (clinics.length < (filters.pagination || 5)) {
160
218
  return { clinics, lastDoc: null };
161
219
  }
162
220
  return { clinics, lastDoc };
163
221
  } catch (error) {
164
- console.log("[CLINIC_SERVICE] Strategy 2 failed:", error);
222
+ console.log('[CLINIC_SERVICE] Strategy 2 failed:', error);
165
223
  }
166
224
  }
167
225
 
168
226
  // Strategy 3: createdAt ordering with client-side name filtering
169
227
  try {
170
- console.log("[CLINIC_SERVICE] Strategy 3: Using createdAt ordering with client-side filtering");
228
+ console.log(
229
+ '[CLINIC_SERVICE] Strategy 3: Using createdAt ordering with client-side filtering',
230
+ );
171
231
  const constraints = getBaseConstraints();
172
- constraints.push(orderBy("createdAt", "desc"));
232
+ constraints.push(orderBy('createdAt', 'desc'));
173
233
 
174
234
  if (filters.lastDoc) {
175
- if (typeof filters.lastDoc.data === "function") {
235
+ if (typeof filters.lastDoc.data === 'function') {
176
236
  constraints.push(startAfter(filters.lastDoc));
177
237
  } else if (Array.isArray(filters.lastDoc)) {
178
238
  constraints.push(startAfter(...filters.lastDoc));
@@ -184,59 +244,60 @@ export async function getClinicsByFilters(
184
244
 
185
245
  const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
186
246
  const querySnapshot = await getDocs(q);
187
- let clinics = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Clinic));
247
+ let clinics = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id } as Clinic));
188
248
 
189
249
  // Apply in-memory filters (includes name search)
190
250
  clinics = applyInMemoryFilters(clinics, filters);
191
-
192
- const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
193
-
251
+
252
+ const lastDoc =
253
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
254
+
194
255
  console.log(`[CLINIC_SERVICE] Strategy 3 success: ${clinics.length} clinics`);
195
-
256
+
196
257
  // Fix Load More - ako je broj rezultata manji od pagination, nema viΕ‘e
197
258
  if (clinics.length < (filters.pagination || 5)) {
198
259
  return { clinics, lastDoc: null };
199
260
  }
200
261
  return { clinics, lastDoc };
201
262
  } catch (error) {
202
- console.log("[CLINIC_SERVICE] Strategy 3 failed:", error);
263
+ console.log('[CLINIC_SERVICE] Strategy 3 failed:', error);
203
264
  }
204
265
 
205
266
  // Strategy 4: Minimal fallback query
206
267
  try {
207
- console.log("[CLINIC_SERVICE] Strategy 4: Minimal fallback");
268
+ console.log('[CLINIC_SERVICE] Strategy 4: Minimal fallback');
208
269
  const constraints: QueryConstraint[] = [
209
- where("isActive", "==", true),
210
- orderBy("createdAt", "desc"),
211
- limit(filters.pagination || 5)
270
+ where('isActive', '==', true),
271
+ orderBy('createdAt', 'desc'),
272
+ limit(filters.pagination || 5),
212
273
  ];
213
274
 
214
275
  const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
215
276
  const querySnapshot = await getDocs(q);
216
- let clinics = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Clinic));
277
+ let clinics = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id } as Clinic));
217
278
 
218
279
  // Apply in-memory filters (includes name search)
219
280
  clinics = applyInMemoryFilters(clinics, filters);
220
-
221
- const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
222
-
281
+
282
+ const lastDoc =
283
+ querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
284
+
223
285
  console.log(`[CLINIC_SERVICE] Strategy 4 success: ${clinics.length} clinics`);
224
-
286
+
225
287
  // Fix Load More - ako je broj rezultata manji od pagination, nema viΕ‘e
226
288
  if (clinics.length < (filters.pagination || 5)) {
227
289
  return { clinics, lastDoc: null };
228
290
  }
229
291
  return { clinics, lastDoc };
230
292
  } catch (error) {
231
- console.log("[CLINIC_SERVICE] Strategy 4 failed:", error);
293
+ console.log('[CLINIC_SERVICE] Strategy 4 failed:', error);
232
294
  }
233
295
 
234
296
  // All strategies failed
235
- console.log("[CLINIC_SERVICE] All strategies failed, returning empty result");
297
+ console.log('[CLINIC_SERVICE] All strategies failed, returning empty result');
236
298
  return { clinics: [], lastDoc: null };
237
-
238
299
  } catch (error) {
239
- console.error("[CLINIC_SERVICE] Error filtering clinics:", error);
300
+ console.error('[CLINIC_SERVICE] Error filtering clinics:', error);
240
301
  return { clinics: [], lastDoc: null };
241
302
  }
242
303
  }
@@ -247,112 +308,135 @@ export async function getClinicsByFilters(
247
308
  function applyInMemoryFilters(clinics: Clinic[], filters: any): (Clinic & { distance?: number })[] {
248
309
  let filteredClinics = [...clinics]; // Kreiraj kopiju
249
310
 
250
- console.log(`[CLINIC_SERVICE] Applying in-memory filters - input: ${filteredClinics.length} clinics`);
251
-
311
+ console.log(
312
+ `[CLINIC_SERVICE] Applying in-memory filters - input: ${filteredClinics.length} clinics`,
313
+ );
314
+
252
315
  // βœ… Multi-tag filter (postojeΔ‡i kod)
253
316
  if (filters.tags && filters.tags.length > 1) {
254
317
  const initialCount = filteredClinics.length;
255
- filteredClinics = filteredClinics.filter(clinic =>
256
- filters.tags.every((tag: ClinicTag) => clinic.tags.includes(tag))
318
+ filteredClinics = filteredClinics.filter((clinic) =>
319
+ filters.tags.every((tag: ClinicTag) => clinic.tags.includes(tag)),
320
+ );
321
+ console.log(
322
+ `[CLINIC_SERVICE] Applied multi-tag filter: ${initialCount} β†’ ${filteredClinics.length}`,
257
323
  );
258
- console.log(`[CLINIC_SERVICE] Applied multi-tag filter: ${initialCount} β†’ ${filteredClinics.length}`);
259
324
  }
260
325
 
261
326
  // πŸ†• DODAJTE: Single tag filter (za Strategy 4 fallback)
262
327
  if (filters.tags && filters.tags.length === 1) {
263
328
  const initialCount = filteredClinics.length;
264
- filteredClinics = filteredClinics.filter(clinic =>
265
- clinic.tags.includes(filters.tags[0])
329
+ filteredClinics = filteredClinics.filter((clinic) => clinic.tags.includes(filters.tags[0]));
330
+ console.log(
331
+ `[CLINIC_SERVICE] Applied single-tag filter: ${initialCount} β†’ ${filteredClinics.length}`,
266
332
  );
267
- console.log(`[CLINIC_SERVICE] Applied single-tag filter: ${initialCount} β†’ ${filteredClinics.length}`);
268
333
  }
269
334
 
270
335
  // πŸ†• DODAJTE: Rating filter
271
336
  if (filters.minRating !== undefined || filters.maxRating !== undefined) {
272
337
  const initialCount = filteredClinics.length;
273
- filteredClinics = filteredClinics.filter(clinic => {
338
+ filteredClinics = filteredClinics.filter((clinic) => {
274
339
  const rating = clinic.reviewInfo?.averageRating || 0;
275
340
  if (filters.minRating !== undefined && rating < filters.minRating) return false;
276
341
  if (filters.maxRating !== undefined && rating > filters.maxRating) return false;
277
342
  return true;
278
343
  });
279
- console.log(`[CLINIC_SERVICE] Applied rating filter (${filters.minRating}-${filters.maxRating}): ${initialCount} β†’ ${filteredClinics.length}`);
344
+ console.log(
345
+ `[CLINIC_SERVICE] Applied rating filter (${filters.minRating}-${filters.maxRating}): ${initialCount} β†’ ${filteredClinics.length}`,
346
+ );
280
347
  }
281
348
 
282
349
  // πŸ†• DODAJTE: Name search filter
283
350
  if (filters.nameSearch && filters.nameSearch.trim()) {
284
351
  const initialCount = filteredClinics.length;
285
352
  const searchTerm = filters.nameSearch.trim().toLowerCase();
286
- filteredClinics = filteredClinics.filter(clinic => {
353
+ filteredClinics = filteredClinics.filter((clinic) => {
287
354
  const name = (clinic.name || '').toLowerCase();
288
355
  const nameLower = clinic.nameLower || '';
289
356
  return name.includes(searchTerm) || nameLower.includes(searchTerm);
290
357
  });
291
- console.log(`[CLINIC_SERVICE] Applied name search filter: ${initialCount} β†’ ${filteredClinics.length}`);
358
+ console.log(
359
+ `[CLINIC_SERVICE] Applied name search filter: ${initialCount} β†’ ${filteredClinics.length}`,
360
+ );
292
361
  }
293
362
 
294
363
  // πŸ†• DODAJTE: Procedure family filtering
295
364
  if (filters.procedureFamily) {
296
365
  const initialCount = filteredClinics.length;
297
- filteredClinics = filteredClinics.filter(clinic => {
366
+ filteredClinics = filteredClinics.filter((clinic) => {
298
367
  const proceduresInfo = clinic.proceduresInfo || [];
299
- return proceduresInfo.some(proc => proc.family === filters.procedureFamily);
368
+ return proceduresInfo.some((proc) => proc.family === filters.procedureFamily);
300
369
  });
301
- console.log(`[CLINIC_SERVICE] Applied procedure family filter: ${initialCount} β†’ ${filteredClinics.length}`);
370
+ console.log(
371
+ `[CLINIC_SERVICE] Applied procedure family filter: ${initialCount} β†’ ${filteredClinics.length}`,
372
+ );
302
373
  }
303
374
 
304
375
  // πŸ†• DODAJTE: Procedure category filtering
305
376
  if (filters.procedureCategory) {
306
377
  const initialCount = filteredClinics.length;
307
- filteredClinics = filteredClinics.filter(clinic => {
378
+ filteredClinics = filteredClinics.filter((clinic) => {
308
379
  const proceduresInfo = clinic.proceduresInfo || [];
309
- return proceduresInfo.some(proc => proc.categoryName === filters.procedureCategory);
380
+ return proceduresInfo.some((proc) => proc.categoryName === filters.procedureCategory);
310
381
  });
311
- console.log(`[CLINIC_SERVICE] Applied procedure category filter: ${initialCount} β†’ ${filteredClinics.length}`);
382
+ console.log(
383
+ `[CLINIC_SERVICE] Applied procedure category filter: ${initialCount} β†’ ${filteredClinics.length}`,
384
+ );
312
385
  }
313
386
 
314
387
  // πŸ†• DODAJTE: Procedure subcategory filtering
315
388
  if (filters.procedureSubcategory) {
316
389
  const initialCount = filteredClinics.length;
317
- filteredClinics = filteredClinics.filter(clinic => {
390
+ filteredClinics = filteredClinics.filter((clinic) => {
318
391
  const proceduresInfo = clinic.proceduresInfo || [];
319
- return proceduresInfo.some(proc => proc.subcategoryName === filters.procedureSubcategory);
392
+ return proceduresInfo.some((proc) => proc.subcategoryName === filters.procedureSubcategory);
320
393
  });
321
- console.log(`[CLINIC_SERVICE] Applied procedure subcategory filter: ${initialCount} β†’ ${filteredClinics.length}`);
394
+ console.log(
395
+ `[CLINIC_SERVICE] Applied procedure subcategory filter: ${initialCount} β†’ ${filteredClinics.length}`,
396
+ );
322
397
  }
323
398
 
324
399
  // πŸ†• DODAJTE: Procedure technology filtering
325
400
  if (filters.procedureTechnology) {
326
401
  const initialCount = filteredClinics.length;
327
- filteredClinics = filteredClinics.filter(clinic => {
402
+ filteredClinics = filteredClinics.filter((clinic) => {
328
403
  const proceduresInfo = clinic.proceduresInfo || [];
329
- return proceduresInfo.some(proc => proc.technologyName === filters.procedureTechnology);
404
+ return proceduresInfo.some((proc) => proc.technologyName === filters.procedureTechnology);
330
405
  });
331
- console.log(`[CLINIC_SERVICE] Applied procedure technology filter: ${initialCount} β†’ ${filteredClinics.length}`);
406
+ console.log(
407
+ `[CLINIC_SERVICE] Applied procedure technology filter: ${initialCount} β†’ ${filteredClinics.length}`,
408
+ );
332
409
  }
333
410
 
334
411
  // βœ… Geo-radius filter (postojeΔ‡i kod)
335
412
  if (filters.center && filters.radiusInKm) {
336
413
  const initialCount = filteredClinics.length;
337
- filteredClinics = filteredClinics.filter(clinic => {
414
+ filteredClinics = filteredClinics.filter((clinic) => {
338
415
  if (!clinic.location?.latitude || !clinic.location?.longitude) {
339
416
  console.log(`[CLINIC_SERVICE] Clinic ${clinic.id} missing location data`);
340
417
  return false;
341
418
  }
342
-
343
- const distance = distanceBetween(
344
- [filters.center.latitude, filters.center.longitude],
345
- [clinic.location.latitude, clinic.location.longitude]
346
- ) / 1000; // Convert to km
347
-
348
- console.log(`[CLINIC_SERVICE] Clinic ${clinic.name}: distance ${distance.toFixed(2)}km (limit: ${filters.radiusInKm}km)`);
349
-
419
+
420
+ const distance =
421
+ distanceBetween(
422
+ [filters.center.latitude, filters.center.longitude],
423
+ [clinic.location.latitude, clinic.location.longitude],
424
+ ) / 1000; // Convert to km
425
+
426
+ console.log(
427
+ `[CLINIC_SERVICE] Clinic ${clinic.name}: distance ${distance.toFixed(2)}km (limit: ${
428
+ filters.radiusInKm
429
+ }km)`,
430
+ );
431
+
350
432
  // Attach distance for frontend sorting/display
351
433
  (clinic as any).distance = distance;
352
434
  return distance <= filters.radiusInKm;
353
435
  });
354
- console.log(`[CLINIC_SERVICE] Applied geo filter (${filters.radiusInKm}km): ${initialCount} β†’ ${filteredClinics.length}`);
355
-
436
+ console.log(
437
+ `[CLINIC_SERVICE] Applied geo filter (${filters.radiusInKm}km): ${initialCount} β†’ ${filteredClinics.length}`,
438
+ );
439
+
356
440
  // Sort by distance when geo filtering is applied
357
441
  filteredClinics.sort((a, b) => ((a as any).distance || 0) - ((b as any).distance || 0));
358
442
  }