3import { useState, useEffect } from 'react';
4import Link from 'next/link';
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
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
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' },
48const STATUS_NAMES: { [key: number]: string } = {
56 17: 'Awaiting Trigger',
59const GENERATION_SOURCE_NAMES: { [key: number]: string } = {
60 109: 'Account Statement Email',
62 134: 'Hold Request Pickup Email',
63 135: 'Hold Request Pickup Txt',
66export default function PendingMessagesPage() {
67 const [messages, setMessages] = useState<PendingMessage[]>([]);
68 const [stats, setStats] = useState<Stats>({
76 const [loading, setLoading] = useState(false);
77 const [error, setError] = useState('');
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
87 const today = new Date();
88 const monthAgo = new Date();
89 monthAgo.setMonth(monthAgo.getMonth() - 1);
91 setStartDate(monthAgo.toISOString().split('T')[0]);
92 setEndDate(today.toISOString().split('T')[0]);
98 const loadStats = async () => {
100 const response = await fetch(`/api/v1/messaging/pending/stats?level=${statsLevel}`);
102 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
114 const data = await response.json();
117 console.error('Error loading stats:', err);
129 const loadMessages = async () => {
132 const params = new URLSearchParams({
133 status: statusFilter,
138 const response = await fetch(`/api/v1/messaging/pending?${params}`);
140 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
141 setError('⚠️ Pending Messages API not yet implemented - UI demonstration mode');
148 f102s: 'Sale Receipt',
149 f103: '2024-12-28 14:30:00',
154 f120: 'customer@example.com',
155 f1001: 'Ready to send',
157 f1004: 'Order confirmation email',
164 f102s: 'Hold Request Pickup Email',
165 f103: '2024-12-28 15:45:00',
168 f120: 'john.smith@email.com',
170 f1002: 'Online Order',
171 f1004: 'Pickup notification',
178 const data = await response.json();
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);
187 setMessages(processedMessages);
190 console.error('Error loading messages:', err);
191 setError('Failed to load messages. Using demo mode.');
198 f102s: 'Sale Receipt',
199 f103: '2024-12-28 14:30:00',
201 f120: 'customer@example.com',
202 f1001: 'Ready to send',
211 const applyChanges = async () => {
212 const changedMessages = messages.filter(msg => msg.holdreq || msg.relreq || msg.neverreq);
214 if (changedMessages.length === 0) {
215 alert('No changes to apply');
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;
226 const response = await fetch('/api/v1/messaging/pending', {
228 headers: { 'Content-Type': 'application/json' },
229 body: JSON.stringify({
235 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
236 console.log(`⚠️ Update API not yet implemented for message ${msg.f100}`);
240 alert(`✓ ${changedMessages.length} message(s) updated successfully`);
242 window.location.reload();
245 console.error('Error applying changes:', err);
246 alert('⚠️ Failed to apply some changes');
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];
263 <div className="min-h-screen bg-gray-50 p-8">
264 <div className="max-w-[1600px] mx-auto">
266 <div className="mb-8">
267 <Link href="/marketing/messaging" className="text-[#00946b] hover:text-[#007055] mb-2 inline-block">
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.
277 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
278 <p className="text-yellow-700">{error}</p>
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>
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>
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>
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>
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>
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>
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>
317 setStatsLevel(e.target.value);
318 setTimeout(loadStats, 100);
320 className="border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-[#00946b]"
322 <option value="4">1 Month</option>
323 <option value="2">2 Months</option>
324 <option value="3">All</option>
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">
333 <label className="block text-sm font-medium text-gray-700 mb-1">Status</label>
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]"
339 {STATUS_OPTIONS.map(opt => (
340 <option key={opt.value} value={opt.value}>{opt.label}</option>
346 <label className="block text-sm font-medium text-gray-700 mb-1">From (including)</label>
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]"
356 <label className="block text-sm font-medium text-gray-700 mb-1">To</label>
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]"
366 onClick={loadMessages}
368 className="px-6 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055] font-medium transition-colors disabled:bg-gray-400"
370 {loading ? 'Loading...' : 'Display'}
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"
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">
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>
401 <tbody className="bg-white divide-y divide-gray-200">
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>
409 ) : messages.length === 0 ? (
411 <td colSpan={11} className="px-6 py-8 text-center text-gray-500">
412 No pending messages found for the selected filters.
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">
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'
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()}
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">
443 checked={msg.relreq || false}
444 onChange={() => toggleCheckbox(msg.f100, 'relreq')}
445 className="rounded border-gray-300 text-green-600 focus:ring-green-500"
448 <td className="px-4 py-3 text-center">
451 checked={msg.holdreq || false}
452 onChange={() => toggleCheckbox(msg.f100, 'holdreq')}
453 className="rounded border-gray-300 text-yellow-600 focus:ring-yellow-500"
456 <td className="px-4 py-3 text-center">
459 checked={msg.neverreq || false}
460 onChange={() => toggleCheckbox(msg.f100, 'neverreq')}
461 className="rounded border-gray-300 text-red-600 focus:ring-red-500"