@blackcode_sa/metaestetics-api 1.5.28 → 1.5.29
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/admin/index.d.mts +1199 -1
- package/dist/admin/index.d.ts +1199 -1
- package/dist/admin/index.js +1337 -2
- package/dist/admin/index.mjs +1333 -2
- package/dist/backoffice/index.d.mts +99 -7
- package/dist/backoffice/index.d.ts +99 -7
- package/dist/index.d.mts +4035 -2364
- package/dist/index.d.ts +4035 -2364
- package/dist/index.js +2616 -1929
- package/dist/index.mjs +2646 -1952
- package/package.json +1 -1
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +642 -0
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -0
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -0
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +508 -0
- package/src/admin/index.ts +53 -4
- package/src/index.ts +28 -4
- package/src/services/clinic/clinic.service.ts +320 -107
- package/src/services/clinic/utils/clinic.utils.ts +66 -117
- package/src/services/clinic/utils/filter.utils.d.ts +23 -0
- package/src/services/clinic/utils/filter.utils.ts +264 -0
- package/src/services/practitioner/practitioner.service.ts +616 -5
- package/src/services/procedure/procedure.service.ts +599 -352
- package/src/services/reviews/reviews.service.ts +842 -0
- package/src/types/clinic/index.ts +24 -56
- package/src/types/practitioner/index.ts +34 -33
- package/src/types/procedure/index.ts +32 -0
- package/src/types/profile/index.ts +1 -1
- package/src/types/reviews/index.ts +126 -0
- package/src/validations/clinic.schema.ts +37 -64
- package/src/validations/practitioner.schema.ts +42 -32
- package/src/validations/procedure.schema.ts +11 -3
- package/src/validations/reviews.schema.ts +189 -0
- package/src/services/clinic/utils/review.utils.ts +0 -93
|
@@ -18,17 +18,19 @@ import {
|
|
|
18
18
|
Clinic,
|
|
19
19
|
CreateClinicData,
|
|
20
20
|
CLINICS_COLLECTION,
|
|
21
|
-
ClinicReview,
|
|
22
21
|
ClinicTag,
|
|
23
22
|
ClinicGroup,
|
|
24
23
|
ClinicBranchSetupData,
|
|
25
24
|
ClinicLocation,
|
|
26
25
|
} from "../../../types/clinic";
|
|
27
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
geohashForLocation,
|
|
28
|
+
distanceBetween,
|
|
29
|
+
geohashQueryBounds,
|
|
30
|
+
} from "geofire-common";
|
|
28
31
|
import {
|
|
29
32
|
clinicSchema,
|
|
30
33
|
createClinicSchema,
|
|
31
|
-
clinicReviewSchema,
|
|
32
34
|
} from "../../../validations/clinic.schema";
|
|
33
35
|
import { z } from "zod";
|
|
34
36
|
import { FirebaseApp } from "firebase/app";
|
|
@@ -273,11 +275,18 @@ export async function createClinic(
|
|
|
273
275
|
photosWithTags: processedPhotosWithTags,
|
|
274
276
|
doctors: [],
|
|
275
277
|
doctorsInfo: [],
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
procedures: [],
|
|
279
|
+
proceduresInfo: [],
|
|
280
|
+
reviewInfo: {
|
|
281
|
+
totalReviews: 0,
|
|
282
|
+
averageRating: 0,
|
|
283
|
+
cleanliness: 0,
|
|
284
|
+
facilities: 0,
|
|
285
|
+
staffFriendliness: 0,
|
|
286
|
+
waitingTime: 0,
|
|
287
|
+
accessibility: 0,
|
|
288
|
+
recommendationPercentage: 0,
|
|
289
|
+
},
|
|
281
290
|
admins: [creatorAdminId],
|
|
282
291
|
createdAt: now,
|
|
283
292
|
updatedAt: now,
|
|
@@ -860,141 +869,81 @@ export async function getAllClinics(
|
|
|
860
869
|
* @param rangeInKm - The range in kilometers to search within
|
|
861
870
|
* @param pagination - Optional number of clinics per page (0 or undefined returns all)
|
|
862
871
|
* @param lastDoc - Optional last document for pagination (if continuing from a previous page)
|
|
863
|
-
* @
|
|
864
|
-
* @returns Array of clinics within range and the last document for pagination
|
|
872
|
+
* @returns Array of clinics with distance information and the last document for pagination
|
|
865
873
|
*/
|
|
866
874
|
export async function getAllClinicsInRange(
|
|
867
875
|
db: Firestore,
|
|
868
876
|
center: { latitude: number; longitude: number },
|
|
869
877
|
rangeInKm: number,
|
|
870
878
|
pagination?: number,
|
|
871
|
-
lastDoc?: any
|
|
872
|
-
filters?: {
|
|
873
|
-
isActive?: boolean;
|
|
874
|
-
tags?: ClinicTag[];
|
|
875
|
-
}
|
|
879
|
+
lastDoc?: any
|
|
876
880
|
): Promise<{ clinics: (Clinic & { distance: number })[]; lastDoc: any }> {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
+
const bounds = geohashQueryBounds(
|
|
882
|
+
[center.latitude, center.longitude],
|
|
883
|
+
rangeInKm * 1000
|
|
884
|
+
);
|
|
881
885
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
let clinicsQuery = query(clinicsCollection);
|
|
886
|
+
const matchingClinics: (Clinic & { distance: number })[] = [];
|
|
887
|
+
let lastDocSnapshot = null;
|
|
885
888
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
889
|
+
for (const b of bounds) {
|
|
890
|
+
const constraints: QueryConstraint[] = [
|
|
891
|
+
where("location.geohash", ">=", b[0]),
|
|
892
|
+
where("location.geohash", "<=", b[1]),
|
|
893
|
+
where("isActive", "==", true),
|
|
894
|
+
];
|
|
893
895
|
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
`[CLINIC_UTILS] Found ${querySnapshot.docs.length} total clinics to filter by distance`
|
|
897
|
-
);
|
|
896
|
+
const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
|
|
897
|
+
const querySnapshot = await getDocs(q);
|
|
898
898
|
|
|
899
|
-
// Filter the results by distance
|
|
900
|
-
const filteredDocs = [];
|
|
901
899
|
for (const doc of querySnapshot.docs) {
|
|
902
900
|
const clinic = doc.data() as Clinic;
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (
|
|
906
|
-
!clinic.location ||
|
|
907
|
-
!clinic.location.latitude ||
|
|
908
|
-
!clinic.location.longitude
|
|
909
|
-
) {
|
|
910
|
-
continue;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Calculate distance
|
|
914
|
-
const distanceInM = distanceBetween(
|
|
915
|
-
[centerLat, centerLng],
|
|
901
|
+
const distance = distanceBetween(
|
|
902
|
+
[center.latitude, center.longitude],
|
|
916
903
|
[clinic.location.latitude, clinic.location.longitude]
|
|
917
904
|
);
|
|
918
905
|
|
|
919
|
-
// Convert to
|
|
920
|
-
const distanceInKm =
|
|
921
|
-
if (distanceInKm <= rangeInKm) {
|
|
922
|
-
// If tags filter exists, apply it
|
|
923
|
-
if (filters?.tags && filters.tags.length > 0) {
|
|
924
|
-
const hasAllTags = filters.tags.every((filterTag) =>
|
|
925
|
-
clinic.tags.some(
|
|
926
|
-
(clinicTag) =>
|
|
927
|
-
(filterTag as any).id === (clinicTag as any).id ||
|
|
928
|
-
(filterTag as any).name === (clinicTag as any).name
|
|
929
|
-
)
|
|
930
|
-
);
|
|
906
|
+
// Convert to kilometers
|
|
907
|
+
const distanceInKm = distance / 1000;
|
|
931
908
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
} else {
|
|
940
|
-
// Add distance to clinic object for reference
|
|
941
|
-
filteredDocs.push({
|
|
942
|
-
doc,
|
|
943
|
-
distance: distanceInKm,
|
|
944
|
-
});
|
|
945
|
-
}
|
|
909
|
+
if (distanceInKm <= rangeInKm) {
|
|
910
|
+
matchingClinics.push({
|
|
911
|
+
...clinic,
|
|
912
|
+
distance: distanceInKm,
|
|
913
|
+
});
|
|
946
914
|
}
|
|
947
915
|
}
|
|
916
|
+
}
|
|
948
917
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
);
|
|
952
|
-
|
|
953
|
-
// Sort results by distance
|
|
954
|
-
filteredDocs.sort((a, b) => a.distance - b.distance);
|
|
955
|
-
|
|
956
|
-
// Apply pagination if needed
|
|
957
|
-
let paginatedDocs = filteredDocs;
|
|
958
|
-
let lastVisible = null;
|
|
918
|
+
// Sort by distance
|
|
919
|
+
matchingClinics.sort((a, b) => a.distance - b.distance);
|
|
959
920
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
921
|
+
if (pagination && pagination > 0) {
|
|
922
|
+
// Paginate results
|
|
923
|
+
let result = matchingClinics;
|
|
924
|
+
if (lastDoc && matchingClinics.length > 0) {
|
|
925
|
+
const lastIndex = matchingClinics.findIndex(
|
|
926
|
+
(clinic) => clinic.id === lastDoc.id
|
|
927
|
+
);
|
|
928
|
+
if (lastIndex !== -1) {
|
|
929
|
+
result = matchingClinics.slice(lastIndex + 1);
|
|
970
930
|
}
|
|
971
|
-
|
|
972
|
-
// Get the paginated subset
|
|
973
|
-
paginatedDocs = filteredDocs.slice(startIndex, startIndex + pagination);
|
|
974
|
-
|
|
975
|
-
// Set the last document for the next pagination
|
|
976
|
-
lastVisible =
|
|
977
|
-
paginatedDocs.length > 0
|
|
978
|
-
? paginatedDocs[paginatedDocs.length - 1].doc
|
|
979
|
-
: null;
|
|
980
931
|
}
|
|
981
932
|
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
id: item.doc.id,
|
|
988
|
-
distance: item.distance, // Include distance in response
|
|
989
|
-
} as Clinic & { distance: number };
|
|
990
|
-
});
|
|
933
|
+
const paginatedClinics = result.slice(0, pagination);
|
|
934
|
+
const newLastDoc =
|
|
935
|
+
paginatedClinics.length > 0
|
|
936
|
+
? paginatedClinics[paginatedClinics.length - 1]
|
|
937
|
+
: null;
|
|
991
938
|
|
|
992
939
|
return {
|
|
993
|
-
clinics,
|
|
994
|
-
lastDoc:
|
|
940
|
+
clinics: paginatedClinics,
|
|
941
|
+
lastDoc: newLastDoc,
|
|
995
942
|
};
|
|
996
|
-
} catch (error) {
|
|
997
|
-
console.error("[CLINIC_UTILS] Error getting clinics in range:", error);
|
|
998
|
-
throw error;
|
|
999
943
|
}
|
|
944
|
+
|
|
945
|
+
return {
|
|
946
|
+
clinics: matchingClinics,
|
|
947
|
+
lastDoc: null,
|
|
948
|
+
};
|
|
1000
949
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Firestore } from "firebase/firestore";
|
|
2
|
+
import { Clinic, ClinicTag } from "../../../types/clinic";
|
|
3
|
+
|
|
4
|
+
export function getClinicsByFilters(
|
|
5
|
+
db: Firestore,
|
|
6
|
+
filters: {
|
|
7
|
+
center?: { latitude: number; longitude: number };
|
|
8
|
+
radiusInKm?: number;
|
|
9
|
+
tags?: ClinicTag[];
|
|
10
|
+
procedureFamily?: string;
|
|
11
|
+
procedureCategory?: string;
|
|
12
|
+
procedureSubcategory?: string;
|
|
13
|
+
procedureTechnology?: string;
|
|
14
|
+
minRating?: number;
|
|
15
|
+
maxRating?: number;
|
|
16
|
+
pagination?: number;
|
|
17
|
+
lastDoc?: any;
|
|
18
|
+
isActive?: boolean;
|
|
19
|
+
}
|
|
20
|
+
): Promise<{
|
|
21
|
+
clinics: (Clinic & { distance?: number })[];
|
|
22
|
+
lastDoc: any;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
query,
|
|
4
|
+
where,
|
|
5
|
+
getDocs,
|
|
6
|
+
Firestore,
|
|
7
|
+
QueryConstraint,
|
|
8
|
+
startAfter,
|
|
9
|
+
limit,
|
|
10
|
+
documentId,
|
|
11
|
+
orderBy,
|
|
12
|
+
} from "firebase/firestore";
|
|
13
|
+
import { Clinic, ClinicTag, CLINICS_COLLECTION } from "../../../types/clinic";
|
|
14
|
+
import { geohashQueryBounds, distanceBetween } from "geofire-common";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get clinics based on multiple filtering criteria
|
|
18
|
+
*
|
|
19
|
+
* @param db - Firestore database instance
|
|
20
|
+
* @param filters - Various filters to apply
|
|
21
|
+
* @returns Filtered clinics and the last document for pagination
|
|
22
|
+
*/
|
|
23
|
+
export async function getClinicsByFilters(
|
|
24
|
+
db: Firestore,
|
|
25
|
+
filters: {
|
|
26
|
+
center?: { latitude: number; longitude: number };
|
|
27
|
+
radiusInKm?: number;
|
|
28
|
+
tags?: ClinicTag[];
|
|
29
|
+
procedureFamily?: string;
|
|
30
|
+
procedureCategory?: string;
|
|
31
|
+
procedureSubcategory?: string;
|
|
32
|
+
procedureTechnology?: string;
|
|
33
|
+
minRating?: number;
|
|
34
|
+
maxRating?: number;
|
|
35
|
+
pagination?: number;
|
|
36
|
+
lastDoc?: any;
|
|
37
|
+
isActive?: boolean;
|
|
38
|
+
}
|
|
39
|
+
): Promise<{
|
|
40
|
+
clinics: (Clinic & { distance?: number })[];
|
|
41
|
+
lastDoc: any;
|
|
42
|
+
}> {
|
|
43
|
+
console.log(
|
|
44
|
+
"[FILTER_UTILS] Starting clinic filtering with criteria:",
|
|
45
|
+
filters
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Determine if we're doing a geo query or a regular query
|
|
49
|
+
const isGeoQuery =
|
|
50
|
+
filters.center && filters.radiusInKm && filters.radiusInKm > 0;
|
|
51
|
+
|
|
52
|
+
// Initialize base constraints
|
|
53
|
+
const constraints: QueryConstraint[] = [];
|
|
54
|
+
|
|
55
|
+
// Add active status filter (default to active if not specified)
|
|
56
|
+
if (filters.isActive !== undefined) {
|
|
57
|
+
constraints.push(where("isActive", "==", filters.isActive));
|
|
58
|
+
} else {
|
|
59
|
+
constraints.push(where("isActive", "==", true));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Add tag filtering if specified
|
|
63
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
64
|
+
// Note: We can only use array-contains-any once per query, so we're selecting one tag
|
|
65
|
+
// for the query and we'll filter the remaining tags in memory
|
|
66
|
+
constraints.push(where("tags", "array-contains", filters.tags[0]));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Hierarchical procedure filter - only apply the most specific one provided
|
|
70
|
+
// Order of specificity: technology > subcategory > category > family
|
|
71
|
+
if (filters.procedureTechnology) {
|
|
72
|
+
constraints.push(
|
|
73
|
+
where("servicesInfo.technology", "==", filters.procedureTechnology)
|
|
74
|
+
);
|
|
75
|
+
} else if (filters.procedureSubcategory) {
|
|
76
|
+
constraints.push(
|
|
77
|
+
where("servicesInfo.subCategory", "==", filters.procedureSubcategory)
|
|
78
|
+
);
|
|
79
|
+
} else if (filters.procedureCategory) {
|
|
80
|
+
constraints.push(
|
|
81
|
+
where("servicesInfo.category", "==", filters.procedureCategory)
|
|
82
|
+
);
|
|
83
|
+
} else if (filters.procedureFamily) {
|
|
84
|
+
constraints.push(
|
|
85
|
+
where("servicesInfo.procedureFamily", "==", filters.procedureFamily)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Add pagination if specified
|
|
90
|
+
if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
|
|
91
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
92
|
+
constraints.push(limit(filters.pagination));
|
|
93
|
+
} else if (filters.pagination && filters.pagination > 0) {
|
|
94
|
+
constraints.push(limit(filters.pagination));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Add ordering to make pagination consistent
|
|
98
|
+
constraints.push(orderBy(documentId()));
|
|
99
|
+
|
|
100
|
+
let clinicsResult: (Clinic & { distance?: number })[] = [];
|
|
101
|
+
let lastVisibleDoc = null;
|
|
102
|
+
|
|
103
|
+
// For geo queries, we need a different approach
|
|
104
|
+
if (isGeoQuery) {
|
|
105
|
+
const center = filters.center!;
|
|
106
|
+
const radiusInKm = filters.radiusInKm!;
|
|
107
|
+
|
|
108
|
+
// Get the geohash query bounds
|
|
109
|
+
const bounds = geohashQueryBounds(
|
|
110
|
+
[center.latitude, center.longitude],
|
|
111
|
+
radiusInKm * 1000 // Convert to meters
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Collect matching clinics from all bounds
|
|
115
|
+
const matchingClinics: (Clinic & { distance: number })[] = [];
|
|
116
|
+
|
|
117
|
+
// Execute queries for each bound
|
|
118
|
+
for (const bound of bounds) {
|
|
119
|
+
// Create a geo query for this bound
|
|
120
|
+
const geoConstraints = [
|
|
121
|
+
...constraints,
|
|
122
|
+
where("location.geohash", ">=", bound[0]),
|
|
123
|
+
where("location.geohash", "<=", bound[1]),
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const q = query(collection(db, CLINICS_COLLECTION), ...geoConstraints);
|
|
127
|
+
const querySnapshot = await getDocs(q);
|
|
128
|
+
|
|
129
|
+
console.log(
|
|
130
|
+
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Process results and filter by actual distance
|
|
134
|
+
for (const doc of querySnapshot.docs) {
|
|
135
|
+
const clinic = { ...doc.data(), id: doc.id } as Clinic;
|
|
136
|
+
|
|
137
|
+
// Calculate actual distance
|
|
138
|
+
const distance = distanceBetween(
|
|
139
|
+
[center.latitude, center.longitude],
|
|
140
|
+
[clinic.location.latitude, clinic.location.longitude]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Convert to kilometers
|
|
144
|
+
const distanceInKm = distance / 1000;
|
|
145
|
+
|
|
146
|
+
// Check if within radius
|
|
147
|
+
if (distanceInKm <= radiusInKm) {
|
|
148
|
+
// Add distance to clinic object
|
|
149
|
+
matchingClinics.push({
|
|
150
|
+
...clinic,
|
|
151
|
+
distance: distanceInKm,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Apply additional filters that couldn't be applied in the query
|
|
158
|
+
let filteredClinics = matchingClinics;
|
|
159
|
+
|
|
160
|
+
// Filter by multiple tags if more than one tag was specified
|
|
161
|
+
if (filters.tags && filters.tags.length > 1) {
|
|
162
|
+
filteredClinics = filteredClinics.filter((clinic) => {
|
|
163
|
+
// Check if clinic has all specified tags
|
|
164
|
+
return filters.tags!.every((tag) => clinic.tags.includes(tag));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Filter by rating
|
|
169
|
+
if (filters.minRating !== undefined) {
|
|
170
|
+
filteredClinics = filteredClinics.filter(
|
|
171
|
+
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating!
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (filters.maxRating !== undefined) {
|
|
176
|
+
filteredClinics = filteredClinics.filter(
|
|
177
|
+
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating!
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Sort by distance
|
|
182
|
+
filteredClinics.sort((a, b) => a.distance - b.distance);
|
|
183
|
+
|
|
184
|
+
// Apply pagination after all filters have been applied
|
|
185
|
+
if (filters.pagination && filters.pagination > 0) {
|
|
186
|
+
// If we have a lastDoc, find its index
|
|
187
|
+
let startIndex = 0;
|
|
188
|
+
if (filters.lastDoc) {
|
|
189
|
+
const lastDocIndex = filteredClinics.findIndex(
|
|
190
|
+
(clinic) => clinic.id === filters.lastDoc.id
|
|
191
|
+
);
|
|
192
|
+
if (lastDocIndex !== -1) {
|
|
193
|
+
startIndex = lastDocIndex + 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Get paginated subset
|
|
198
|
+
const paginatedClinics = filteredClinics.slice(
|
|
199
|
+
startIndex,
|
|
200
|
+
startIndex + filters.pagination
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Set last document for next pagination
|
|
204
|
+
lastVisibleDoc =
|
|
205
|
+
paginatedClinics.length > 0
|
|
206
|
+
? paginatedClinics[paginatedClinics.length - 1]
|
|
207
|
+
: null;
|
|
208
|
+
|
|
209
|
+
clinicsResult = paginatedClinics;
|
|
210
|
+
} else {
|
|
211
|
+
clinicsResult = filteredClinics;
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// For non-geo queries, execute a single query with all constraints
|
|
215
|
+
const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
|
|
216
|
+
const querySnapshot = await getDocs(q);
|
|
217
|
+
|
|
218
|
+
console.log(
|
|
219
|
+
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Convert docs to clinics
|
|
223
|
+
const clinics = querySnapshot.docs.map((doc) => {
|
|
224
|
+
return { ...doc.data(), id: doc.id } as Clinic;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Apply filters that couldn't be applied in the query
|
|
228
|
+
let filteredClinics = clinics;
|
|
229
|
+
|
|
230
|
+
// Filter by multiple tags if more than one tag was specified
|
|
231
|
+
if (filters.tags && filters.tags.length > 1) {
|
|
232
|
+
filteredClinics = filteredClinics.filter((clinic) => {
|
|
233
|
+
// Check if clinic has all specified tags
|
|
234
|
+
return filters.tags!.every((tag) => clinic.tags.includes(tag));
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Filter by rating
|
|
239
|
+
if (filters.minRating !== undefined) {
|
|
240
|
+
filteredClinics = filteredClinics.filter(
|
|
241
|
+
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating!
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (filters.maxRating !== undefined) {
|
|
246
|
+
filteredClinics = filteredClinics.filter(
|
|
247
|
+
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating!
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Set last document for pagination
|
|
252
|
+
lastVisibleDoc =
|
|
253
|
+
querySnapshot.docs.length > 0
|
|
254
|
+
? querySnapshot.docs[querySnapshot.docs.length - 1]
|
|
255
|
+
: null;
|
|
256
|
+
|
|
257
|
+
clinicsResult = filteredClinics;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
clinics: clinicsResult,
|
|
262
|
+
lastDoc: lastVisibleDoc,
|
|
263
|
+
};
|
|
264
|
+
}
|