EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
page.tsx
Go to the documentation of this file.
1'use client';
2
3import { useState, useEffect } from 'react';
4import Link from 'next/link';
5
6interface PendingMessage {
7 f100: number; // Message ID
8 f101: number; // Status code
9 f101s?: string; // Status string
10 f102: number; // Generation source code
11 f102s?: string; // Generation source string
12 f103: string; // Created datetime
13 f110: number; // Customer ID
14 f111?: number; // Product ID
15 f112?: number; // Quantity
16 f113?: number; // Sale number
17 f120?: string; // Email/Phone
18 f1001?: string; // Status message
19 f1002?: string; // Source
20 f1003?: string; // Additional contact
21 f1004?: string; // Details
22 holdreq?: boolean; // Mark for hold
23 relreq?: boolean; // Mark for release
24 neverreq?: boolean; // Mark for never send
25}
26
27interface Stats {
28 f201: number; // Sending
29 f202: number; // On Hold
30 f203: number; // Completed
31 f204: number; // Never Send
32 f217: number; // Awaiting Trigger
33 f120: number; // Created Today
34}
35
36const STATUS_OPTIONS = [
37 { value: '1', label: 'Sending' },
38 { value: '2', label: 'Hold' },
39 { value: '3', label: 'Completed' },
40 { value: '4', label: 'NeverSend' },
41 { value: '5', label: 'Active Locked' },
42 { value: '8', label: 'Duplicate Rejected' },
43 { value: '9', label: 'Re-Sending' },
44 { value: '17', label: 'Awaiting Trigger' },
45 { value: '0', label: 'All' },
46];
47
48const STATUS_NAMES: { [key: number]: string } = {
49 1: 'Sending',
50 2: 'Hold',
51 3: 'Completed',
52 4: 'Never Send',
53 5: 'Active Locked',
54 8: 'Duplicate',
55 9: 'ReSending',
56 17: 'Awaiting Trigger',
57};
58
59const GENERATION_SOURCE_NAMES: { [key: number]: string } = {
60 109: 'Account Statement Email',
61 119: 'Sale Receipt',
62 134: 'Hold Request Pickup Email',
63 135: 'Hold Request Pickup Txt',
64};
65
66export default function PendingMessagesPage() {
67 const [messages, setMessages] = useState<PendingMessage[]>([]);
68 const [stats, setStats] = useState<Stats>({
69 f201: 0,
70 f202: 0,
71 f203: 0,
72 f204: 0,
73 f217: 0,
74 f120: 0,
75 });
76 const [loading, setLoading] = useState(false);
77 const [error, setError] = useState('');
78
79 // Filter states
80 const [statusFilter, setStatusFilter] = useState('2'); // Default to Hold
81 const [startDate, setStartDate] = useState('');
82 const [endDate, setEndDate] = useState('');
83 const [statsLevel, setStatsLevel] = useState('2'); // 2 months
84
85 useEffect(() => {
86 // Set default dates
87 const today = new Date();
88 const monthAgo = new Date();
89 monthAgo.setMonth(monthAgo.getMonth() - 1);
90
91 setStartDate(monthAgo.toISOString().split('T')[0]);
92 setEndDate(today.toISOString().split('T')[0]);
93
94 loadStats();
95 loadMessages();
96 }, []);
97
98 const loadStats = async () => {
99 try {
100 const response = await fetch(`/api/v1/messaging/pending/stats?level=${statsLevel}`);
101
102 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
103 setStats({
104 f201: 12,
105 f202: 45,
106 f203: 1523,
107 f204: 8,
108 f217: 3,
109 f120: 67,
110 });
111 return;
112 }
113
114 const data = await response.json();
115 setStats(data);
116 } catch (err) {
117 console.error('Error loading stats:', err);
118 setStats({
119 f201: 12,
120 f202: 45,
121 f203: 1523,
122 f204: 8,
123 f217: 3,
124 f120: 67,
125 });
126 }
127 };
128
129 const loadMessages = async () => {
130 setLoading(true);
131 try {
132 const params = new URLSearchParams({
133 status: statusFilter,
134 startDate,
135 endDate,
136 });
137
138 const response = await fetch(`/api/v1/messaging/pending?${params}`);
139
140 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
141 setError('⚠️ Pending Messages API not yet implemented - UI demonstration mode');
142 setMessages([
143 {
144 f100: 1001,
145 f101: 2,
146 f101s: 'Hold',
147 f102: 119,
148 f102s: 'Sale Receipt',
149 f103: '2024-12-28 14:30:00',
150 f110: 5234,
151 f111: 12345,
152 f112: 2,
153 f113: 78901,
154 f120: 'customer@example.com',
155 f1001: 'Ready to send',
156 f1002: 'POS System',
157 f1004: 'Order confirmation email',
158 },
159 {
160 f100: 1002,
161 f101: 1,
162 f101s: 'Sending',
163 f102: 134,
164 f102s: 'Hold Request Pickup Email',
165 f103: '2024-12-28 15:45:00',
166 f110: 6789,
167 f113: 78902,
168 f120: 'john.smith@email.com',
169 f1001: 'Queued',
170 f1002: 'Online Order',
171 f1004: 'Pickup notification',
172 },
173 ]);
174 setLoading(false);
175 return;
176 }
177
178 const data = await response.json();
179
180 // Process messages
181 const processedMessages = (data.DATS || []).map((msg: PendingMessage) => {
182 msg.f101s = STATUS_NAMES[msg.f101] || 'Unknown';
183 msg.f102s = GENERATION_SOURCE_NAMES[msg.f102] || String(msg.f102);
184 return msg;
185 });
186
187 setMessages(processedMessages);
188 setError('');
189 } catch (err) {
190 console.error('Error loading messages:', err);
191 setError('Failed to load messages. Using demo mode.');
192 setMessages([
193 {
194 f100: 1001,
195 f101: 2,
196 f101s: 'Hold',
197 f102: 119,
198 f102s: 'Sale Receipt',
199 f103: '2024-12-28 14:30:00',
200 f110: 5234,
201 f120: 'customer@example.com',
202 f1001: 'Ready to send',
203 f1002: 'POS System',
204 },
205 ]);
206 } finally {
207 setLoading(false);
208 }
209 };
210
211 const applyChanges = async () => {
212 const changedMessages = messages.filter(msg => msg.holdreq || msg.relreq || msg.neverreq);
213
214 if (changedMessages.length === 0) {
215 alert('No changes to apply');
216 return;
217 }
218
219 try {
220 for (const msg of changedMessages) {
221 let newStatus = msg.f101;
222 if (msg.holdreq) newStatus = 2;
223 if (msg.relreq) newStatus = 1;
224 if (msg.neverreq) newStatus = 4;
225
226 const response = await fetch('/api/v1/messaging/pending', {
227 method: 'PUT',
228 headers: { 'Content-Type': 'application/json' },
229 body: JSON.stringify({
230 f100: msg.f100,
231 f101: newStatus,
232 }),
233 });
234
235 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
236 console.log(`⚠️ Update API not yet implemented for message ${msg.f100}`);
237 }
238 }
239
240 alert(`✓ ${changedMessages.length} message(s) updated successfully`);
241 setTimeout(() => {
242 window.location.reload();
243 }, 500);
244 } catch (err) {
245 console.error('Error applying changes:', err);
246 alert('⚠️ Failed to apply some changes');
247 }
248 };
249
250 const toggleCheckbox = (messageId: number, field: 'holdreq' | 'relreq' | 'neverreq') => {
251 setMessages(messages.map(msg => {
252 if (msg.f100 === messageId) {
253 // Clear other checkboxes
254 const updated = { ...msg, holdreq: false, relreq: false, neverreq: false };
255 updated[field] = !msg[field];
256 return updated;
257 }
258 return msg;
259 }));
260 };
261
262 return (
263 <div className="min-h-screen bg-gray-50 p-8">
264 <div className="max-w-[1600px] mx-auto">
265 {/* Header */}
266 <div className="mb-8">
267 <Link href="/marketing/messaging" className="text-[#00946b] hover:text-[#007055] mb-2 inline-block">
268 ← Back to Messaging
269 </Link>
270 <h1 className="text-3xl font-bold text-gray-900">📬 Pending Messages</h1>
271 <p className="text-gray-600 mt-2">
272 This report lists pending outbound messages due to be sent from this system to customers and other external parties.
273 </p>
274 </div>
275
276 {error && (
277 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
278 <p className="text-yellow-700">{error}</p>
279 </div>
280 )}
281
282 {/* Stats Panel */}
283 <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
284 <div className="bg-white rounded-lg border border-blue-200 p-4 shadow-sm">
285 <div className="text-sm text-gray-600 mb-1">Sending</div>
286 <div className="text-2xl font-bold text-gray-900">{stats.f201.toLocaleString()}</div>
287 </div>
288 <div className="bg-white rounded-lg border border-yellow-200 p-4 shadow-sm">
289 <div className="text-sm text-gray-600 mb-1">Awaiting Trigger</div>
290 <div className="text-2xl font-bold text-gray-900">{stats.f217.toLocaleString()}</div>
291 </div>
292 <div className="bg-white rounded-lg border border-orange-200 p-4 shadow-sm">
293 <div className="text-sm text-gray-600 mb-1">On Hold</div>
294 <div className="text-2xl font-bold text-gray-900">{stats.f202.toLocaleString()}</div>
295 </div>
296 <div className="bg-white rounded-lg border border-green-200 p-4 shadow-sm">
297 <div className="text-sm text-gray-600 mb-1">Completed</div>
298 <div className="text-2xl font-bold text-gray-900">{stats.f203.toLocaleString()}</div>
299 </div>
300 <div className="bg-white rounded-lg border border-red-200 p-4 shadow-sm">
301 <div className="text-sm text-gray-600 mb-1">Never Send</div>
302 <div className="text-2xl font-bold text-gray-900">{stats.f204.toLocaleString()}</div>
303 </div>
304 <div className="bg-white rounded-lg border border-purple-200 p-4 shadow-sm">
305 <div className="text-sm text-gray-600 mb-1">Created Today</div>
306 <div className="text-2xl font-bold text-gray-900">{stats.f120.toLocaleString()}</div>
307 </div>
308 </div>
309
310 {/* Stats Level Selector */}
311 <div className="flex justify-end mb-6">
312 <div className="bg-white rounded-lg border border-gray-200 p-3 shadow-sm">
313 <label className="text-sm text-gray-700 mr-2">Show:</label>
314 <select
315 value={statsLevel}
316 onChange={(e) => {
317 setStatsLevel(e.target.value);
318 setTimeout(loadStats, 100);
319 }}
320 className="border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-[#00946b]"
321 >
322 <option value="4">1 Month</option>
323 <option value="2">2 Months</option>
324 <option value="3">All</option>
325 </select>
326 </div>
327 </div>
328
329 {/* Filter Controls */}
330 <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
331 <div className="flex flex-wrap gap-4 items-end">
332 <div>
333 <label className="block text-sm font-medium text-gray-700 mb-1">Status</label>
334 <select
335 value={statusFilter}
336 onChange={(e) => setStatusFilter(e.target.value)}
337 className="border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#00946b]"
338 >
339 {STATUS_OPTIONS.map(opt => (
340 <option key={opt.value} value={opt.value}>{opt.label}</option>
341 ))}
342 </select>
343 </div>
344
345 <div>
346 <label className="block text-sm font-medium text-gray-700 mb-1">From (including)</label>
347 <input
348 type="date"
349 value={startDate}
350 onChange={(e) => setStartDate(e.target.value)}
351 className="border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#00946b]"
352 />
353 </div>
354
355 <div>
356 <label className="block text-sm font-medium text-gray-700 mb-1">To</label>
357 <input
358 type="date"
359 value={endDate}
360 onChange={(e) => setEndDate(e.target.value)}
361 className="border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#00946b]"
362 />
363 </div>
364
365 <button
366 onClick={loadMessages}
367 disabled={loading}
368 className="px-6 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055] font-medium transition-colors disabled:bg-gray-400"
369 >
370 {loading ? 'Loading...' : 'Display'}
371 </button>
372
373 <button
374 onClick={applyChanges}
375 className="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium transition-colors ml-auto"
376 >
377 Apply Changes
378 </button>
379 </div>
380 </div>
381
382 {/* Messages Table */}
383 <div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
384 <div className="overflow-x-auto">
385 <table className="min-w-full divide-y divide-gray-200">
386 <thead className="bg-gray-50">
387 <tr>
388 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID#</th>
389 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
390 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Gen Source</th>
391 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Created</th>
392 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Customer ID</th>
393 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email/Phone</th>
394 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Sale#</th>
395 <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Details</th>
396 <th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">Release</th>
397 <th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">Hold</th>
398 <th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">Never Send</th>
399 </tr>
400 </thead>
401 <tbody className="bg-white divide-y divide-gray-200">
402 {loading ? (
403 <tr>
404 <td colSpan={11} className="px-6 py-8 text-center text-gray-500">
405 <div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-[#00946b] mr-2"></div>
406 Loading messages...
407 </td>
408 </tr>
409 ) : messages.length === 0 ? (
410 <tr>
411 <td colSpan={11} className="px-6 py-8 text-center text-gray-500">
412 No pending messages found for the selected filters.
413 </td>
414 </tr>
415 ) : (
416 messages.map((msg) => (
417 <tr key={msg.f100} className="hover:bg-gray-50">
418 <td className="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">{msg.f100}</td>
419 <td className="px-4 py-3 whitespace-nowrap">
420 <span
421 className={`px-2 py-1 text-xs font-semibold rounded-full ${
422 msg.f101 === 1 ? 'bg-blue-100 text-blue-800' :
423 msg.f101 === 2 ? 'bg-yellow-100 text-yellow-800' :
424 msg.f101 === 3 ? 'bg-green-100 text-green-800' :
425 msg.f101 === 4 ? 'bg-red-100 text-red-800' :
426 'bg-gray-100 text-gray-800'
427 }`}
428 >
429 {msg.f101s}
430 </span>
431 </td>
432 <td className="px-4 py-3 text-sm text-gray-700">{msg.f102s}</td>
433 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-700">
434 {new Date(msg.f103).toLocaleString()}
435 </td>
436 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{msg.f110}</td>
437 <td className="px-4 py-3 text-sm text-gray-700">{msg.f120 || msg.f1003 || '-'}</td>
438 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{msg.f113 || '-'}</td>
439 <td className="px-4 py-3 text-sm text-gray-700">{msg.f1004 || msg.f1001 || '-'}</td>
440 <td className="px-4 py-3 text-center">
441 <input
442 type="checkbox"
443 checked={msg.relreq || false}
444 onChange={() => toggleCheckbox(msg.f100, 'relreq')}
445 className="rounded border-gray-300 text-green-600 focus:ring-green-500"
446 />
447 </td>
448 <td className="px-4 py-3 text-center">
449 <input
450 type="checkbox"
451 checked={msg.holdreq || false}
452 onChange={() => toggleCheckbox(msg.f100, 'holdreq')}
453 className="rounded border-gray-300 text-yellow-600 focus:ring-yellow-500"
454 />
455 </td>
456 <td className="px-4 py-3 text-center">
457 <input
458 type="checkbox"
459 checked={msg.neverreq || false}
460 onChange={() => toggleCheckbox(msg.f100, 'neverreq')}
461 className="rounded border-gray-300 text-red-600 focus:ring-red-500"
462 />
463 </td>
464 </tr>
465 ))
466 )}
467 </tbody>
468 </table>
469 </div>
470 </div>
471 </div>
472 </div>
473 );
474}