lokesh341 commited on
Commit
0f87956
·
verified ·
1 Parent(s): 29ee83e

Rename templates/combined_summary.html to combined_summary.html

Browse files
combined_summary.html ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template, session, redirect, url_for
2
+ from salesforce import get_salesforce_connection
3
+ from flask import Blueprint, render_template, request, session, jsonify, redirect, url_for
4
+ import os
5
+ import re
6
+ from salesforce import get_salesforce_connection
7
+
8
+
9
+ combined_summary_blueprint = Blueprint('combined_summary', __name__)
10
+
11
+ # Initialize Salesforce connection
12
+ sf = get_salesforce_connection()
13
+
14
+ def escape_soql(value):
15
+ """Escape single quotes in SOQL query values to prevent injection."""
16
+ if value:
17
+ return value.replace("'", "\\'")
18
+ return value
19
+
20
+ @combined_summary_blueprint.route('/combined_summary')
21
+ def combined_summary():
22
+ email = session.get('user_email')
23
+ if not email:
24
+ print("No user email in session, redirecting to login")
25
+ return redirect(url_for('login'))
26
+
27
+ try:
28
+ # Sanitize email for SOQL query
29
+ safe_email = escape_soql(email)
30
+
31
+ # ====== FETCH REWARDS ======
32
+ reward_query = f"SELECT Id, Reward_Points__c FROM Customer_Login__c WHERE Email__c = '{safe_email}'"
33
+ reward_data = sf.query_all(reward_query)
34
+ if not reward_data.get("records"):
35
+ print(f"No reward info found for email: {email}")
36
+ return "Reward info not found", 404
37
+
38
+ user_points = reward_data["records"][0].get("Reward_Points__c", 0)
39
+
40
+ # Determine tier
41
+ tiers = {
42
+ "Bronze": 100,
43
+ "Silver": 200,
44
+ "Gold": 300,
45
+ "Platinum": 500
46
+ }
47
+ current_tier, next_tier = "Bronze", "Silver"
48
+ start_point, end_point = 0, 100
49
+ if user_points >= 100 and user_points < 200:
50
+ current_tier, next_tier = "Silver", "Gold"
51
+ start_point, end_point = 100, 200
52
+ elif user_points >= 200 and user_points < 300:
53
+ current_tier, next_tier = "Gold", "Platinum"
54
+ start_point, end_point = 200, 300
55
+ elif user_points >= 300:
56
+ current_tier, next_tier = "Platinum", "N/A"
57
+ start_point, end_point = 300, 500
58
+
59
+ progress_percentage = ((user_points - start_point) / (end_point - start_point)) * 100 if end_point != start_point else 100
60
+ points_needed_for_next_tier = max(0, end_point - user_points)
61
+
62
+ # ====== FETCH ORDER SUMMARY ======
63
+ order_query = f"""
64
+ SELECT Id, Customer_Name__c, Customer_Email__c, Total_Amount__c, Order_Details__c,
65
+ Order_Status__c, Discount__c, Total_Bill__c
66
+ FROM Order__c
67
+ WHERE Customer_Email__c = '{safe_email}'
68
+ ORDER BY CreatedDate DESC
69
+ LIMIT 1
70
+ """
71
+ order_result = sf.query_all(order_query)
72
+ if not order_result.get("records"):
73
+ print(f"No order found for email: {email}")
74
+ return "No order found", 404
75
+
76
+ order = order_result["records"][0]
77
+ order_details = order.get("Order_Details__c", "")
78
+ order_items = []
79
+ sector_names = set() # Use a set to ensure sector names are unique
80
+
81
+ for line in order_details.split('\n'):
82
+ item_parts = line.split('|')
83
+ if len(item_parts) >= 5:
84
+ item_name_raw = item_parts[0].strip()
85
+ item_name = ' '.join(item_name_raw.split(' ')[:-1]).strip()
86
+ safe_item_name = escape_soql(item_name)
87
+
88
+ menu_query = f"""
89
+ SELECT Name, Price__c, Image1__c,
90
+ Ingredient_1__r.Ingredient_Name__c, Ingredient_1__r.Ingredient_Image__c,
91
+ Ingredient_1__r.Health_Benefits__c, Ingredient_1__r.Fun_Facts__c,
92
+ Ingredient_2__r.Ingredient_Name__c, Ingredient_2__r.Ingredient_Image__c,
93
+ Ingredient_2__r.Health_Benefits__c, Ingredient_2__r.Fun_Facts__c,
94
+ Sector__c
95
+ FROM Menu_Item__c
96
+ WHERE Name = '{safe_item_name}'
97
+ """
98
+ menu_result = sf.query_all(menu_query)
99
+ ingredients = []
100
+
101
+ if menu_result.get("records"):
102
+ menu_item = menu_result["records"][0]
103
+
104
+ # Process Ingredient 1 if it exists
105
+ if menu_item.get('Ingredient_1__r') is not None:
106
+ ingredients.append({
107
+ "name": menu_item['Ingredient_1__r'].get('Ingredient_Name__c', ''),
108
+ "image": menu_item['Ingredient_1__r'].get('Ingredient_Image__c', ''),
109
+ "health_benefits": menu_item['Ingredient_1__r'].get('Health_Benefits__c', ''),
110
+ "fun_facts": menu_item['Ingredient_1__r'].get('Fun_Facts__c', '')
111
+ })
112
+
113
+ # Process Ingredient 2 if it exists
114
+ if menu_item.get('Ingredient_2__r') is not None:
115
+ ingredients.append({
116
+ "name": menu_item['Ingredient_2__r'].get('Ingredient_Name__c', ''),
117
+ "image": menu_item['Ingredient_2__r'].get('Ingredient_Image__c', ''),
118
+ "health_benefits": menu_item['Ingredient_2__r'].get('Health_Benefits__c', ''),
119
+ "fun_facts": menu_item['Ingredient_2__r'].get('Fun_Facts__c', '')
120
+ })
121
+
122
+ # Process the Sector__c field from Menu_Item__c
123
+ if menu_item.get('Sector__c'):
124
+ sector_names.update(menu_item['Sector__c'].split(',')) # Add sectors to the set
125
+
126
+ # Only add the item if ingredients are present
127
+ order_items.append({
128
+ "name": item_name,
129
+ "price": menu_item.get("Price__c", 0),
130
+ "image_url": menu_item.get("Image1__c", ''),
131
+ "ingredients": ingredients
132
+ })
133
+
134
+ # Fetch the sector details from the Sector_Detail__c object
135
+ sector_details = {}
136
+ for sector_name in sector_names:
137
+ safe_sector_name = escape_soql(sector_name.strip())
138
+ sector_query = f"""
139
+ SELECT Name, Image_URL__c, Description__c
140
+ FROM Sector_Detail__c
141
+ WHERE Name = '{safe_sector_name}'
142
+ """
143
+ sector_result = sf.query_all(sector_query)
144
+ if sector_result.get("records"):
145
+ sector_record = sector_result["records"][0]
146
+ sector_details[sector_name] = {
147
+ "image_url": sector_record.get('Image_URL__c', ''),
148
+ "description": sector_record.get('Description__c', '')
149
+ }
150
+
151
+ return render_template(
152
+ 'combined_summary.html',
153
+ user_points=round(user_points),
154
+ current_tier=current_tier,
155
+ next_tier=next_tier,
156
+ start_point=start_point,
157
+ end_point=end_point,
158
+ progress_percentage=round(progress_percentage),
159
+ points_needed_for_next_tier=round(points_needed_for_next_tier),
160
+ order_items=order_items,
161
+ sector_details=sector_details
162
+ )
163
+
164
+ except Exception as e:
165
+ print(f"Error in combined_summary: {str(e)}")
166
+ return f"Error: {str(e)}", 500
templates/combined_summary.html DELETED
@@ -1,606 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta http-equiv="x-ua-compatible" content="ie=edge">
7
- <title>Order Summary</title>
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <style>
10
- /* Custom animations */
11
- @keyframes fadeIn {
12
- from { opacity: 0; transform: translateY(10px); }
13
- to { opacity: 1; transform: translateY(0); }
14
- }
15
- @keyframes lightMoveUp {
16
- 0% { background-position: 0 100%; }
17
- 100% { background-position: 0 0%; }
18
- }
19
- @keyframes shine {
20
- from { transform: rotate(0deg) translateX(-100%); }
21
- to { transform: rotate(25deg) translateX(100%); }
22
- }
23
- .modal, .popup {
24
- animation: fadeIn 0.2s ease-in-out;
25
- }
26
- .progress-bar {
27
- transition: width 0.6s ease-in-out;
28
- }
29
- .ingredient-image {
30
- transition: transform 0.3s ease-in-out;
31
- }
32
- .ingredient-button:hover .ingredient-image {
33
- transform: scale(1.1);
34
- }
35
- .tier-badge {
36
- position: relative;
37
- overflow: hidden;
38
- background-color: #FFB347;
39
- }
40
- .tier-badge::after {
41
- content: '';
42
- position: absolute;
43
- top: -50%;
44
- left: -50%;
45
- width: 200%;
46
- height: 200%;
47
- background: linear-gradient(45deg, transparent 0%, rgba(255, 255, 255, 0.2) 50%, transparent 100%);
48
- animation: shine 3s infinite;
49
- }
50
- /* Custom class for order items */
51
- .custom-class {
52
- text-align: center;
53
- border-radius: 8px;
54
- border-width: 1px;
55
- border-color: #E5E7EB;
56
- padding: 20px !important;
57
- margin-bottom: 12px;
58
- background-color: #FFFFFF;
59
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
60
- }
61
- .history {
62
- display: flex;
63
- justify-content: center;
64
- font-size: 1.75rem;
65
- font-weight: 700;
66
- padding: 1.5rem;
67
- }
68
- /* Scrollable sector images */
69
- .sector-images-container {
70
- display: flex;
71
- overflow-x: auto;
72
- padding: 10px 0;
73
- gap: 15px;
74
- }
75
- .sector-image {
76
- max-width: 150px;
77
- height: 100px;
78
- object-fit: cover;
79
- cursor: pointer;
80
- border-radius: 6px;
81
- }
82
- .sector-name {
83
- text-align: center;
84
- font-size: 0.75rem;
85
- font-weight: 600;
86
- margin-top: 6px;
87
- max-width: 100px;
88
- color: #1F2937;
89
- }
90
- /* Full-width gradient header */
91
- .back-to-menu {
92
- position: fixed;
93
- top: 0;
94
- left: 0;
95
- right: 0;
96
- display: flex;
97
- align-items: center;
98
- padding: 14px 24px;
99
- background: linear-gradient(45deg, #FFA07A, #FFB347);
100
- color: #FFFFFF;
101
- font-size: 1.125rem;
102
- font-weight: 600;
103
- text-decoration: none;
104
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
105
- z-index: 9999;
106
- transition: background-color 0.3s ease, transform 0.2s ease;
107
- }
108
- .back-to-menu:hover {
109
- background: linear-gradient(45deg, #FF8C61, #FF9E2C);
110
- transform: translateY(-1px);
111
- }
112
- /* Content spacing */
113
- .container {
114
- margin-top: 90px;
115
- }
116
- /* Sector popup */
117
- #sector-popup {
118
- display: none;
119
- position: fixed;
120
- inset: 0;
121
- background: rgba(17, 24, 39, 0.6);
122
- justify-content: center;
123
- align-items: center;
124
- z-index: 10000;
125
- animation: fadeIn 0.2s ease-in-out;
126
- }
127
- #popup-content {
128
- background: linear-gradient(135deg, #FFFFFF 0%, #F9FAFB 100%);
129
- padding: 24px;
130
- border-radius: 12px;
131
- width: 90%;
132
- max-width: 550px;
133
- max-height: 85vh;
134
- overflow-y: auto;
135
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
136
- border: 1px solid #E5E7EB;
137
- position: relative;
138
- contain: layout;
139
- }
140
- .popup-header {
141
- position: relative;
142
- padding-bottom: 12px;
143
- margin-bottom: 16px;
144
- border-bottom: 2px solid #FFB347;
145
- display: flex;
146
- justify-content: space-between;
147
- align-items: center;
148
- }
149
- .popup-close {
150
- background: #FFB347;
151
- color: #FFFFFF;
152
- border-radius: 50%;
153
- width: 40px;
154
- height: 40px;
155
- display: flex;
156
- align-items: center;
157
- justify-content: center;
158
- font-size: 24px;
159
- font-weight: bold;
160
- border: none;
161
- cursor: pointer;
162
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
163
- line-height: 1;
164
- padding: 0;
165
- position: relative;
166
- overflow: hidden;
167
- will-change: transform, background;
168
- }
169
- .popup-close::before {
170
- content: '';
171
- position: absolute;
172
- top: 100%;
173
- left: 0;
174
- width: 100%;
175
- height: 100%;
176
- background: linear-gradient(to top, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
177
- animation: lightMoveUp 1.5s infinite linear;
178
- z-index: 0;
179
- }
180
- .popup-close:hover {
181
- background: #FF9E2C;
182
- transform: scale(1.1);
183
- }
184
- .popup-close span {
185
- position: relative;
186
- z-index: 1;
187
- }
188
- /* Ingredient modal */
189
- .ingredient-modal-container {
190
- display: none;
191
- position: fixed;
192
- inset: 0;
193
- background: rgba(0, 0, 0, 0.6);
194
- justify-content: center;
195
- align-items: center;
196
- z-index: 10000;
197
- animation: fadeIn 0.2s ease-in-out;
198
- }
199
- .ingredient-modal {
200
- background: linear-gradient(135deg, #FFFFFF 0%, #F9FAFB 100%);
201
- border-radius: 12px;
202
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
203
- border: 1px solid #E5E7EB;
204
- width: 90%;
205
- max-width: 500px;
206
- max-height: 90vh;
207
- overflow-y: auto;
208
- position: relative;
209
- contain: layout;
210
- }
211
- .modal-section {
212
- padding: 16px;
213
- border-left: 4px solid #2DD4BF;
214
- margin-bottom: 16px;
215
- background: #F9FAFB;
216
- border-radius: 6px;
217
- }
218
- /* Green border for ingredient images when section is visible */
219
- .ingredients-section:not(.hidden) .ingredient-image {
220
- border: 2px solid #10B981;
221
- border-radius: 6px;
222
- }
223
- /* Prevent body scroll when modal is open */
224
- body.modal-open {
225
- overflow: hidden;
226
- }
227
- /* Fallback messages */
228
- .no-data {
229
- text-align: center;
230
- color: #6B7280;
231
- font-size: 1.125rem;
232
- padding: 20px;
233
- background: #F9FAFB;
234
- border-radius: 8px;
235
- margin-bottom: 20px;
236
- }
237
- /* Responsive adjustments */
238
- @media (max-width: 640px) {
239
- .back-to-menu {
240
- padding: 12px 16px;
241
- font-size: 1rem;
242
- }
243
- .container {
244
- margin-top: 70px;
245
- }
246
- .history {
247
- font-size: 1.5rem;
248
- padding: 1rem;
249
- }
250
- .custom-class {
251
- padding: 16px !important;
252
- }
253
- .sector-image {
254
- max-width: 120px;
255
- height: 80px;
256
- }
257
- #popup-content {
258
- width: 95%;
259
- padding: 16px;
260
- max-height: 80vh;
261
- }
262
- .ingredient-modal {
263
- width: 95%;
264
- max-height: 80vh;
265
- }
266
- .popup-close {
267
- width: 40px;
268
- height: 40px;
269
- font-size: 24px;
270
- }
271
- .popup-close::before {
272
- animation: lightMoveUp 1.2s infinite linear;
273
- }
274
- }
275
- </style>
276
- </head>
277
- <body class="bg-gray-50">
278
- <a href="{{ url_for('menu.menu') }}" class="back-to-menu" aria-label="Go back to menu">
279
- <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
280
- <path d="M15 18l-6-6 6-6"></path>
281
- </svg>
282
- Back to Menu
283
- </a>
284
-
285
- <div class="container mx-auto p-6 pt-2">
286
- <!-- Order Confirmation -->
287
- <div class="section bg-white shadow-sm rounded-xl p-6 mb-6">
288
- <h1 class="text-center text-2xl font-bold text-orange-500">Order Confirmed!</h1>
289
- <p class="text-center text-gray-600 mt-2">Estimated delivery time: {{ delivery_time }} minutes</p>
290
- </div>
291
-
292
- <!-- Reward Status Section -->
293
- <div class="section bg-white rounded-xl shadow-lg p-6 mb-6">
294
- <div class="flex items-center justify-between mb-4 cursor-pointer" onclick="toggleTierDetails()" aria-expanded="false" aria-controls="tierDetails">
295
- <div id="tierBadge" class="w-8 h-8 p-1 rounded-full flex items-center justify-center tier-badge">
296
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-white">
297
- <path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"></path>
298
- <path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"></path>
299
- <path d="M4 22h16"></path>
300
- <path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"></path>
301
- <path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"></path>
302
- <path d="M9 9c0 .97.64 1.79 1.5 2.05A2 2 0 0 1 12 13c.82 0 1.54-.39 2-1"></path>
303
- <path d="M18 9H6a4 4 0 0 1 0-8h12a4 4 0 0 1 0 8Z"></path>
304
- </svg>
305
- </div>
306
- <span class="ml-2 text-xl font-bold text-orange-500" id="currentTier">{{ current_tier }} Tier</span>
307
- <svg id="arrowIcon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-600">
308
- <path id="arrowPath" d="M19 9l-7 7-7-7"></path>
309
- </svg>
310
- </div>
311
-
312
- <!-- Collapsible Content Section -->
313
- <div id="tierDetails" class="tier-details hidden">
314
- <div class="text-center mb-6">
315
- <p class="text-gray-600">Valid through December {{ validity_year }}</p>
316
- <p class="text-lg font-semibold mt-2 text-yellow-600" id="pointsDisplay">{{ user_points }} points</p>
317
- </div>
318
-
319
- <!-- Progress Bar -->
320
- <div class="relative h-4 bg-gray-200 rounded-full overflow-hidden mb-4">
321
- <div id="progressBar" class="absolute left-0 top-0 h-full progress-bar bg-teal-400" style="width: {{ progress_percentage }}%"></div>
322
- </div>
323
-
324
- <div class="flex justify-between text-sm text-gray-600 mb-4">
325
- <span id="startPoint">{{ start_point }}</span>
326
- <span id="endPoint">{{ end_point }}</span>
327
- </div>
328
-
329
- <p class="text-center text-gray-700">
330
- You need <span class="font-semibold text-gray-800" id="pointsNeeded">{{ points_needed_for_next_tier }}</span> more
331
- points to reach
332
- <span class="font-semibold text-orange-500" id="nextTier">{{ next_tier }}</span>
333
- </p>
334
-
335
- <!-- Tier Benefits -->
336
- <div class="mt-6 bg-gray-50 rounded-xl p-4">
337
- <h3 class="text-lg font-semibold mb-4 text-gray-800" id="benefitsTitle">
338
- Benefits of Reaching {{ next_tier }} Tier
339
- </h3>
340
- <ul class="space-y-3" id="benefitsList">
341
- {% if next_tier == "Silver" %}
342
- <li class="flex items-center">
343
- <span class="text-2xl mr-3">🏷️</span>
344
- <span class="text-gray-700">10% discount on next purchase</span>
345
- </li>
346
- <li class="flex items-center">
347
- <span class="text-2xl mr-3">🍹</span>
348
- <span class="text-gray-700">Free soft drink with your next order</span>
349
- </li>
350
- {% elif next_tier == "Gold" %}
351
- <li class="flex items-center">
352
- <span class="text-2xl mr-3">🏷️</span>
353
- <span class="text-gray-700">15% discount on next purchase</span>
354
- </li>
355
- <li class="flex items-center">
356
- <span class="text-2xl mr-3">🍰</span>
357
- <span class="text-gray-700">Free dessert on next order</span>
358
- </li>
359
- {% elif next_tier == "Platinum" %}
360
- <li class="flex items-center">
361
- <span class="text-2xl mr-3">🏷️</span>
362
- <span class="text-gray-700">20% discount on next purchase</span>
363
- </li>
364
- <li class="flex items-center">
365
- <span class="text-2xl mr-3">🍴</span>
366
- <span class="text-gray-700">Free entrée with your next order</span>
367
- </li>
368
- {% else %}
369
- <li class="text-gray-700">You've reached the highest tier! Enjoy exclusive benefits.</li>
370
- {% endif %}
371
- </ul>
372
- </div>
373
- </div>
374
- </div>
375
-
376
- <!-- Ingredient History -->
377
- <div class="history">
378
- <h1 class="text-center text-2xl font-bold text-orange-500">Ingredient History</h1>
379
- </div>
380
- {% if sector_details %}
381
- <div class="sector-images-container">
382
- {% for sector_name, sector in sector_details.items() %}
383
- <div class="sector-item">
384
- <img src="{{ sector.image_url | default('/static/placeholder.jpg') }}" alt="{{ sector_name }}" class="sector-image lazyload" loading="lazy" onclick="showSectorDescription('{{ sector.description | escape }}')">
385
- <h3 class="sector-name">{{ sector_name }}</h3>
386
- </div>
387
- {% endfor %}
388
- </div>
389
- {% else %}
390
- <div class="no-data">No sector details available.</div>
391
- {% endif %}
392
-
393
- <!-- Popup for Sector Description -->
394
- <div id="sector-popup" class="popup" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="sector-popup-title">
395
- <div id="popup-content">
396
- <div class="popup-header flex justify-between items-center">
397
- <h3 id="sector-popup-title" class="text-xl font-semibold text-gray-800">Origin History</h3>
398
- <button onclick="closePopup()" class="popup-close" aria-label="Close sector description popup"><span>×</span></button>
399
- </div>
400
- <p id="sector-description" class="text-gray-700 leading-relaxed"></p>
401
- </div>
402
- </div>
403
-
404
- <!-- Order Items Section -->
405
- <div class="text-center text-2xl font-bold text-orange-500 mb-6">Previous Orders</div>
406
- {% if order_items %}
407
- {% for item in order_items %}
408
- <div class="custom-class">
409
- <img src="{{ item.image_url | default('/static/placeholder.jpg') }}" alt="{{ item.name | escape }}" class="w-full h-48 object-cover rounded-xl mb-4 lazyload" loading="lazy" />
410
- <h3 class="text-lg font-semibold text-gray-800">{{ item.name | escape }}</h3>
411
- <p class="text-gray-600 mb-2">${{ "%.2f"|format(item.price | default(0)) }}</p>
412
-
413
- {% if item.ingredients %}
414
- <button id="ingredientsToggleButton{{ loop.index }}" class="text-green-600 text-sm font-semibold mt-2 ml-2 text-left pb-4 hover:text-green-700" onclick="toggleIngredients({{ loop.index }})" aria-expanded="false" aria-controls="ingredientsSection{{ loop.index }}">
415
- Show Ingredients
416
- </button>
417
-
418
- <div id="ingredientsSection{{ loop.index }}" class="ingredients-section hidden mt-4">
419
- <div class="grid grid-cols-2 gap-4">
420
- {% for ingredient in item.ingredients %}
421
- <button
422
- onclick="showIngredientDetails(
423
- '{{ loop.index }}',
424
- '{{ ingredient.name | escape }}',
425
- '{{ ingredient.image | default('/static/placeholder.jpg') | escape }}',
426
- '{{ ingredient.health_benefits | escape }}',
427
- '{{ ingredient.fun_facts | escape }}'
428
- )"
429
- class="relative group ingredient-button"
430
- aria-label="View details for {{ ingredient.name }}"
431
- >
432
- <div class="overflow-hidden rounded-lg">
433
- <img
434
- src="{{ ingredient.image | default('/static/placeholder.jpg') }}"
435
- alt="{{ ingredient.name | escape }}"
436
- class="w-full h-32 object-cover ingredient-image lazyload"
437
- loading="lazy"
438
- />
439
- </div>
440
- <p class="mt-2 text-center text-sm font-medium text-gray-800">{{ ingredient.name | escape }}</p>
441
- </button>
442
- {% endfor %}
443
- </div>
444
- </div>
445
- {% endif %}
446
- </div>
447
-
448
- <!-- Modal for Ingredients -->
449
- <div id="ingredientModal{{ loop.index }}" class="ingredient-modal-container hidden modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle{{ loop.index }}">
450
- <div class="ingredient-modal">
451
- <div class="p-6">
452
- <div class="popup-header flex justify-between items-center mb-4">
453
- <h3 id="modalTitle{{ loop.index }}" class="text-xl font-semibold text-gray-800"></h3>
454
- <button onclick="closeModal({{ loop.index }})" class="popup-close" aria-label="Close ingredient details modal"><span>×</span></button>
455
- </div>
456
-
457
- <img id="modalImage{{ loop.index }}" src="" alt="" class="w-full h-48 object-cover rounded-xl mb-4 border border-gray-200 lazyload" loading="lazy" />
458
-
459
- <div class="space-y-4">
460
- <div class="modal-section">
461
- <h4 class="font-semibold mb-2 text-gray-800 flex items-center">
462
- <svg class="w-5 h-5 mr-2 text-teal-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
463
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
464
- </svg>
465
- Health Benefits
466
- </h4>
467
- <ul id="healthBenefits{{ loop.index }}" class="list-disc list-inside text-gray-700"></ul>
468
- </div>
469
-
470
- <div class="modal-section">
471
- <h4 class="font-semibold mb-2 text-gray-800 flex items-center">
472
- <svg class="w-5 h-5 mr-2 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
473
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
474
- </svg>
475
- Fun Facts
476
- </h4>
477
- <ul id="funFacts{{ loop.index }}" class="list-disc list-inside text-gray-700"></ul>
478
- </div>
479
- </div>
480
- </div>
481
- </div>
482
- </div>
483
- {% endfor %}
484
- {% else %}
485
- <div class="no-data">No previous orders found.</div>
486
- {% endif %}
487
- </div>
488
-
489
- <script>
490
- // Debounce function for performance
491
- function debounce(func, wait) {
492
- let timeout;
493
- return function executedFunction(...args) {
494
- const later = () => {
495
- clearTimeout(timeout);
496
- func(...args);
497
- };
498
- clearTimeout(timeout);
499
- timeout = setTimeout(later, wait);
500
- };
501
- }
502
-
503
- // Show sector description popup
504
- function showSectorDescription(description) {
505
- try {
506
- document.getElementById('sector-description').innerText = description || 'No description available.';
507
- document.getElementById('sector-popup').style.display = 'flex';
508
- document.body.classList.add('modal-open');
509
- } catch (e) {
510
- console.error('Error showing sector description:', e);
511
- }
512
- }
513
-
514
- // Close sector popup
515
- function closePopup() {
516
- document.getElementById('sector-popup').style.display = 'none';
517
- document.body.classList.remove('modal-open');
518
- }
519
-
520
- // Toggle tier details
521
- function toggleTierDetails() {
522
- const tierDetails = document.getElementById('tierDetails');
523
- const arrowPath = document.getElementById('arrowPath');
524
- const toggleButton = tierDetails.parentElement.querySelector('[aria-controls="tierDetails"]');
525
- const isExpanded = !tierDetails.classList.contains('hidden');
526
- tierDetails.classList.toggle('hidden');
527
- arrowPath.setAttribute('d', tierDetails.classList.contains('hidden') ? 'M19 9l-7 7-7-7' : 'M19 15l-7-7-7 7');
528
- toggleButton.setAttribute('aria-expanded', !isExpanded);
529
- }
530
-
531
- // Toggle ingredients section
532
- function toggleIngredients(index) {
533
- try {
534
- const currentSection = document.getElementById('ingredientsSection' + index);
535
- const currentButton = document.getElementById('ingredientsToggleButton' + index);
536
- const isCurrentlyVisible = !currentSection.classList.contains('hidden');
537
-
538
- document.querySelectorAll('[id^="ingredientsSection"]').forEach(section => section.classList.add('hidden'));
539
- document.querySelectorAll('[id^="ingredientsToggleButton"]').forEach(button => {
540
- button.textContent = 'Show Ingredients';
541
- button.setAttribute('aria-expanded', 'false');
542
- });
543
-
544
- if (!isCurrentlyVisible) {
545
- currentSection.classList.remove('hidden');
546
- currentButton.textContent = 'Hide Ingredients';
547
- currentButton.setAttribute('aria-expanded', 'true');
548
- }
549
- } catch (e) {
550
- console.error('Error toggling ingredients:', e);
551
- }
552
- }
553
-
554
- // Show ingredient details modal
555
- function showIngredientDetails(index, name, image, healthBenefits, funFacts) {
556
- try {
557
- const healthBenefitsList = (healthBenefits || '').split(',').filter(item => item.trim());
558
- const funFactsList = (funFacts || '').split(',').filter(item => item.trim());
559
- document.getElementById("modalTitle" + index).textContent = name || 'Unknown Ingredient';
560
- document.getElementById("modalImage" + index).src = image || '/static/placeholder.jpg';
561
- const healthBenefitsUl = document.getElementById("healthBenefits" + index);
562
- const funFactsUl = document.getElementById("funFacts" + index);
563
- healthBenefitsUl.innerHTML = '';
564
- funFactsUl.innerHTML = '';
565
- healthBenefitsList.length ? healthBenefitsList.forEach(item => {
566
- const li = document.createElement("li");
567
- li.textContent = item.trim();
568
- healthBenefitsUl.appendChild(li);
569
- }) : healthBenefitsUl.innerHTML = '<li>No health benefits available.</li>';
570
- funFactsList.length ? funFactsList.forEach(item => {
571
- const li = document.createElement("li");
572
- li.textContent = item.trim();
573
- funFactsUl.appendChild(li);
574
- }) : funFactsUl.innerHTML = '<li>No fun facts available.</li>';
575
- document.getElementById("ingredientModal" + index).style.display = "flex";
576
- document.body.classList.add('modal-open');
577
- } catch (e) {
578
- console.error('Error showing ingredient details:', e);
579
- }
580
- }
581
-
582
- // Close ingredient modal
583
- function closeModal(index) {
584
- try {
585
- document.getElementById("ingredientModal" + index).style.display = "none";
586
- document.body.classList.remove('modal-open');
587
- } catch (e) {
588
- console.error('Error closing modal:', e);
589
- }
590
- }
591
-
592
- // Close modals on outside click
593
- document.addEventListener('click', function(e) {
594
- if (e.target.id === 'sector-popup') {
595
- closePopup();
596
- }
597
- document.querySelectorAll('.ingredient-modal-container').forEach(modal => {
598
- if (e.target === modal) {
599
- const index = modal.id.replace('ingredientModal', '');
600
- closeModal(index);
601
- }
602
- });
603
- });
604
- </script>
605
- </body>
606
- </html>