@snapdragonsnursery/react-components 1.0.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.
@@ -0,0 +1,478 @@
1
+ import React, { useState } from "react";
2
+ import ChildSearchModal from "./ChildSearchModal";
3
+
4
+ const ChildSearchDemo = () => {
5
+ const [isModalOpen, setIsModalOpen] = useState(false);
6
+ const [selectedChild, setSelectedChild] = useState(null);
7
+ const [demoConfig, setDemoConfig] = useState({
8
+ siteId: null,
9
+ siteIds: null, // Array for multi-site search
10
+ sites: null, // Array of site objects from useUserSites hook
11
+ activeOnly: true,
12
+ status: "active",
13
+ dobFrom: "",
14
+ dobTo: "",
15
+ ageFrom: "",
16
+ ageTo: "",
17
+ sortBy: "last_name",
18
+ sortOrder: "asc",
19
+ bypassPermissions: false,
20
+ showSiteFilter: true,
21
+ multiSelect: false,
22
+ maxSelections: null,
23
+ });
24
+
25
+ // Mock sites data for demo purposes
26
+ const mockSites = [
27
+ { site_id: 1, site_name: "Nursery A" },
28
+ { site_id: 2, site_name: "Nursery B" },
29
+ { site_id: 3, site_name: "Nursery C" },
30
+ ];
31
+
32
+ const handleChildSelect = (child) => {
33
+ if (demoConfig.multiSelect) {
34
+ // Multi-select mode - child is an array
35
+ setSelectedChild(child);
36
+ console.log("Selected children:", child);
37
+ } else {
38
+ // Single select mode - child is a single object
39
+ setSelectedChild(child);
40
+ console.log("Selected child:", child);
41
+ }
42
+ };
43
+
44
+ const openModal = () => {
45
+ setIsModalOpen(true);
46
+ };
47
+
48
+ const closeModal = () => {
49
+ setIsModalOpen(false);
50
+ };
51
+
52
+ return (
53
+ <div className="p-6 max-w-4xl mx-auto">
54
+ <h1 className="text-2xl font-bold mb-6">Child Search Modal Demo</h1>
55
+
56
+ {/* Configuration */}
57
+ <div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg mb-6">
58
+ <h2 className="text-lg font-semibold mb-4">Configuration</h2>
59
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
60
+ <div>
61
+ <label className="block text-sm font-medium mb-2">
62
+ Site ID (optional)
63
+ </label>
64
+ <input
65
+ type="number"
66
+ value={demoConfig.siteId || ""}
67
+ onChange={(e) =>
68
+ setDemoConfig((prev) => ({
69
+ ...prev,
70
+ siteId: e.target.value ? parseInt(e.target.value) : null,
71
+ }))
72
+ }
73
+ placeholder="Leave empty for all sites"
74
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
75
+ />
76
+ </div>
77
+ <div>
78
+ <label className="block text-sm font-medium mb-2">
79
+ Site IDs (comma-separated)
80
+ </label>
81
+ <input
82
+ type="text"
83
+ value={demoConfig.siteIds ? demoConfig.siteIds.join(",") : ""}
84
+ onChange={(e) =>
85
+ setDemoConfig((prev) => ({
86
+ ...prev,
87
+ siteIds: e.target.value
88
+ ? e.target.value
89
+ .split(",")
90
+ .map((id) => parseInt(id.trim()))
91
+ .filter((id) => !isNaN(id))
92
+ : null,
93
+ }))
94
+ }
95
+ placeholder="1,2,3 for multiple sites"
96
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
97
+ />
98
+ </div>
99
+ <div>
100
+ <label className="block text-sm font-medium mb-2">Status</label>
101
+ <select
102
+ value={demoConfig.status}
103
+ onChange={(e) =>
104
+ setDemoConfig((prev) => ({
105
+ ...prev,
106
+ status: e.target.value,
107
+ }))
108
+ }
109
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
110
+ >
111
+ <option value="active">Active only</option>
112
+ <option value="inactive">Inactive only</option>
113
+ <option value="all">All children</option>
114
+ </select>
115
+ </div>
116
+ <div>
117
+ <label className="block text-sm font-medium mb-2">
118
+ Bypass Permissions
119
+ </label>
120
+ <select
121
+ value={demoConfig.bypassPermissions.toString()}
122
+ onChange={(e) =>
123
+ setDemoConfig((prev) => ({
124
+ ...prev,
125
+ bypassPermissions: e.target.value === "true",
126
+ }))
127
+ }
128
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
129
+ >
130
+ <option value="false">Use permissions</option>
131
+ <option value="true">Bypass permissions</option>
132
+ </select>
133
+ </div>
134
+ </div>
135
+
136
+ {/* Selection Mode Configuration */}
137
+ <div className="mt-4 p-4 bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800 rounded-lg">
138
+ <h3 className="text-sm font-semibold text-purple-800 dark:text-purple-200 mb-3">
139
+ Selection Mode
140
+ </h3>
141
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
142
+ <div>
143
+ <label className="block text-sm font-medium mb-2">
144
+ Selection Mode
145
+ </label>
146
+ <select
147
+ value={demoConfig.multiSelect ? "multi" : "single"}
148
+ onChange={(e) =>
149
+ setDemoConfig((prev) => ({
150
+ ...prev,
151
+ multiSelect: e.target.value === "multi",
152
+ }))
153
+ }
154
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
155
+ >
156
+ <option value="single">Single Selection</option>
157
+ <option value="multi">Multi Selection</option>
158
+ </select>
159
+ </div>
160
+ {demoConfig.multiSelect && (
161
+ <>
162
+ <div>
163
+ <label className="block text-sm font-medium mb-2">
164
+ Max Selections
165
+ </label>
166
+ <select
167
+ value={demoConfig.maxSelections || "unlimited"}
168
+ onChange={(e) =>
169
+ setDemoConfig((prev) => ({
170
+ ...prev,
171
+ maxSelections:
172
+ e.target.value === "unlimited"
173
+ ? null
174
+ : parseInt(e.target.value),
175
+ }))
176
+ }
177
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
178
+ >
179
+ <option value="unlimited">Unlimited</option>
180
+ <option value="5">5 children</option>
181
+ <option value="10">10 children</option>
182
+ <option value="20">20 children</option>
183
+ </select>
184
+ </div>
185
+ <div className="flex items-end">
186
+ <button
187
+ onClick={() => setSelectedChild(null)}
188
+ className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600"
189
+ >
190
+ Clear Selection
191
+ </button>
192
+ </div>
193
+ </>
194
+ )}
195
+ </div>
196
+ </div>
197
+
198
+ {/* Site Configuration */}
199
+ <div className="mt-4 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
200
+ <h3 className="text-sm font-semibold text-green-800 dark:text-green-200 mb-3">
201
+ Site Configuration
202
+ </h3>
203
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
204
+ <div>
205
+ <label className="block text-sm font-medium mb-2">
206
+ Use Mock Sites
207
+ </label>
208
+ <select
209
+ value={demoConfig.sites ? "mock" : "none"}
210
+ onChange={(e) =>
211
+ setDemoConfig((prev) => ({
212
+ ...prev,
213
+ sites: e.target.value === "mock" ? mockSites : null,
214
+ }))
215
+ }
216
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
217
+ >
218
+ <option value="none">No sites (use permissions)</option>
219
+ <option value="mock">Use mock sites (demo)</option>
220
+ </select>
221
+ </div>
222
+ <div>
223
+ <label className="block text-sm font-medium mb-2">
224
+ Show Site Filter
225
+ </label>
226
+ <select
227
+ value={demoConfig.showSiteFilter.toString()}
228
+ onChange={(e) =>
229
+ setDemoConfig((prev) => ({
230
+ ...prev,
231
+ showSiteFilter: e.target.value === "true",
232
+ }))
233
+ }
234
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
235
+ >
236
+ <option value="true">Show site filter</option>
237
+ <option value="false">Hide site filter</option>
238
+ </select>
239
+ </div>
240
+ </div>
241
+ </div>
242
+
243
+ {/* Advanced Configuration */}
244
+ <div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
245
+ <h3 className="text-sm font-semibold text-blue-800 dark:text-blue-200 mb-3">
246
+ Advanced Configuration
247
+ </h3>
248
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
249
+ <div>
250
+ <label className="block text-sm font-medium mb-2">DOB From</label>
251
+ <input
252
+ type="date"
253
+ value={demoConfig.dobFrom}
254
+ onChange={(e) =>
255
+ setDemoConfig((prev) => ({
256
+ ...prev,
257
+ dobFrom: e.target.value,
258
+ }))
259
+ }
260
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
261
+ />
262
+ </div>
263
+ <div>
264
+ <label className="block text-sm font-medium mb-2">DOB To</label>
265
+ <input
266
+ type="date"
267
+ value={demoConfig.dobTo}
268
+ onChange={(e) =>
269
+ setDemoConfig((prev) => ({
270
+ ...prev,
271
+ dobTo: e.target.value,
272
+ }))
273
+ }
274
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
275
+ />
276
+ </div>
277
+ <div>
278
+ <label className="block text-sm font-medium mb-2">
279
+ Age From (months)
280
+ </label>
281
+ <input
282
+ type="number"
283
+ min="0"
284
+ value={demoConfig.ageFrom}
285
+ onChange={(e) =>
286
+ setDemoConfig((prev) => ({
287
+ ...prev,
288
+ ageFrom: e.target.value,
289
+ }))
290
+ }
291
+ placeholder="0"
292
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
293
+ />
294
+ </div>
295
+ <div>
296
+ <label className="block text-sm font-medium mb-2">
297
+ Age To (months)
298
+ </label>
299
+ <input
300
+ type="number"
301
+ min="0"
302
+ value={demoConfig.ageTo}
303
+ onChange={(e) =>
304
+ setDemoConfig((prev) => ({
305
+ ...prev,
306
+ ageTo: e.target.value,
307
+ }))
308
+ }
309
+ placeholder="60"
310
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
311
+ />
312
+ </div>
313
+ </div>
314
+ <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
315
+ <div>
316
+ <label className="block text-sm font-medium mb-2">Sort By</label>
317
+ <select
318
+ value={demoConfig.sortBy}
319
+ onChange={(e) =>
320
+ setDemoConfig((prev) => ({
321
+ ...prev,
322
+ sortBy: e.target.value,
323
+ }))
324
+ }
325
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
326
+ >
327
+ <option value="last_name">Last Name</option>
328
+ <option value="first_name">First Name</option>
329
+ <option value="full_name">Full Name</option>
330
+ <option value="date_of_birth">Date of Birth</option>
331
+ <option value="site_name">Site Name</option>
332
+ </select>
333
+ </div>
334
+ <div>
335
+ <label className="block text-sm font-medium mb-2">
336
+ Sort Order
337
+ </label>
338
+ <select
339
+ value={demoConfig.sortOrder}
340
+ onChange={(e) =>
341
+ setDemoConfig((prev) => ({
342
+ ...prev,
343
+ sortOrder: e.target.value,
344
+ }))
345
+ }
346
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
347
+ >
348
+ <option value="asc">Ascending</option>
349
+ <option value="desc">Descending</option>
350
+ </select>
351
+ </div>
352
+ </div>
353
+ </div>
354
+ </div>
355
+
356
+ {/* Open Modal Button */}
357
+ <div className="mb-6">
358
+ <button
359
+ onClick={openModal}
360
+ className="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg font-medium transition-colors"
361
+ >
362
+ Open Child Search Modal
363
+ </button>
364
+ </div>
365
+
366
+ {/* Selected Child Display */}
367
+ {selectedChild && (
368
+ <div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4 mb-6">
369
+ <h3 className="text-lg font-semibold text-green-800 dark:text-green-200 mb-2">
370
+ {demoConfig.multiSelect ? "Selected Children" : "Selected Child"}
371
+ </h3>
372
+ <div className="text-green-700 dark:text-green-300">
373
+ {demoConfig.multiSelect && Array.isArray(selectedChild) ? (
374
+ // Multi-select display
375
+ <div>
376
+ <p className="mb-2">
377
+ <strong>Total Selected:</strong> {selectedChild.length}
378
+ {demoConfig.maxSelections && ` / ${demoConfig.maxSelections}`}
379
+ </p>
380
+ <div className="space-y-2 max-h-60 overflow-y-auto">
381
+ {selectedChild.map((child, index) => (
382
+ <div
383
+ key={child.child_id}
384
+ className="p-2 bg-white dark:bg-gray-800 rounded border"
385
+ >
386
+ <p>
387
+ <strong>{index + 1}.</strong> {child.full_name} (ID:{" "}
388
+ {child.child_id})
389
+ </p>
390
+ <p className="text-sm">
391
+ Site: {child.site_name} • DOB:{" "}
392
+ {child.date_of_birth
393
+ ? new Date(child.date_of_birth).toLocaleDateString(
394
+ "en-GB"
395
+ )
396
+ : "N/A"}
397
+ </p>
398
+ </div>
399
+ ))}
400
+ </div>
401
+ </div>
402
+ ) : (
403
+ // Single select display
404
+ <div>
405
+ <p>
406
+ <strong>Name:</strong> {selectedChild.full_name}
407
+ </p>
408
+ <p>
409
+ <strong>ID:</strong> {selectedChild.child_id}
410
+ </p>
411
+ <p>
412
+ <strong>Site:</strong> {selectedChild.site_name}
413
+ </p>
414
+ <p>
415
+ <strong>Date of Birth:</strong>{" "}
416
+ {selectedChild.date_of_birth
417
+ ? new Date(selectedChild.date_of_birth).toLocaleDateString(
418
+ "en-GB"
419
+ )
420
+ : "N/A"}
421
+ </p>
422
+ <p>
423
+ <strong>Age:</strong> {selectedChild.age_years || 0}y{" "}
424
+ {selectedChild.age_months || 0}m
425
+ </p>
426
+ <p>
427
+ <strong>Status:</strong>{" "}
428
+ {selectedChild.is_active ? "Active" : "Inactive"}
429
+ </p>
430
+ </div>
431
+ )}
432
+ </div>
433
+ </div>
434
+ )}
435
+
436
+ {/* Usage Instructions */}
437
+ <div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
438
+ <h3 className="text-lg font-semibold text-blue-800 dark:text-blue-200 mb-2">
439
+ Usage Instructions
440
+ </h3>
441
+ <div className="text-blue-700 dark:text-blue-300 space-y-2">
442
+ <p>• Click "Open Child Search Modal" to open the search interface</p>
443
+ <p>• Type in the search box to find children by name or ID</p>
444
+ <p>• Click on a child to select them</p>
445
+ <p>• Use the pagination controls to navigate through results</p>
446
+ <p>• The modal will close automatically when a child is selected</p>
447
+ </div>
448
+ </div>
449
+
450
+ {/* Child Search Modal */}
451
+ <ChildSearchModal
452
+ isOpen={isModalOpen}
453
+ onClose={closeModal}
454
+ onSelect={handleChildSelect}
455
+ title="Search Children"
456
+ siteId={demoConfig.siteId}
457
+ siteIds={demoConfig.siteIds}
458
+ sites={demoConfig.sites}
459
+ activeOnly={demoConfig.activeOnly}
460
+ status={demoConfig.status}
461
+ dobFrom={demoConfig.dobFrom}
462
+ dobTo={demoConfig.dobTo}
463
+ ageFrom={demoConfig.ageFrom}
464
+ ageTo={demoConfig.ageTo}
465
+ sortBy={demoConfig.sortBy}
466
+ sortOrder={demoConfig.sortOrder}
467
+ bypassPermissions={demoConfig.bypassPermissions}
468
+ applicationContext="child-search-demo"
469
+ showAdvancedFilters={true}
470
+ showSiteFilter={demoConfig.showSiteFilter}
471
+ multiSelect={demoConfig.multiSelect}
472
+ maxSelections={demoConfig.maxSelections}
473
+ />
474
+ </div>
475
+ );
476
+ };
477
+
478
+ export default ChildSearchDemo;