SakibAhmed commited on
Commit
51a4854
·
verified ·
1 Parent(s): d0766b2

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +203 -160
  2. templates/index.html +69 -7
app.py CHANGED
@@ -1,160 +1,203 @@
1
- from flask import Flask, request, jsonify, render_template
2
- from flask_cors import CORS # Import CORS
3
- from dotenv import load_dotenv
4
- import os
5
- import logging
6
- from datetime import datetime
7
- import csv
8
- import json # For logging complex items
9
- from uuid import uuid4
10
-
11
- load_dotenv()
12
-
13
- app = Flask(__name__, template_folder='templates', static_folder='static')
14
- CORS(app) # Enable CORS for all routes and all origins by default
15
-
16
- # Configure logging for the mock API
17
- MOCK_API_LOG_LEVEL = os.getenv("MOCK_API_LOG_LEVEL", "INFO").upper()
18
- mock_numeric_level = getattr(logging, MOCK_API_LOG_LEVEL, logging.INFO)
19
- logging.basicConfig(
20
- level=mock_numeric_level,
21
- format='%(asctime)s - MockOrderAPI - %(levelname)s - %(message)s'
22
- )
23
- mock_logger = logging.getLogger(__name__)
24
- mock_logger.info(f"Mock Order API logging level set to: {MOCK_API_LOG_LEVEL}")
25
-
26
-
27
- MOCK_API_SECRET_KEY = os.getenv('MOCK_API_FLASK_SECRET_KEY', 'a_different_secret_key_for_mock_api')
28
- if MOCK_API_SECRET_KEY == 'a_different_secret_key_for_mock_api' and not os.getenv('MOCK_API_FLASK_SECRET_KEY'):
29
- mock_logger.warning("Using default Flask secret key for Mock API. Set MOCK_API_FLASK_SECRET_KEY in .env if sessions are used.")
30
- app.secret_key = MOCK_API_SECRET_KEY
31
-
32
- ORDERS_CSV_FILE = 'received_orders.csv'
33
- CSV_HEADER = ['timestamp', 'order_id', 'session_id', 'user_email', 'business_id', 'canonical_user_id', 'items_json_array']
34
-
35
- def initialize_orders_csv():
36
- if not os.path.exists(ORDERS_CSV_FILE):
37
- try:
38
- with open(ORDERS_CSV_FILE, 'w', newline='', encoding='utf-8') as f:
39
- writer = csv.writer(f)
40
- writer.writerow(CSV_HEADER)
41
- mock_logger.info(f"Orders CSV file '{ORDERS_CSV_FILE}' created.")
42
- except IOError as e:
43
- mock_logger.error(f"Failed to create orders CSV file '{ORDERS_CSV_FILE}': {e}", exc_info=True)
44
-
45
- @app.route('/')
46
- def index():
47
- """Serves the main dashboard page (index.html)."""
48
- return render_template('index.html')
49
-
50
- @app.route('/api/v1/orders/list', methods=['GET'])
51
- def list_orders():
52
- initialize_orders_csv()
53
- orders = []
54
- try:
55
- if not os.path.exists(ORDERS_CSV_FILE):
56
- mock_logger.warning(f"Attempted to list orders, but '{ORDERS_CSV_FILE}' does not exist.")
57
- return jsonify({"status": "success", "orders": [], "message": "No orders file found."}), 200
58
-
59
- with open(ORDERS_CSV_FILE, 'r', newline='', encoding='utf-8') as f:
60
- reader = csv.DictReader(f)
61
- for row_number, row in enumerate(reader, 1): # Start row_number from 1 for logging
62
- # Basic check for expected number of columns based on header
63
- if len(row) != len(CSV_HEADER):
64
- mock_logger.warning(f"Row {row_number} in '{ORDERS_CSV_FILE}' has an incorrect number of columns. Expected {len(CSV_HEADER)}, got {len(row)}. Skipping row: {row}")
65
- continue # Skip malformed rows
66
-
67
- try:
68
- items_str = row.get('items_json_array', '[]')
69
- # Handle potential empty strings or malformed JSON before parsing
70
- if not items_str or items_str.isspace():
71
- row['items_json_array'] = []
72
- else:
73
- row['items_json_array'] = json.loads(items_str)
74
-
75
- except json.JSONDecodeError:
76
- mock_logger.warning(f"Could not parse items_json_array for order {row.get('order_id', 'N/A')} in row {row_number}: '{row.get('items_json_array', '')}'")
77
- row['items_json_array'] = [] # Default to empty list on error
78
- except TypeError: # Handles if items_json_array is None from DictReader
79
- mock_logger.warning(f"items_json_array was None for order {row.get('order_id', 'N/A')} in row {row_number}. Defaulting to empty list.")
80
- row['items_json_array'] = []
81
-
82
- orders.append(row)
83
-
84
- orders.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
85
- mock_logger.info(f"Successfully retrieved {len(orders)} orders from CSV.")
86
- return jsonify({"status": "success", "orders": orders}), 200
87
- except FileNotFoundError:
88
- mock_logger.info(f"Orders CSV file '{ORDERS_CSV_FILE}' not found when listing orders.")
89
- return jsonify({"status": "success", "orders": [], "message": "No orders recorded yet."}), 200
90
- except Exception as e:
91
- mock_logger.error(f"Error reading or parsing orders CSV: {e}", exc_info=True)
92
- return jsonify({"status": "error", "message": "Failed to retrieve orders."}), 500
93
-
94
-
95
- @app.route('/api/v1/orders/place', methods=['POST'])
96
- def place_order():
97
- initialize_orders_csv()
98
- try:
99
- order_data = request.json
100
- if not order_data:
101
- mock_logger.warning("Received empty payload for order placement.")
102
- return jsonify({"status": "error", "message": "Empty payload."}), 400
103
-
104
- user_info = order_data.get("user_info", {})
105
- order_items = order_data.get("order_items", [])
106
-
107
- if not isinstance(order_items, list) or not order_items:
108
- mock_logger.warning(f"Invalid or empty order_items received: {order_items}")
109
- return jsonify({"status": "error", "message": "Invalid or empty 'order_items' list is required."}), 400
110
-
111
- mock_logger.info(f"--- ORDER RECEIVED by Mock Order API ---")
112
- mock_logger.info(f"Timestamp: {datetime.now().isoformat()}")
113
- mock_logger.info(f"User Info: {json.dumps(user_info)}")
114
- mock_logger.info("Order Items:")
115
- for item in order_items:
116
- mock_logger.info(f" - Product: {item.get('product_name', 'N/A')}, SKU: {item.get('sku', 'N/A')}, Quantity: {item.get('quantity', 'N/A')}, Unit: {item.get('unit', 'N/A')}")
117
-
118
- simulated_order_id = f"MOCK-{uuid4().hex[:8].upper()}"
119
-
120
- try:
121
- # Ensure the file has a header if it's empty or just created
122
- is_new_file = not os.path.exists(ORDERS_CSV_FILE) or os.path.getsize(ORDERS_CSV_FILE) == 0
123
-
124
- with open(ORDERS_CSV_FILE, 'a', newline='', encoding='utf-8') as f:
125
- writer = csv.writer(f)
126
- if is_new_file: # Should be handled by initialize_orders_csv, but as a safeguard
127
- writer.writerow(CSV_HEADER)
128
- mock_logger.info(f"Header written to new/empty {ORDERS_CSV_FILE} before appending order.")
129
-
130
- items_json_str = json.dumps(order_items)
131
- writer.writerow([
132
- datetime.now().isoformat(),
133
- simulated_order_id,
134
- user_info.get('session_id', 'N/A'),
135
- user_info.get('email', 'N/A'),
136
- user_info.get('business_id', 'N/A'),
137
- user_info.get('canonical_user_id', 'N/A'),
138
- items_json_str
139
- ])
140
- mock_logger.info(f"Order {simulated_order_id} successfully written to {ORDERS_CSV_FILE}")
141
- except IOError as e_csv:
142
- mock_logger.error(f"Failed to write order {simulated_order_id} to CSV: {e_csv}", exc_info=True)
143
- return jsonify({"status": "success", "message": "Order received but failed to log locally.", "order_id": simulated_order_id, "warning": "Internal logging error"}), 200
144
-
145
-
146
- mock_logger.info(f"--- END OF ORDER PROCESSING (Order ID: {simulated_order_id}) ---")
147
- return jsonify({"status": "success", "message": "Order received and logged by Mock API.", "order_id": simulated_order_id}), 200
148
-
149
- except Exception as e:
150
- mock_logger.error(f"Unexpected error in place_order: {e}", exc_info=True)
151
- return jsonify({"status": "error", "message": "An unexpected internal server error occurred."}), 500
152
-
153
- @app.route('/health', methods=['GET'])
154
- def health_check():
155
- return jsonify({"status": "healthy", "message": "Mock Order API is running."}), 200
156
-
157
-
158
- if __name__ == '__main__':
159
- debug_mode = os.getenv('FLASK_DEBUG', 'False').lower() == 'true' # Example of using FLASK_DEBUG env var
160
- app.run(host='0.0.0.0', port=7860, debug=debug_mode)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template
2
+ from flask_cors import CORS # Import CORS
3
+ from dotenv import load_dotenv
4
+ import os
5
+ import logging
6
+ from datetime import datetime
7
+ import csv
8
+ import json # For logging complex items
9
+ from uuid import uuid4
10
+
11
+ load_dotenv()
12
+
13
+ app = Flask(__name__, template_folder='templates', static_folder='static')
14
+ CORS(app) # Enable CORS for all routes and all origins by default
15
+
16
+ # Configure logging for the mock API
17
+ MOCK_API_LOG_LEVEL = os.getenv("MOCK_API_LOG_LEVEL", "INFO").upper()
18
+ mock_numeric_level = getattr(logging, MOCK_API_LOG_LEVEL, logging.INFO)
19
+ logging.basicConfig(
20
+ level=mock_numeric_level,
21
+ format='%(asctime)s - MockOrderAPI - %(levelname)s - %(message)s'
22
+ )
23
+ mock_logger = logging.getLogger(__name__)
24
+ mock_logger.info(f"Mock Order API logging level set to: {MOCK_API_LOG_LEVEL}")
25
+
26
+
27
+ MOCK_API_SECRET_KEY = os.getenv('MOCK_API_FLASK_SECRET_KEY', 'a_different_secret_key_for_mock_api')
28
+ if MOCK_API_SECRET_KEY == 'a_different_secret_key_for_mock_api' and not os.getenv('MOCK_API_FLASK_SECRET_KEY'):
29
+ mock_logger.warning("Using default Flask secret key for Mock API. Set MOCK_API_FLASK_SECRET_KEY in .env if sessions are used.")
30
+ app.secret_key = MOCK_API_SECRET_KEY
31
+
32
+ ORDERS_CSV_FILE = 'received_orders.csv'
33
+ CSV_HEADER = ['timestamp', 'order_id', 'session_id', 'user_email', 'business_id', 'canonical_user_id', 'items_json_array']
34
+
35
+ def initialize_orders_csv():
36
+ if not os.path.exists(ORDERS_CSV_FILE):
37
+ try:
38
+ with open(ORDERS_CSV_FILE, 'w', newline='', encoding='utf-8') as f:
39
+ writer = csv.writer(f)
40
+ writer.writerow(CSV_HEADER)
41
+ mock_logger.info(f"Orders CSV file '{ORDERS_CSV_FILE}' created.")
42
+ except IOError as e:
43
+ mock_logger.error(f"Failed to create orders CSV file '{ORDERS_CSV_FILE}': {e}", exc_info=True)
44
+
45
+ @app.route('/')
46
+ def index():
47
+ """Serves the main dashboard page (index.html)."""
48
+ return render_template('index.html')
49
+
50
+ @app.route('/api/v1/orders/list', methods=['GET'])
51
+ def list_orders():
52
+ initialize_orders_csv() # Ensure CSV exists with header if it's the first operation
53
+ orders = []
54
+ try:
55
+ # This check is slightly redundant due to initialize_orders_csv, but safe
56
+ if not os.path.exists(ORDERS_CSV_FILE) or os.path.getsize(ORDERS_CSV_FILE) == 0:
57
+ mock_logger.info(f"'{ORDERS_CSV_FILE}' is missing or empty. Returning no orders.")
58
+ # initialize_orders_csv should have created it with a header if missing.
59
+ # If it's empty after that, it implies no data.
60
+ return jsonify({"status": "success", "orders": [], "message": "No orders recorded yet."}), 200
61
+
62
+ with open(ORDERS_CSV_FILE, 'r', newline='', encoding='utf-8') as f:
63
+ reader = csv.DictReader(f)
64
+ if reader.fieldnames != CSV_HEADER:
65
+ mock_logger.error(f"CSV header mismatch in '{ORDERS_CSV_FILE}'. Expected {CSV_HEADER}, got {reader.fieldnames}. Returning empty list.")
66
+ return jsonify({"status": "error", "orders": [], "message": "CSV file header corruption."}), 500
67
+
68
+ for row_number, row in enumerate(reader, 1):
69
+ if len(row) != len(CSV_HEADER): # Check based on header in file
70
+ mock_logger.warning(f"Row {row_number} in '{ORDERS_CSV_FILE}' has an incorrect number of columns. Expected {len(reader.fieldnames)}, got {len(row)}. Skipping row: {row}")
71
+ continue
72
+
73
+ try:
74
+ items_str = row.get('items_json_array', '[]')
75
+ if not items_str or items_str.isspace():
76
+ row['items_json_array'] = []
77
+ else:
78
+ row['items_json_array'] = json.loads(items_str)
79
+ except json.JSONDecodeError:
80
+ mock_logger.warning(f"Could not parse items_json_array for order {row.get('order_id', 'N/A')} in row {row_number}: '{row.get('items_json_array', '')}'")
81
+ row['items_json_array'] = []
82
+ except TypeError:
83
+ mock_logger.warning(f"items_json_array was None for order {row.get('order_id', 'N/A')} in row {row_number}. Defaulting to empty list.")
84
+ row['items_json_array'] = []
85
+
86
+ orders.append(row)
87
+
88
+ orders.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
89
+ mock_logger.info(f"Successfully retrieved {len(orders)} orders from CSV.")
90
+ return jsonify({"status": "success", "orders": orders}), 200
91
+ except FileNotFoundError: # Should be less likely due to initialize_orders_csv
92
+ mock_logger.info(f"Orders CSV file '{ORDERS_CSV_FILE}' not found when listing orders.")
93
+ return jsonify({"status": "success", "orders": [], "message": "No orders recorded yet."}), 200
94
+ except Exception as e:
95
+ mock_logger.error(f"Error reading or parsing orders CSV: {e}", exc_info=True)
96
+ return jsonify({"status": "error", "message": "Failed to retrieve orders."}), 500
97
+
98
+
99
+ @app.route('/api/v1/orders/place', methods=['POST'])
100
+ def place_order():
101
+ initialize_orders_csv() # Ensure CSV exists with header
102
+ try:
103
+ order_data = request.json
104
+ if not order_data:
105
+ mock_logger.warning("Received empty payload for order placement.")
106
+ return jsonify({"status": "error", "message": "Empty payload."}), 400
107
+
108
+ user_info = order_data.get("user_info", {})
109
+ order_items = order_data.get("order_items", [])
110
+
111
+ if not isinstance(order_items, list) or not order_items:
112
+ mock_logger.warning(f"Invalid or empty order_items received: {order_items}")
113
+ return jsonify({"status": "error", "message": "Invalid or empty 'order_items' list is required."}), 400
114
+
115
+ mock_logger.info(f"--- ORDER RECEIVED by Mock Order API ---")
116
+ mock_logger.info(f"Timestamp: {datetime.now().isoformat()}")
117
+ mock_logger.info(f"User Info: {json.dumps(user_info)}")
118
+ mock_logger.info("Order Items:")
119
+ for item in order_items: # SKU will be logged if present in received data
120
+ mock_logger.info(f" - Product: {item.get('product_name', 'N/A')}, SKU: {item.get('sku', 'N/A')}, Quantity: {item.get('quantity', 'N/A')}, Unit: {item.get('unit', 'N/A')}")
121
+
122
+ simulated_order_id = f"MOCK-{uuid4().hex[:8].upper()}"
123
+
124
+ try:
125
+ # initialize_orders_csv ensures header. 'a' mode appends.
126
+ with open(ORDERS_CSV_FILE, 'a', newline='', encoding='utf-8') as f:
127
+ writer = csv.writer(f)
128
+ items_json_str = json.dumps(order_items)
129
+ writer.writerow([
130
+ datetime.now().isoformat(),
131
+ simulated_order_id,
132
+ user_info.get('session_id', 'N/A'),
133
+ user_info.get('email', 'N/A'),
134
+ user_info.get('business_id', 'N/A'),
135
+ user_info.get('canonical_user_id', 'N/A'),
136
+ items_json_str
137
+ ])
138
+ mock_logger.info(f"Order {simulated_order_id} successfully written to {ORDERS_CSV_FILE}")
139
+ except IOError as e_csv:
140
+ mock_logger.error(f"Failed to write order {simulated_order_id} to CSV: {e_csv}", exc_info=True)
141
+ return jsonify({"status": "success", "message": "Order received but failed to log locally.", "order_id": simulated_order_id, "warning": "Internal logging error"}), 200
142
+
143
+ mock_logger.info(f"--- END OF ORDER PROCESSING (Order ID: {simulated_order_id}) ---")
144
+ return jsonify({"status": "success", "message": "Order received and logged by Mock API.", "order_id": simulated_order_id}), 200
145
+
146
+ except Exception as e:
147
+ mock_logger.error(f"Unexpected error in place_order: {e}", exc_info=True)
148
+ return jsonify({"status": "error", "message": "An unexpected internal server error occurred."}), 500
149
+
150
+ @app.route('/api/v1/orders/delete/<order_id>', methods=['DELETE'])
151
+ def delete_order_route(order_id):
152
+ if not os.path.exists(ORDERS_CSV_FILE):
153
+ mock_logger.warning(f"Attempted to delete order {order_id}, but '{ORDERS_CSV_FILE}' does not exist.")
154
+ return jsonify({"status": "error", "message": f"Order {order_id} not found (orders data file missing)."}), 404
155
+
156
+ rows_to_keep = []
157
+ order_found = False
158
+
159
+ try:
160
+ with open(ORDERS_CSV_FILE, 'r', newline='', encoding='utf-8') as f_read:
161
+ reader = csv.DictReader(f_read)
162
+ # Validate header before proceeding
163
+ if reader.fieldnames != CSV_HEADER:
164
+ mock_logger.error(f"CSV header mismatch in '{ORDERS_CSV_FILE}' during delete. Expected {CSV_HEADER}, got {reader.fieldnames}.")
165
+ return jsonify({"status": "error", "message": "CSV file header corruption. Cannot process deletion."}), 500
166
+
167
+ for row in reader:
168
+ if row.get('order_id') == order_id:
169
+ order_found = True
170
+ mock_logger.info(f"Order {order_id} identified for deletion.")
171
+ else:
172
+ rows_to_keep.append(row)
173
+
174
+ if not order_found:
175
+ mock_logger.warning(f"Order {order_id} not found in '{ORDERS_CSV_FILE}' for deletion.")
176
+ return jsonify({"status": "error", "message": f"Order ID {order_id} not found."}), 404
177
+
178
+ # Rewrite the CSV file with the remaining orders
179
+ with open(ORDERS_CSV_FILE, 'w', newline='', encoding='utf-8') as f_write:
180
+ writer = csv.DictWriter(f_write, fieldnames=CSV_HEADER)
181
+ writer.writeheader()
182
+ writer.writerows(rows_to_keep)
183
+
184
+ mock_logger.info(f"Order {order_id} successfully deleted. '{ORDERS_CSV_FILE}' has been updated.")
185
+ return jsonify({"status": "success", "message": f"Order {order_id} deleted successfully."}), 200
186
+
187
+ except FileNotFoundError:
188
+ mock_logger.error(f"'{ORDERS_CSV_FILE}' vanished during delete operation for {order_id}.")
189
+ return jsonify({"status": "error", "message": "Orders file disappeared during operation."}), 500
190
+ except Exception as e:
191
+ mock_logger.error(f"Error during deletion of order {order_id}: {e}", exc_info=True)
192
+ return jsonify({"status": "error", "message": "An internal error occurred while attempting to delete the order."}), 500
193
+
194
+ @app.route('/health', methods=['GET'])
195
+ def health_check():
196
+ return jsonify({"status": "healthy", "message": "Mock Order API is running."}), 200
197
+
198
+
199
+ if __name__ == '__main__':
200
+ port = 7860 # Define port for clarity and consistency
201
+ debug_mode = os.getenv('FLASK_DEBUG', 'False').lower() == 'true'
202
+ mock_logger.info(f"Starting Mock Order API server on 0.0.0.0:{port} (Debug: {debug_mode})")
203
+ app.run(host='0.0.0.0', port=port, debug=debug_mode)
templates/index.html CHANGED
@@ -121,6 +121,22 @@
121
  .inner-items-table td {
122
  word-break: break-word; /* Prevent long product names from breaking layout */
123
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
 
126
  /* Responsive improvements */
@@ -145,6 +161,10 @@
145
  padding: 3px 5px;
146
  font-size: 0.9em; /* Relative to parent cell font size */
147
  }
 
 
 
 
148
  }
149
  </style>
150
  </head>
@@ -166,6 +186,7 @@
166
  <th>Business ID</th>
167
  <th>Canonical User ID</th>
168
  <th>Items</th>
 
169
  </tr>
170
  </thead>
171
  <tbody>
@@ -183,7 +204,6 @@
183
  statusMessage.textContent = 'Refreshing orders...';
184
  refreshButton.disabled = true;
185
  try {
186
- // Assuming the API is served from the same origin or CORS is configured
187
  const response = await fetch('/api/v1/orders/list');
188
  if (!response.ok) {
189
  throw new Error(`HTTP error! status: ${response.status}`);
@@ -211,7 +231,7 @@
211
  if (!orders || orders.length === 0) {
212
  const row = ordersTableBody.insertRow();
213
  const cell = row.insertCell();
214
- cell.colSpan = 7; // Number of columns
215
  cell.textContent = 'No orders found.';
216
  cell.style.textAlign = 'center';
217
  return;
@@ -230,16 +250,14 @@
230
  const itemsCell = row.insertCell();
231
  if (order.items_json_array && Array.isArray(order.items_json_array) && order.items_json_array.length > 0) {
232
  const itemsTable = document.createElement('table');
233
- itemsTable.classList.add('inner-items-table'); // For styling
234
 
235
  const tHead = itemsTable.createTHead();
236
  const headerRow = tHead.insertRow();
237
  const thProduct = document.createElement('th');
238
  thProduct.textContent = 'Product';
239
  headerRow.appendChild(thProduct);
240
- const thSku = document.createElement('th');
241
- thSku.textContent = 'SKU';
242
- headerRow.appendChild(thSku);
243
  const thQty = document.createElement('th');
244
  thQty.textContent = 'Qty';
245
  headerRow.appendChild(thQty);
@@ -248,16 +266,60 @@
248
  order.items_json_array.forEach(item => {
249
  const itemRow = tBody.insertRow();
250
  itemRow.insertCell().textContent = item.product_name || 'N/A';
251
- itemRow.insertCell().textContent = item.sku || 'N/A';
252
  itemRow.insertCell().textContent = item.quantity || 'N/A';
253
  });
254
  itemsCell.appendChild(itemsTable);
255
  } else {
256
  itemsCell.textContent = 'No items';
257
  }
 
 
 
 
 
 
 
 
258
  });
259
  }
260
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  refreshButton.addEventListener('click', fetchOrders);
262
  document.addEventListener('DOMContentLoaded', fetchOrders); // Initial load
263
  </script>
 
121
  .inner-items-table td {
122
  word-break: break-word; /* Prevent long product names from breaking layout */
123
  }
124
+
125
+ /* Styles for delete button in actions column */
126
+ .delete-button {
127
+ padding: 5px 10px;
128
+ background-color: #e74c3c; /* Red */
129
+ color: white;
130
+ border: none;
131
+ border-radius: 4px;
132
+ cursor: pointer;
133
+ font-size: 0.9em;
134
+ transition: background-color 0.2s ease;
135
+ }
136
+
137
+ .delete-button:hover {
138
+ background-color: #c0392b; /* Darker red */
139
+ }
140
 
141
 
142
  /* Responsive improvements */
 
161
  padding: 3px 5px;
162
  font-size: 0.9em; /* Relative to parent cell font size */
163
  }
164
+ .delete-button {
165
+ padding: 4px 8px;
166
+ font-size: 0.85em;
167
+ }
168
  }
169
  </style>
170
  </head>
 
186
  <th>Business ID</th>
187
  <th>Canonical User ID</th>
188
  <th>Items</th>
189
+ <th>Actions</th>
190
  </tr>
191
  </thead>
192
  <tbody>
 
204
  statusMessage.textContent = 'Refreshing orders...';
205
  refreshButton.disabled = true;
206
  try {
 
207
  const response = await fetch('/api/v1/orders/list');
208
  if (!response.ok) {
209
  throw new Error(`HTTP error! status: ${response.status}`);
 
231
  if (!orders || orders.length === 0) {
232
  const row = ordersTableBody.insertRow();
233
  const cell = row.insertCell();
234
+ cell.colSpan = 8; // Adjusted colspan
235
  cell.textContent = 'No orders found.';
236
  cell.style.textAlign = 'center';
237
  return;
 
250
  const itemsCell = row.insertCell();
251
  if (order.items_json_array && Array.isArray(order.items_json_array) && order.items_json_array.length > 0) {
252
  const itemsTable = document.createElement('table');
253
+ itemsTable.classList.add('inner-items-table');
254
 
255
  const tHead = itemsTable.createTHead();
256
  const headerRow = tHead.insertRow();
257
  const thProduct = document.createElement('th');
258
  thProduct.textContent = 'Product';
259
  headerRow.appendChild(thProduct);
260
+ // SKU header removed
 
 
261
  const thQty = document.createElement('th');
262
  thQty.textContent = 'Qty';
263
  headerRow.appendChild(thQty);
 
266
  order.items_json_array.forEach(item => {
267
  const itemRow = tBody.insertRow();
268
  itemRow.insertCell().textContent = item.product_name || 'N/A';
269
+ // SKU cell removed
270
  itemRow.insertCell().textContent = item.quantity || 'N/A';
271
  });
272
  itemsCell.appendChild(itemsTable);
273
  } else {
274
  itemsCell.textContent = 'No items';
275
  }
276
+
277
+ // Add Actions cell with Delete button
278
+ const actionsCell = row.insertCell();
279
+ const deleteButton = document.createElement('button');
280
+ deleteButton.textContent = 'Delete';
281
+ deleteButton.classList.add('delete-button');
282
+ deleteButton.onclick = () => deleteOrder(order.order_id);
283
+ actionsCell.appendChild(deleteButton);
284
  });
285
  }
286
 
287
+ async function deleteOrder(orderId) {
288
+ if (!confirm(`Are you sure you want to delete order ${orderId}?`)) {
289
+ return;
290
+ }
291
+ statusMessage.textContent = `Deleting order ${orderId}...`;
292
+ refreshButton.disabled = true; // Disable refresh button during delete operation
293
+
294
+ try {
295
+ const response = await fetch(`/api/v1/orders/delete/${orderId}`, {
296
+ method: 'DELETE',
297
+ });
298
+
299
+ // Try to parse JSON regardless of response.ok, as error messages might be in JSON
300
+ const responseData = await response.json().catch(() => {
301
+ return { status: "error", message: `Failed to parse server response. Status: ${response.status}` };
302
+ });
303
+
304
+ if (!response.ok) {
305
+ throw new Error(responseData.message || `HTTP error! status: ${response.status}`);
306
+ }
307
+
308
+ if (responseData.status === 'success') {
309
+ statusMessage.textContent = `Order ${orderId} deleted successfully. Refreshing list...`;
310
+ await fetchOrders(); // Refresh the list (fetchOrders will re-enable refreshButton)
311
+ } else {
312
+ statusMessage.textContent = `Error deleting order: ${responseData.message}`;
313
+ console.error('API Error (delete):', responseData.message);
314
+ refreshButton.disabled = false; // Re-enable if fetchOrders is not called
315
+ }
316
+ } catch (error) {
317
+ statusMessage.textContent = `Failed to delete order ${orderId}. ${error.message}. See console for details.`;
318
+ console.error('Delete error:', error);
319
+ refreshButton.disabled = false; // Re-enable on any error
320
+ }
321
+ }
322
+
323
  refreshButton.addEventListener('click', fetchOrders);
324
  document.addEventListener('DOMContentLoaded', fetchOrders); // Initial load
325
  </script>