-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathstate.js
More file actions
609 lines (565 loc) · 21 KB
/
Copy pathstate.js
File metadata and controls
609 lines (565 loc) · 21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
/* ==========================================================================
GREEN KARMA — REACTIVE STATE STORE
Persistent State, Role-Based Access, & Event Subscriptions
========================================================================== */
import { Formatters } from './utils/formatters.js';
class StateStore {
constructor() {
this.listeners = new Set();
this.loadInitialState();
}
loadInitialState() {
this.state = {
// Current User Role: 'citizen' | 'worker' | 'admin' | 'institution'
currentRole: 'citizen',
// Citizen Profile (Shivansh Prajapati)
user: {
id: 'usr_shivansh_99',
name: 'Shivansh Prajapati',
email: 'shivansh.green@karma.org',
phone: '+91 98765 43210',
avatar: 'SP',
address: 'Flat 402, Green Meadows, Ward 4B, Mumbai',
greenPoints: 1250, // 100 GP = ₹10 => ₹125
lifetimeWasteKg: 125,
pickupsCompleted: 18,
co2SavedKg: 84.5,
treesEquivalent: 6.2,
waterSavedLitres: 480,
rank: 12,
greenStreakDays: 8,
kycVerified: true,
joinDate: '12 Jan 2026'
},
// Municipal Worker Profile (Ramesh Kumar)
worker: {
id: 'wrk_ramesh_04',
name: 'Ramesh Kumar',
zone: 'Zone 4 — Ward 4B (West Bandra)',
vehicle: 'Electric Waste Van (MH-02-GK-4091)',
rating: 4.9,
todayCollections: 14,
totalVerifiedKg: 1840,
status: 'Active on Route'
},
// Government / Municipal City Data (Smart City Command Center)
cityStats: {
totalWasteTons: 24850,
recycledPercentage: 68,
activeCitizens: 125420,
verifiedPickups: 87540,
wasteDiversionRate: 42,
greenPointsIssued: 4850000,
greenPointsRedeemed: 3920000,
hotspotsResolved: 342,
connectedDatabases: [
{ name: 'Municipal Waste Registry (BMC-GIS)', status: 'ONLINE', ping: '18ms', records: '1.4M' },
{ name: 'National Citizen Green Ledger (UIDAI / SBM)', status: 'ONLINE', ping: '24ms', records: '125K' },
{ name: 'Smart City Fleet & Worker Dispatch API', status: 'ONLINE', ping: '12ms', records: '480 Vans' },
{ name: 'Authorized Material Recovery Facilities (MRF)', status: 'ONLINE', ping: '32ms', records: '18 Hubs' }
]
},
// Active & Historical Pickup Requests
pickups: [
{
id: 'GK-2026-89421',
category: 'wet',
categoryName: 'Wet Waste (Organic)',
pointsReward: 10,
quantityKg: 4.5,
subType: 'Kitchen Vegetable & Fruit Scraps',
address: 'Flat 402, Green Meadows, Ward 4B, Mumbai',
createdAt: new Date(Date.now() - 25 * 60 * 1000).toISOString(),
status: 'on_the_way', // 'created', 'assigned', 'on_the_way', 'collected', 'verified', 'rejected'
workerName: 'Ramesh Kumar',
workerPhone: '+91 98111 22334',
vehicleNo: 'MH-02-GK-4091',
otp: '8492',
etaMinutes: 12,
currentLocation: [19.0620, 72.8410],
destinationLocation: [19.0760, 72.8777],
notes: 'Biodegradable green bin kept at doorstep'
},
{
id: 'GK-2026-89210',
category: 'dry',
categoryName: 'Dry Waste (Recyclable)',
pointsReward: 7,
quantityKg: 8.2,
subType: 'Cardboard cartons & PET beverage bottles',
address: 'Flat 402, Green Meadows, Ward 4B, Mumbai',
createdAt: new Date(Date.now() - 2 * 86400 * 1000).toISOString(),
status: 'verified',
workerName: 'Ramesh Kumar',
otp: '5120',
pointsCredited: 7
},
{
id: 'GK-2026-88741',
category: 'harmful',
categoryName: 'Harmful Waste (Hazardous)',
pointsReward: 5,
quantityKg: 2.0,
subType: 'Used Lithium Batteries & Broken Fluorescent Tube',
address: 'Flat 402, Green Meadows, Ward 4B, Mumbai',
createdAt: new Date(Date.now() - 5 * 86400 * 1000).toISOString(),
status: 'verified',
workerName: 'Sunil Jadhav',
otp: '3941',
pointsCredited: 5
}
],
// Assigned queue for worker portal
workerQueue: [
{
id: 'GK-2026-89421',
userName: 'Shivansh Prajapati',
address: 'Flat 402, Green Meadows, Ward 4B',
category: 'wet',
subType: 'Kitchen Scraps',
quantityKg: 4.5,
pointsReward: 10,
status: 'on_the_way',
otp: '8492',
photoUrl: 'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?w=300&q=80'
},
{
id: 'GK-2026-89512',
userName: 'Aanya Sharma',
address: 'B-14 Silver Oak Apt, Hill Road',
category: 'dry',
subType: 'Paper & Metal Cans',
quantityKg: 6.0,
pointsReward: 7,
status: 'assigned',
otp: '1984',
photoUrl: 'https://images.unsplash.com/photo-1532996122724-e3c354a0b15b?w=300&q=80'
},
{
id: 'GK-2026-89601',
userName: 'Rohit Verma',
address: 'Sector 3, Sunrise Society',
category: 'harmful',
subType: 'Medical & E-waste',
quantityKg: 1.5,
pointsReward: 5,
status: 'assigned',
otp: '7721',
photoUrl: 'https://images.unsplash.com/photo-1588872657578-7efd1f1555ed?w=300&q=80'
}
],
// Rewards & Fintech Transactions
transactions: [
{
id: 'TXN-849102',
title: 'Wet Waste Pickup Verified',
category: 'EARN',
amountGp: 10,
equivalentInr: 1,
date: new Date(Date.now() - 3 * 3600 * 1000).toISOString(),
type: 'credit',
status: 'SUCCESS',
refId: 'GK-2026-89210'
},
{
id: 'TXN-848011',
title: '7-Day Green Streak Bonus',
category: 'STREAK',
amountGp: 25,
equivalentInr: 2.5,
date: new Date(Date.now() - 24 * 3600 * 1000).toISOString(),
type: 'credit',
status: 'SUCCESS'
},
{
id: 'TXN-846200',
title: 'Jio Mobile Recharge ₹10',
category: 'RECHARGE',
amountGp: 100,
equivalentInr: 10,
date: new Date(Date.now() - 48 * 3600 * 1000).toISOString(),
type: 'debit',
status: 'SUCCESS',
meta: 'Mob: 9876543210 (Jio Prepaid)'
},
{
id: 'TXN-845119',
title: 'Weekly Segregation Challenge Completed',
category: 'CHALLENGE',
amountGp: 50,
equivalentInr: 5,
date: new Date(Date.now() - 4 * 86400 * 1000).toISOString(),
type: 'credit',
status: 'SUCCESS'
}
],
// Gamified Badges
badges: [
{
id: 'badge_1',
name: 'First Recycler',
icon: '🌱',
description: 'Completed your very first segregated waste pickup.',
unlocked: true,
unlockedAt: '15 Jan 2026'
},
{
id: 'badge_2',
name: '100 KG Recycled',
icon: '♻️',
description: 'Diverted over 100 kilograms of waste from city landfills.',
unlocked: true,
unlockedAt: '20 Aug 2026'
},
{
id: 'badge_3',
name: '7-Day Green Streak',
icon: '🔥',
description: 'Logged segregated waste 7 consecutive days in a row.',
unlocked: true,
unlockedAt: '28 Aug 2026'
},
{
id: 'badge_4',
name: 'Eco Champion',
icon: '🏆',
description: 'Accumulate more than 2,000 Green Points.',
unlocked: false,
progress: 62.5 // 1250/2000
},
{
id: 'badge_5',
name: 'Planet Protector',
icon: '🌎',
description: 'Save 100+ KG of CO₂ emissions through responsible disposal.',
unlocked: false,
progress: 84.5 // 84.5/100
},
{
id: 'badge_6',
name: 'Waste Warrior',
icon: '🚮',
description: 'Successfully reported and helped clear harmful waste.',
unlocked: true,
unlockedAt: '10 Feb 2026'
},
{
id: 'badge_7',
name: 'Community Hero',
icon: '🏅',
description: 'Reported verified illegal dumping hotspots in your ward.',
unlocked: false,
progress: 50
}
],
// Daily & Weekly Challenges
challenges: [
{
id: 'ch_1',
title: 'Weekly Segregation Master',
description: 'Segregate waste correctly 5 times this week.',
current: 4,
target: 5,
rewardGp: 50,
daysLeft: 3,
category: 'weekly'
},
{
id: 'ch_2',
title: 'Plastic Recovery Sprint',
description: 'Recycle 10 plastic containers, boxes, or bottles.',
current: 7,
target: 10,
rewardGp: 20,
daysLeft: 2,
category: 'daily'
},
{
id: 'ch_3',
title: 'Zero Single-Use Plastic',
description: 'Avoid single-use plastic bags for 7 consecutive days.',
current: 5,
target: 7,
rewardGp: 30,
daysLeft: 2,
category: 'weekly'
},
{
id: 'ch_4',
title: 'Spot & Clean Hotspot',
description: 'Report an illegal dumping hotspot in your ward.',
current: 0,
target: 1,
rewardGp: 20,
daysLeft: 5,
category: 'weekly'
}
],
// Leaderboard dataset
leaderboards: {
global: [
{ rank: 1, name: 'Aarav Mehta', avatar: 'AM', points: 5420, wasteKg: 520, streak: 34, location: 'Mumbai' },
{ rank: 2, name: 'Priya Sharma', avatar: 'PS', points: 4980, wasteKg: 460, streak: 28, location: 'Bengaluru' },
{ rank: 3, name: 'Vikramaditya Roy', avatar: 'VR', points: 4650, wasteKg: 430, streak: 25, location: 'Delhi' },
{ rank: 4, name: 'Ananya Gupta', avatar: 'AG', points: 3890, wasteKg: 370, streak: 19, location: 'Pune' },
{ rank: 5, name: 'Karan Malhotra', avatar: 'KM', points: 3410, wasteKg: 310, streak: 15, location: 'Hyderabad' },
{ rank: 12, name: 'Shivansh Prajapati (You)', avatar: 'SP', points: 1250, wasteKg: 125, streak: 8, location: 'Mumbai', isUser: true }
],
city: [
{ rank: 1, name: 'Aarav Mehta', avatar: 'AM', points: 5420, wasteKg: 520, streak: 34, location: 'Ward 2A' },
{ rank: 2, name: 'Sneha Deshmukh', avatar: 'SD', points: 4210, wasteKg: 400, streak: 22, location: 'Ward 4B' },
{ rank: 3, name: 'Rahul Rane', avatar: 'RR', points: 3950, wasteKg: 380, streak: 18, location: 'Ward 7C' },
{ rank: 8, name: 'Shivansh Prajapati (You)', avatar: 'SP', points: 1250, wasteKg: 125, streak: 8, location: 'Ward 4B', isUser: true }
],
neighborhood: [
{ rank: 1, name: 'Sneha Deshmukh', avatar: 'SD', points: 4210, wasteKg: 400, streak: 22, location: 'Bldg 3' },
{ rank: 2, name: 'Rohan Patil', avatar: 'RP', points: 2150, wasteKg: 210, streak: 14, location: 'Bldg 8' },
{ rank: 3, name: 'Shivansh Prajapati (You)', avatar: 'SP', points: 1250, wasteKg: 125, streak: 8, location: 'Bldg 4', isUser: true }
],
college: [
{ rank: 1, name: 'IIT Bombay Eco Cell', avatar: 'IIT', points: 38400, wasteKg: 3600, location: 'Powai Campus' },
{ rank: 2, name: 'BITS Pilani Green Club', avatar: 'BP', points: 31200, wasteKg: 2950, location: 'Goa Campus' },
{ rank: 3, name: 'Delhi University Nature Hub', avatar: 'DU', points: 27800, wasteKg: 2600, location: 'North Campus' }
],
school: [
{ rank: 1, name: 'Delhi Public School Green Club', avatar: 'DPS', points: 19500, wasteKg: 1840, location: 'RK Puram' },
{ rank: 2, name: 'The Mother\'s International School', avatar: 'MIS', points: 16200, wasteKg: 1510, location: 'New Delhi' },
{ rank: 3, name: 'St. Xavier\'s Eco Brigade', avatar: 'SX', points: 14800, wasteKg: 1390, location: 'Mumbai' }
]
},
// Illegal Dumping Reports
illegalDumpingReports: [
{
id: 'DUMP-2026-104',
location: 'Under Flyover, Link Road, Ward 4B',
wasteType: 'Construction Debris & Mixed Plastics',
reportedAt: new Date(Date.now() - 4 * 3600 * 1000).toISOString(),
status: 'Investigating', // 'Submitted', 'Assigned', 'Investigating', 'Resolved'
photoUrl: 'https://images.unsplash.com/photo-1611288875785-58586c06a4b1?w=300&q=80',
rewardGp: 20
},
{
id: 'DUMP-2026-098',
location: 'Near Old Water Tank, Sector 9',
wasteType: 'Abandoned Commercial Waste',
reportedAt: new Date(Date.now() - 36 * 3600 * 1000).toISOString(),
status: 'Resolved',
photoUrl: 'https://images.unsplash.com/photo-1605600659908-0ef719419d41?w=300&q=80',
rewardGp: 20,
pointsCredited: true
}
],
// Notifications Feed
notifications: [
{
id: 'notif_1',
title: '🚚 Pickup On The Way',
message: 'Worker Ramesh Kumar is 12 mins away from your location for request #GK-2026-89421.',
timestamp: new Date(Date.now() - 15 * 60 * 1000).toISOString(),
read: false,
type: 'pickup'
},
{
id: 'notif_2',
title: '🌱 You Earned +10 Green Points!',
message: 'Wet waste collection #GK-2026-89210 verified successfully by municipal inspector.',
timestamp: new Date(Date.now() - 3 * 3600 * 1000).toISOString(),
read: false,
type: 'points'
},
{
id: 'notif_3',
title: '🔥 8-Day Green Streak!',
message: 'Keep logging segregated waste daily to unlock the Eco Master +50 GP milestone.',
timestamp: new Date(Date.now() - 24 * 3600 * 1000).toISOString(),
read: true,
type: 'streak'
},
{
id: 'notif_4',
title: '📱 Recharge Successful',
message: '₹10 Jio recharge applied successfully using 100 Green Points.',
timestamp: new Date(Date.now() - 48 * 3600 * 1000).toISOString(),
read: true,
type: 'reward'
}
]
};
}
// Subscribe to state changes
subscribe(listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
notify() {
this.listeners.forEach(fn => fn(this.state));
}
// Set Active Role
setRole(role) {
this.state.currentRole = role;
this.notify();
}
// Submit New Waste Request
createWasteRequest(formData) {
const pointsMap = { wet: 10, dry: 7, harmful: 5 };
const newRequest = {
id: Formatters.generateRequestId(),
category: formData.category,
categoryName: formData.category === 'wet' ? 'Wet Waste (Organic)' : formData.category === 'dry' ? 'Dry Waste (Recyclable)' : 'Harmful Waste (Hazardous)',
pointsReward: pointsMap[formData.category] || 5,
quantityKg: parseFloat(formData.quantity) || 3.0,
subType: formData.subType || 'General segregated waste',
description: formData.description || '',
address: formData.address || this.state.user.address,
landmark: formData.landmark || '',
pickupType: formData.pickupType || 'doorstep',
scheduledDate: formData.scheduledDate || 'Today',
scheduledTime: formData.scheduledTime || 'Slot 10:00 AM - 12:00 PM',
createdAt: new Date().toISOString(),
status: 'created',
otp: Math.floor(1000 + Math.random() * 9000).toString(),
etaMinutes: 18,
photoUrl: formData.photoUrl || null
};
this.state.pickups.unshift(newRequest);
// Also push to worker queue for testing
this.state.workerQueue.unshift({
id: newRequest.id,
userName: this.state.user.name,
address: newRequest.address,
category: newRequest.category,
subType: newRequest.subType,
quantityKg: newRequest.quantityKg,
pointsReward: newRequest.pointsReward,
status: 'created',
otp: newRequest.otp,
photoUrl: newRequest.photoUrl || 'https://images.unsplash.com/photo-1532996122724-e3c354a0b15b?w=300&q=80'
});
this.addNotification({
title: '📋 Waste Request Created',
message: `Your pickup request #${newRequest.id} is confirmed. Status: Awaiting Worker Assignment.`,
type: 'pickup'
});
this.notify();
return newRequest;
}
// Worker Verifies Waste & Credits GP
verifyWasteSubmission(pickupId, approved = true, adjustedWeightKg = null) {
const pickup = this.state.pickups.find(p => p.id === pickupId);
const workerItem = this.state.workerQueue.find(p => p.id === pickupId);
if (approved) {
const points = pickup ? pickup.pointsReward : 10;
const weight = adjustedWeightKg || (pickup ? pickup.quantityKg : 4.0);
// Update Pickup Status
if (pickup) {
pickup.status = 'verified';
pickup.quantityKg = weight;
}
if (workerItem) workerItem.status = 'verified';
// Credit User Points & Impact
this.state.user.greenPoints += points;
this.state.user.lifetimeWasteKg += weight;
this.state.user.pickupsCompleted += 1;
this.state.user.co2SavedKg += Math.round(weight * 0.65 * 10) / 10;
this.state.user.treesEquivalent = Math.round((this.state.user.co2SavedKg / 13.5) * 10) / 10;
// Update Municipal Stats
this.state.cityStats.verifiedPickups += 1;
this.state.cityStats.greenPointsIssued += points;
// Add Ledger Transaction
this.state.transactions.unshift({
id: `TXN-${Math.floor(100000 + Math.random() * 900000)}`,
title: `${pickup ? pickup.categoryName : 'Waste'} Pickup Verified`,
category: 'EARN',
amountGp: points,
equivalentInr: Formatters.gpToInr(points),
date: new Date().toISOString(),
type: 'credit',
status: 'SUCCESS',
refId: pickupId
});
// Add Notification
this.addNotification({
title: `🌱 +${points} Green Points Credited!`,
message: `Waste pickup #${pickupId} (${weight} kg) has been verified. Green Wallet updated.`,
type: 'points'
});
this.notify();
return { success: true, points, weight };
} else {
if (pickup) pickup.status = 'rejected';
if (workerItem) workerItem.status = 'rejected';
this.addNotification({
title: `⚠️ Waste Submission Rejected`,
message: `Pickup #${pickupId} was rejected due to improper segregation. Please re-segregate and try again.`,
type: 'error'
});
this.notify();
return { success: false };
}
}
// Redeem Green Points (Mobile Recharge, Utility Bills, Vouchers)
redeemPoints(category, title, amountGp, metadata = '') {
if (this.state.user.greenPoints < amountGp) {
return { success: false, message: 'Insufficient Green Points balance' };
}
const inrValue = Formatters.gpToInr(amountGp);
this.state.user.greenPoints -= amountGp;
// Add Ledger Transaction
this.state.transactions.unshift({
id: `TXN-${Math.floor(100000 + Math.random() * 900000)}`,
title: title,
category: category,
amountGp: amountGp,
equivalentInr: inrValue,
date: new Date().toISOString(),
type: 'debit',
status: 'SUCCESS',
meta: metadata
});
this.state.cityStats.greenPointsRedeemed += amountGp;
this.addNotification({
title: `🎁 Redemption Successful`,
message: `${title} applied for ₹${inrValue} (${amountGp} GP deducted).`,
type: 'reward'
});
this.notify();
return { success: true, newBalanceGp: this.state.user.greenPoints, inrValue };
}
// Report Illegal Dumping
reportIllegalDumping(data) {
const newReport = {
id: `DUMP-2026-${Math.floor(100 + Math.random() * 900)}`,
location: data.location,
wasteType: data.wasteType,
reportedAt: new Date().toISOString(),
status: 'Submitted',
photoUrl: data.photoUrl || 'https://images.unsplash.com/photo-1611288875785-58586c06a4b1?w=300&q=80',
rewardGp: 20
};
this.state.illegalDumpingReports.unshift(newReport);
this.addNotification({
title: '🚨 Illegal Dumping Reported',
message: `Report #${newReport.id} registered. Once municipal inspection resolves this site, +20 GP will be credited.`,
type: 'info'
});
this.notify();
return newReport;
}
// Helper to Add Notification
addNotification(notif) {
this.state.notifications.unshift({
id: `notif_${Date.now()}`,
title: notif.title,
message: notif.message,
timestamp: new Date().toISOString(),
read: false,
type: notif.type || 'info'
});
}
// Mark all notifications as read
markAllNotificationsRead() {
this.state.notifications.forEach(n => n.read = true);
this.notify();
}
}
export const State = new StateStore();