@codesinger0/shared-components 1.0.45 → 1.0.46

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.
@@ -0,0 +1,243 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ collection,
4
+ doc,
5
+ getDocs,
6
+ getDoc,
7
+ addDoc,
8
+ setDoc,
9
+ updateDoc,
10
+ deleteDoc,
11
+ orderBy,
12
+ query,
13
+ serverTimestamp
14
+ } from 'firebase/firestore';
15
+
16
+ /**
17
+ * Generic Firestore Collection Hook
18
+ *
19
+ * @param {string} collectionName - Name of the Firestore collection
20
+ * @param {Function} ModelClass - Constructor function for the model class
21
+ * @param {Object} options - Configuration options
22
+ * @param {string} options.defaultSortField - Default field to sort by (default: 'createdAt')
23
+ * @param {string} options.defaultSortOrder - Default sort order ('asc' or 'desc', default: 'desc')
24
+ * @param {Object} options.errorMessages - Custom error messages for operations
25
+ * @returns {Object} Hook state and methods
26
+ */
27
+ export const useFirestoreCollection = (
28
+ collectionName,
29
+ ModelClass,
30
+ options = {},
31
+ db
32
+ ) => {
33
+ const {
34
+ defaultSortField = 'createdAt',
35
+ defaultSortOrder = 'desc',
36
+ errorMessages = {}
37
+ } = options;
38
+
39
+ // Default error messages with option to override
40
+ const defaultErrorMessages = {
41
+ get: 'קבלת נתונים',
42
+ getById: 'קבלת פריט',
43
+ create: 'יצירת פריט',
44
+ update: 'עדכון פריט',
45
+ delete: 'מחיקת פריט',
46
+ notFound: 'פריט לא נמצא',
47
+ ...errorMessages
48
+ };
49
+
50
+ const [items, setItems] = useState([]);
51
+ const [loading, setLoading] = useState(false);
52
+ const [error, setError] = useState(null);
53
+
54
+ // Helper function to clear error
55
+ const clearError = () => setError(null);
56
+
57
+ // Helper function to handle errors
58
+ const handleError = (error, operation) => {
59
+ console.error(`Error in ${operation}:`, error);
60
+ setError(`שגיאה ב${defaultErrorMessages[operation] || operation}: ${error.message}`);
61
+ setLoading(false);
62
+ };
63
+
64
+ // Get all items
65
+ const getItems = async (sortField = defaultSortField, sortOrder = defaultSortOrder) => {
66
+ setLoading(true);
67
+ clearError();
68
+
69
+ try {
70
+ const q = query(
71
+ collection(db, collectionName),
72
+ orderBy(sortField, sortOrder)
73
+ );
74
+ const querySnapshot = await getDocs(q);
75
+
76
+ const itemsData = [];
77
+ querySnapshot.forEach((doc) => {
78
+ itemsData.push(new ModelClass({
79
+ ...doc.data(),
80
+ id: doc.id
81
+ }));
82
+ });
83
+
84
+ setItems(itemsData);
85
+ setLoading(false);
86
+ return itemsData;
87
+ } catch (error) {
88
+ handleError(error, 'get');
89
+ return [];
90
+ }
91
+ };
92
+
93
+ // Get single item by ID
94
+ const getItem = async (itemId) => {
95
+ setLoading(true);
96
+ clearError();
97
+
98
+ try {
99
+ const docRef = doc(db, collectionName, itemId);
100
+ const docSnap = await getDoc(docRef);
101
+
102
+ if (docSnap.exists()) {
103
+ const itemData = new ModelClass({
104
+ ...docSnap.data(),
105
+ id: docSnap.id
106
+ });
107
+ setLoading(false);
108
+ return itemData;
109
+ } else {
110
+ const errorMsg = defaultErrorMessages.notFound;
111
+ setError(errorMsg);
112
+ setLoading(false);
113
+ throw new Error(errorMsg);
114
+ }
115
+ } catch (error) {
116
+ handleError(error, 'getById');
117
+ return null;
118
+ }
119
+ };
120
+
121
+ // Create new item
122
+ const createItem = async (itemData, customId = null) => {
123
+ setLoading(true);
124
+ clearError();
125
+
126
+ try {
127
+ // Create Model instance and validate
128
+ const item = new ModelClass(itemData);
129
+ const validation = item.validate();
130
+
131
+ if (!validation.isValid) {
132
+ throw new Error(validation.errors.join(', '));
133
+ }
134
+
135
+ // Prepare item data with timestamps
136
+ const firestoreData = {
137
+ ...item.toFirestore(),
138
+ createdAt: serverTimestamp(),
139
+ updatedAt: serverTimestamp()
140
+ };
141
+
142
+ let docRef;
143
+ if (customId) {
144
+ // Use custom ID
145
+ docRef = doc(db, collectionName, customId);
146
+ await setDoc(docRef, firestoreData);
147
+ } else {
148
+ // Auto-generate ID
149
+ docRef = await addDoc(collection(db, collectionName), firestoreData);
150
+ }
151
+
152
+ // Return Model instance
153
+ const newItem = new ModelClass({
154
+ id: docRef.id,
155
+ ...item.toFirestore(),
156
+ createdAt: firestoreData.createdAt,
157
+ updatedAt: firestoreData.updatedAt
158
+ });
159
+
160
+ setLoading(false);
161
+ return newItem;
162
+ } catch (error) {
163
+ handleError(error, 'create');
164
+ return null;
165
+ }
166
+ };
167
+
168
+ const updateItem = async (itemId, updates) => {
169
+ setLoading(true);
170
+ clearError();
171
+
172
+ try {
173
+ // Get existing document first
174
+ const docRef = doc(db, collectionName, itemId);
175
+ const existingDoc = await getDoc(docRef);
176
+
177
+ if (!existingDoc.exists()) {
178
+ throw new Error('Document not found');
179
+ }
180
+
181
+ // Merge existing data with updates
182
+ const existingData = existingDoc.data();
183
+ const mergedData = { ...existingData, ...updates, id: itemId };
184
+
185
+ // Create Model instance and validate
186
+ const item = new ModelClass(mergedData);
187
+ const validation = item.validate();
188
+
189
+ if (!validation.isValid) {
190
+ throw new Error(validation.errors.join(', '));
191
+ }
192
+
193
+ // Prepare updates with timestamp
194
+ const updateData = {
195
+ ...item.toFirestore(),
196
+ updatedAt: serverTimestamp()
197
+ };
198
+
199
+ await updateDoc(docRef, updateData);
200
+ setLoading(false);
201
+
202
+ // Return Model instance
203
+ return new ModelClass({ id: itemId, ...updateData });
204
+ } catch (error) {
205
+ handleError(error, 'update');
206
+ return null;
207
+ }
208
+ };
209
+
210
+ // Delete item
211
+ const deleteItem = async (itemId) => {
212
+ setLoading(true);
213
+ clearError();
214
+
215
+ try {
216
+ const docRef = doc(db, collectionName, itemId);
217
+ await deleteDoc(docRef);
218
+ setLoading(false);
219
+
220
+ return true;
221
+ } catch (error) {
222
+ handleError(error, 'delete');
223
+ return false;
224
+ }
225
+ };
226
+
227
+ return {
228
+ // State
229
+ items,
230
+ loading,
231
+ error,
232
+
233
+ // Actions
234
+ getItems,
235
+ getItem,
236
+ createItem,
237
+ updateItem,
238
+ deleteItem,
239
+
240
+ // Utility
241
+ clearError
242
+ };
243
+ };
package/dist/index.js CHANGED
@@ -19,4 +19,5 @@ export { ToastProvider, useToast } from './components/ToastProvider'
19
19
  export { CartProvider, useCart } from './context/CartContext'
20
20
 
21
21
  // Hooks
22
- export { default as useScrollLock } from './hooks/useScrollLock'
22
+ export { default as useScrollLock } from './hooks/useScrollLock'
23
+ export { useFirestoreCollection } from './hooks/useFirestoreCollection'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesinger0/shared-components",
3
- "version": "1.0.45",
3
+ "version": "1.0.46",
4
4
  "description": "Shared React components for customer projects",
5
5
  "main": "dist/index.js",
6
6
  "files": [