@blackcode_sa/metaestetics-api 1.12.2 → 1.12.3

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
@@ -16829,6 +16829,26 @@ var CategoryService = class extends BaseService {
16829
16829
  })
16830
16830
  );
16831
16831
  }
16832
+ /**
16833
+ * Vraća sve kategorije za određenu familiju za potrebe filtera (bez paginacije)
16834
+ * @param family - Familija procedura (aesthetics/surgery)
16835
+ * @returns Lista aktivnih kategorija koje pripadaju traženoj familiji
16836
+ */
16837
+ async getAllForFilterByFamily(family) {
16838
+ const q = query32(
16839
+ this.categoriesRef,
16840
+ where32("family", "==", family),
16841
+ where32("isActive", "==", true),
16842
+ orderBy19("name")
16843
+ );
16844
+ const snapshot = await getDocs32(q);
16845
+ return snapshot.docs.map(
16846
+ (doc38) => ({
16847
+ id: doc38.id,
16848
+ ...doc38.data()
16849
+ })
16850
+ );
16851
+ }
16832
16852
  /**
16833
16853
  * Vraća sve kategorije sa paginacijom
16834
16854
  * @param options - Pagination and filter options
@@ -17492,7 +17512,9 @@ var TechnologyService = class extends BaseService {
17492
17512
  if (!technology) {
17493
17513
  throw new Error(`Technology with id ${technologyId} not found`);
17494
17514
  }
17495
- const updatedContraindications = (technology.contraindications || []).filter((c) => c.id !== contraindication.id);
17515
+ const updatedContraindications = (technology.contraindications || []).filter(
17516
+ (c) => c.id !== contraindication.id
17517
+ );
17496
17518
  await updateDoc33(docRef, {
17497
17519
  contraindications: updatedContraindications,
17498
17520
  updatedAt: /* @__PURE__ */ new Date()
@@ -17513,9 +17535,7 @@ var TechnologyService = class extends BaseService {
17513
17535
  throw new Error(`Technology with id ${technologyId} not found`);
17514
17536
  }
17515
17537
  const contraindications = technology.contraindications || [];
17516
- const index = contraindications.findIndex(
17517
- (c) => c.id === contraindication.id
17518
- );
17538
+ const index = contraindications.findIndex((c) => c.id === contraindication.id);
17519
17539
  if (index === -1) {
17520
17540
  console.warn(
17521
17541
  `Contraindication with id ${contraindication.id} not found for technology ${technologyId}. No update performed.`
@@ -17564,9 +17584,7 @@ var TechnologyService = class extends BaseService {
17564
17584
  if (!technology) {
17565
17585
  throw new Error(`Technology with id ${technologyId} not found`);
17566
17586
  }
17567
- const updatedBenefits = (technology.benefits || []).filter(
17568
- (b) => b.id !== benefit.id
17569
- );
17587
+ const updatedBenefits = (technology.benefits || []).filter((b) => b.id !== benefit.id);
17570
17588
  await updateDoc33(docRef, {
17571
17589
  benefits: updatedBenefits,
17572
17590
  updatedAt: /* @__PURE__ */ new Date()
@@ -17672,9 +17690,7 @@ var TechnologyService = class extends BaseService {
17672
17690
  * );
17673
17691
  */
17674
17692
  validateCertification(requiredCertification, practitionerCertification) {
17675
- const doctorLevel = Object.values(CertificationLevel).indexOf(
17676
- practitionerCertification.level
17677
- );
17693
+ const doctorLevel = Object.values(CertificationLevel).indexOf(practitionerCertification.level);
17678
17694
  const requiredLevel = Object.values(CertificationLevel).indexOf(
17679
17695
  requiredCertification.minimumLevel
17680
17696
  );
@@ -17715,18 +17731,11 @@ var TechnologyService = class extends BaseService {
17715
17731
  async getAllowedTechnologies(practitioner) {
17716
17732
  const allTechnologies = await this.getAll();
17717
17733
  const allowedTechnologies = allTechnologies.technologies.filter(
17718
- (technology) => this.validateCertification(
17719
- technology.certificationRequirement,
17720
- practitioner.certification
17721
- )
17734
+ (technology) => this.validateCertification(technology.certificationRequirement, practitioner.certification)
17722
17735
  );
17723
17736
  const families = [...new Set(allowedTechnologies.map((t) => t.family))];
17724
- const categories = [
17725
- ...new Set(allowedTechnologies.map((t) => t.categoryId))
17726
- ];
17727
- const subcategories = [
17728
- ...new Set(allowedTechnologies.map((t) => t.subcategoryId))
17729
- ];
17737
+ const categories = [...new Set(allowedTechnologies.map((t) => t.categoryId))];
17738
+ const subcategories = [...new Set(allowedTechnologies.map((t) => t.subcategoryId))];
17730
17739
  return {
17731
17740
  technologies: allowedTechnologies,
17732
17741
  families,
@@ -17734,6 +17743,25 @@ var TechnologyService = class extends BaseService {
17734
17743
  subcategories
17735
17744
  };
17736
17745
  }
17746
+ /**
17747
+ * Gets all active technologies for a subcategory for filter dropdowns (by subcategory only).
17748
+ * @param subcategoryId - The ID of the subcategory.
17749
+ */
17750
+ async getAllForFilterBySubcategory(subcategoryId) {
17751
+ const q = query34(
17752
+ collection34(this.db, TECHNOLOGIES_COLLECTION),
17753
+ where34("isActive", "==", true),
17754
+ where34("subcategoryId", "==", subcategoryId),
17755
+ orderBy21("name")
17756
+ );
17757
+ const snapshot = await getDocs34(q);
17758
+ return snapshot.docs.map(
17759
+ (doc38) => ({
17760
+ id: doc38.id,
17761
+ ...doc38.data()
17762
+ })
17763
+ );
17764
+ }
17737
17765
  /**
17738
17766
  * Gets all active technologies for a subcategory for filter dropdowns.
17739
17767
  * @param categoryId - The ID of the parent category.
@@ -17802,12 +17830,7 @@ var ProductService = class extends BaseService {
17802
17830
  * @returns Firestore collection reference
17803
17831
  */
17804
17832
  getProductsRef(technologyId) {
17805
- return collection35(
17806
- this.db,
17807
- TECHNOLOGIES_COLLECTION,
17808
- technologyId,
17809
- PRODUCTS_COLLECTION
17810
- );
17833
+ return collection35(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
17811
17834
  }
17812
17835
  /**
17813
17836
  * Creates a new product under technology
@@ -17822,10 +17845,7 @@ var ProductService = class extends BaseService {
17822
17845
  updatedAt: now,
17823
17846
  isActive: true
17824
17847
  };
17825
- const productRef = await addDoc7(
17826
- this.getProductsRef(technologyId),
17827
- newProduct
17828
- );
17848
+ const productRef = await addDoc7(this.getProductsRef(technologyId), newProduct);
17829
17849
  return { id: productRef.id, ...newProduct };
17830
17850
  }
17831
17851
  /**
@@ -17833,17 +17853,8 @@ var ProductService = class extends BaseService {
17833
17853
  * This uses a collectionGroup query to search across all technologies.
17834
17854
  */
17835
17855
  async getAll(options) {
17836
- const {
17837
- rowsPerPage,
17838
- lastVisible,
17839
- categoryId,
17840
- subcategoryId,
17841
- technologyId
17842
- } = options;
17843
- const constraints = [
17844
- where35("isActive", "==", true),
17845
- orderBy22("name")
17846
- ];
17856
+ const { rowsPerPage, lastVisible, categoryId, subcategoryId, technologyId } = options;
17857
+ const constraints = [where35("isActive", "==", true), orderBy22("name")];
17847
17858
  if (categoryId) {
17848
17859
  constraints.push(where35("categoryId", "==", categoryId));
17849
17860
  }
@@ -17857,10 +17868,7 @@ var ProductService = class extends BaseService {
17857
17868
  constraints.push(startAfter18(lastVisible));
17858
17869
  }
17859
17870
  constraints.push(limit20(rowsPerPage));
17860
- const q = query35(
17861
- collectionGroup3(this.db, PRODUCTS_COLLECTION),
17862
- ...constraints
17863
- );
17871
+ const q = query35(collectionGroup3(this.db, PRODUCTS_COLLECTION), ...constraints);
17864
17872
  const snapshot = await getDocs35(q);
17865
17873
  const products = snapshot.docs.map(
17866
17874
  (doc38) => ({
@@ -17886,10 +17894,7 @@ var ProductService = class extends BaseService {
17886
17894
  if (technologyId) {
17887
17895
  constraints.push(where35("technologyId", "==", technologyId));
17888
17896
  }
17889
- const q = query35(
17890
- collectionGroup3(this.db, PRODUCTS_COLLECTION),
17891
- ...constraints
17892
- );
17897
+ const q = query35(collectionGroup3(this.db, PRODUCTS_COLLECTION), ...constraints);
17893
17898
  const snapshot = await getCountFromServer6(q);
17894
17899
  return snapshot.data().count;
17895
17900
  }
@@ -17898,10 +17903,7 @@ var ProductService = class extends BaseService {
17898
17903
  * This uses a single collectionGroup query for efficiency.
17899
17904
  */
17900
17905
  async getProductCounts() {
17901
- const q = query35(
17902
- collectionGroup3(this.db, PRODUCTS_COLLECTION),
17903
- where35("isActive", "==", true)
17904
- );
17906
+ const q = query35(collectionGroup3(this.db, PRODUCTS_COLLECTION), where35("isActive", "==", true));
17905
17907
  const snapshot = await getDocs35(q);
17906
17908
  const counts = {
17907
17909
  byCategory: {},
@@ -17926,6 +17928,23 @@ var ProductService = class extends BaseService {
17926
17928
  });
17927
17929
  return counts;
17928
17930
  }
17931
+ /**
17932
+ * Gets all products for a specific technology (non-paginated, for filters/dropdowns)
17933
+ */
17934
+ async getAllByTechnology(technologyId) {
17935
+ const q = query35(
17936
+ this.getProductsRef(technologyId),
17937
+ where35("isActive", "==", true),
17938
+ orderBy22("name")
17939
+ );
17940
+ const snapshot = await getDocs35(q);
17941
+ return snapshot.docs.map(
17942
+ (doc38) => ({
17943
+ id: doc38.id,
17944
+ ...doc38.data()
17945
+ })
17946
+ );
17947
+ }
17929
17948
  /**
17930
17949
  * Gets all products for a brand by filtering through all technologies
17931
17950
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.12.2",
4
+ "version": "1.12.3",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -0,0 +1,102 @@
1
+ # Service Method Fixes for Procedure Creation
2
+
3
+ ## Problem
4
+ The procedure creation components (`CreateProcedureBulk.tsx`, `CreateProcedure.tsx`, `UpdateProcedure.tsx`) were not loading categories and subcategories because the service methods were returning paginated objects instead of arrays.
5
+
6
+ ## Root Cause
7
+ The procedure service was calling paginated methods that return objects like `{ categories, lastVisible }` instead of filter methods that return arrays like `Category[]`.
8
+
9
+ ## Changes Made
10
+
11
+ ### 1. Added Missing Methods
12
+
13
+ #### CategoryService
14
+ - **Added**: `getAllForFilterByFamily(family: ProcedureFamily): Promise<Category[]>`
15
+ - **Added**: Interface `ICategoryService` in `category.types.ts`
16
+ - **Modified**: `CategoryService` now implements `ICategoryService`
17
+
18
+ #### TechnologyService
19
+ - **Added**: `getAllForFilterBySubcategory(subcategoryId: string): Promise<Technology[]>`
20
+ - **Modified**: Added method to `ITechnologyService` interface
21
+ - **Modified**: `TechnologyService` implements `ITechnologyService`
22
+
23
+ #### ProductService
24
+ - **Added**: `getAllByTechnology(technologyId: string): Promise<Product[]>`
25
+ - **Modified**: Added method to `IProductService` interface
26
+
27
+ ### 2. Updated Procedure Service Calls
28
+
29
+ Changed from paginated methods to filter methods:
30
+
31
+ ```typescript
32
+ // Before (returned { categories, lastVisible })
33
+ await getCategoryService().getAllByFamily(family)
34
+
35
+ // After (returns Category[])
36
+ await getCategoryService().getAllForFilterByFamily(family)
37
+ ```
38
+
39
+ ```typescript
40
+ // Before (returned { subcategories, lastVisible })
41
+ await getSubcategoryService().getAllByCategoryId(id)
42
+
43
+ // After (returns Subcategory[])
44
+ await getSubcategoryService().getAllForFilterByCategoryId(id)
45
+ ```
46
+
47
+ ```typescript
48
+ // Before (returned { technologies, lastVisible })
49
+ await getTechnologyService().getAllBySubcategoryId(id)
50
+
51
+ // After (returns Technology[])
52
+ await getTechnologyService().getAllForFilterBySubcategory(id)
53
+ ```
54
+
55
+ ```typescript
56
+ // Before (method didn't exist)
57
+ await getProductService().getAllByTechnology(technologyId)
58
+
59
+ // After (returns Product[])
60
+ await getProductService().getAllByTechnology(technologyId)
61
+ ```
62
+
63
+ ### 3. Service Method Patterns
64
+
65
+ #### Paginated Methods (for admin UI with pagination)
66
+ - Return: `{ items: T[], lastVisible: DocumentData }`
67
+ - Example: `getAllByFamily()`, `getAllByCategoryId()`, `getAllBySubcategoryId()`
68
+
69
+ #### Filter Methods (for dropdowns/filters)
70
+ - Return: `T[]`
71
+ - Example: `getAllForFilter()`, `getAllForFilterByFamily()`, `getAllForFilterByCategoryId()`
72
+
73
+ ## Files Modified
74
+
75
+ ### API (Backend)
76
+ - `Api/src/backoffice/services/category.service.ts`
77
+ - `Api/src/backoffice/services/technology.service.ts`
78
+ - `Api/src/backoffice/services/product.service.ts`
79
+ - `Api/src/backoffice/types/category.types.ts`
80
+ - `Api/src/backoffice/types/technology.types.ts`
81
+ - `Api/src/backoffice/types/product.types.ts`
82
+
83
+ ### Client App
84
+ - `ClinicApp/src/store/procedure/procedure.service.ts`
85
+
86
+ ## Expected Result
87
+ Categories, subcategories, technologies, and products should now load correctly in:
88
+ - Create Procedure Bulk form
89
+ - Create Procedure form
90
+ - Update Procedure form
91
+
92
+ The dropdowns should populate with the correct options when selecting families, categories, subcategories, and technologies.
93
+
94
+ ## Testing
95
+ 1. Navigate to Create Procedure Bulk
96
+ 2. Select a family (Aesthetics/Surgery) - categories should load
97
+ 3. Select a category - subcategories should load
98
+ 4. Select a subcategory - technologies should load
99
+ 5. Select a technology - products should load and qualified practitioners should appear
100
+
101
+ ## Note
102
+ If TypeScript errors persist about missing methods, the API package may need to be rebuilt (`npm run build` in the Api directory) for the type definitions to be updated in the consuming client application.
@@ -12,10 +12,10 @@ import {
12
12
  startAfter,
13
13
  updateDoc,
14
14
  where,
15
- } from "firebase/firestore";
16
- import { Category, CATEGORIES_COLLECTION } from "../types/category.types";
17
- import { BaseService } from "../../services/base.service";
18
- import { ProcedureFamily } from "../types/static/procedure-family.types";
15
+ } from 'firebase/firestore';
16
+ import { Category, CATEGORIES_COLLECTION, ICategoryService } from '../types/category.types';
17
+ import { BaseService } from '../../services/base.service';
18
+ import { ProcedureFamily } from '../types/static/procedure-family.types';
19
19
 
20
20
  /**
21
21
  * Servis za upravljanje kategorijama procedura.
@@ -30,7 +30,7 @@ import { ProcedureFamily } from "../types/static/procedure-family.types";
30
30
  * family: ProcedureFamily.AESTHETICS
31
31
  * });
32
32
  */
33
- export class CategoryService extends BaseService {
33
+ export class CategoryService extends BaseService implements ICategoryService {
34
34
  /**
35
35
  * Referenca na Firestore kolekciju kategorija
36
36
  */
@@ -43,9 +43,9 @@ export class CategoryService extends BaseService {
43
43
  * @param category - Podaci za novu kategoriju
44
44
  * @returns Kreirana kategorija sa generisanim ID-em
45
45
  */
46
- async create(category: Omit<Category, "id" | "createdAt" | "updatedAt">) {
46
+ async create(category: Omit<Category, 'id' | 'createdAt' | 'updatedAt'>) {
47
47
  const now = new Date();
48
- const newCategory: Omit<Category, "id"> = {
48
+ const newCategory: Omit<Category, 'id'> = {
49
49
  ...category,
50
50
  createdAt: now,
51
51
  updatedAt: now,
@@ -68,8 +68,8 @@ export class CategoryService extends BaseService {
68
68
  for (const family of families) {
69
69
  const q = query(
70
70
  this.categoriesRef,
71
- where("family", "==", family),
72
- where("isActive", "==", active)
71
+ where('family', '==', family),
72
+ where('isActive', '==', active),
73
73
  );
74
74
  const snapshot = await getCountFromServer(q);
75
75
  counts[family] = snapshot.data().count;
@@ -82,14 +82,36 @@ export class CategoryService extends BaseService {
82
82
  * @returns Lista svih aktivnih kategorija
83
83
  */
84
84
  async getAllForFilter() {
85
- const q = query(this.categoriesRef, where("isActive", "==", true));
85
+ const q = query(this.categoriesRef, where('isActive', '==', true));
86
86
  const snapshot = await getDocs(q);
87
87
  return snapshot.docs.map(
88
- (doc) =>
88
+ doc =>
89
89
  ({
90
90
  id: doc.id,
91
91
  ...doc.data(),
92
- } as Category)
92
+ } as Category),
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Vraća sve kategorije za određenu familiju za potrebe filtera (bez paginacije)
98
+ * @param family - Familija procedura (aesthetics/surgery)
99
+ * @returns Lista aktivnih kategorija koje pripadaju traženoj familiji
100
+ */
101
+ async getAllForFilterByFamily(family: ProcedureFamily) {
102
+ const q = query(
103
+ this.categoriesRef,
104
+ where('family', '==', family),
105
+ where('isActive', '==', true),
106
+ orderBy('name'),
107
+ );
108
+ const snapshot = await getDocs(q);
109
+ return snapshot.docs.map(
110
+ doc =>
111
+ ({
112
+ id: doc.id,
113
+ ...doc.data(),
114
+ } as Category),
93
115
  );
94
116
  }
95
117
 
@@ -103,12 +125,12 @@ export class CategoryService extends BaseService {
103
125
  active?: boolean;
104
126
  limit?: number;
105
127
  lastVisible?: DocumentData;
106
- } = {}
128
+ } = {},
107
129
  ) {
108
130
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
109
131
  const constraints = [
110
- where("isActive", "==", active),
111
- orderBy("name"),
132
+ where('isActive', '==', active),
133
+ orderBy('name'),
112
134
  queryLimit ? limit(queryLimit) : undefined,
113
135
  lastVisible ? startAfter(lastVisible) : undefined,
114
136
  ].filter((c): c is NonNullable<typeof c> => !!c);
@@ -116,11 +138,11 @@ export class CategoryService extends BaseService {
116
138
  const q = query(this.categoriesRef, ...constraints);
117
139
  const snapshot = await getDocs(q);
118
140
  const categories = snapshot.docs.map(
119
- (doc) =>
141
+ doc =>
120
142
  ({
121
143
  id: doc.id,
122
144
  ...doc.data(),
123
- } as Category)
145
+ } as Category),
124
146
  );
125
147
  const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
126
148
  return { categories, lastVisible: newLastVisible };
@@ -138,13 +160,13 @@ export class CategoryService extends BaseService {
138
160
  active?: boolean;
139
161
  limit?: number;
140
162
  lastVisible?: DocumentData;
141
- } = {}
163
+ } = {},
142
164
  ) {
143
165
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
144
166
  const constraints = [
145
- where("family", "==", family),
146
- where("isActive", "==", active),
147
- orderBy("name"),
167
+ where('family', '==', family),
168
+ where('isActive', '==', active),
169
+ orderBy('name'),
148
170
  queryLimit ? limit(queryLimit) : undefined,
149
171
  lastVisible ? startAfter(lastVisible) : undefined,
150
172
  ].filter((c): c is NonNullable<typeof c> => !!c);
@@ -152,11 +174,11 @@ export class CategoryService extends BaseService {
152
174
  const q = query(this.categoriesRef, ...constraints);
153
175
  const snapshot = await getDocs(q);
154
176
  const categories = snapshot.docs.map(
155
- (doc) =>
177
+ doc =>
156
178
  ({
157
179
  id: doc.id,
158
180
  ...doc.data(),
159
- } as Category)
181
+ } as Category),
160
182
  );
161
183
  const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
162
184
  return { categories, lastVisible: newLastVisible };
@@ -168,10 +190,7 @@ export class CategoryService extends BaseService {
168
190
  * @param category - Novi podaci za kategoriju
169
191
  * @returns Ažurirana kategorija
170
192
  */
171
- async update(
172
- id: string,
173
- category: Partial<Omit<Category, "id" | "createdAt">>
174
- ) {
193
+ async update(id: string, category: Partial<Omit<Category, 'id' | 'createdAt'>>) {
175
194
  const updateData = {
176
195
  ...category,
177
196
  updatedAt: new Date(),