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 SmsAccount {
7 f100: string; // Account ID
8 f109: string; // Account Name
9 f110: string; // Type (SMS, SMTP, POP3, etc)
10 f120: number; // Status flags (1=Hold)
11 f120s?: string; // Status string (Active/Hold)
12 f120s_bkcolor?: string; // Background color
13 f101?: string; // Server/Config
14 f102?: string; // Port
15 f103?: string; // Username/Email
16 f104?: string; // Password
17 f130?: string; // SMS Provider type
18 STAT?: Array<{
19 f120?: string;
20 TRCE?: Array<{ f110: string }>;
21 }>;
22}
23
24interface SmsProvider {
25 value: string;
26 label: string;
27 description: string;
28 requiresEmail: boolean;
29}
30
31const SMS_PROVIDERS: SmsProvider[] = [
32 { value: '0', label: '--Select One', description: '', requiresEmail: false },
33 { value: '2', label: 'Fieldpine Gateway', description: 'Fieldpine provide a general purpose gateway for most countries. See Fieldpine SMS for pricing and further details', requiresEmail: false },
34 { value: '1', label: 'Email Gateway', description: 'Enter the details of the email address to be sent too. This option requires an SMTP (Send Email) account.', requiresEmail: true },
35 { value: '6401', label: 'NZ. Spark eTxt', description: 'Configure the SMS setup after you have registered for Spark eTxt and can successfully send Txt messages from your email account outside the POS. This option requires an SMTP (Send Email) account.', requiresEmail: false },
36];
37
38export default function TxtOptionsPage() {
39 const [accounts, setAccounts] = useState<SmsAccount[]>([]);
40 const [loading, setLoading] = useState(true);
41 const [error, setError] = useState('');
42
43 // Modal states
44 const [showNewAccountModal, setShowNewAccountModal] = useState(false);
45 const [showTestModal, setShowTestModal] = useState(false);
46 const [showLogModal, setShowLogModal] = useState(false);
47 const [showControlPanelModal, setShowControlPanelModal] = useState(false);
48
49 // Selected account for modals
50 const [selectedAccount, setSelectedAccount] = useState<SmsAccount | null>(null);
51
52 // New account form
53 const [accountType, setAccountType] = useState<'SMTP' | 'POP3' | 'UPLOAD' | 'FTP' | 'SMS'>('SMS');
54 const [newAccountForm, setNewAccountForm] = useState({
55 name: '',
56 server: '',
57 port: '',
58 username: '',
59 password: '',
60 directory: '',
61 smsProvider: '0',
62 smsEmail: '',
63 });
64
65 // Test form
66 const [testTarget, setTestTarget] = useState('');
67 const [testMessage, setTestMessage] = useState('This is a test message to verify the system is configured correctly. You can delete this message.');
68
69 // Control panel
70 const [controlPanelStatus, setControlPanelStatus] = useState('Contacting Server, please wait...');
71
72 useEffect(() => {
73 loadAccounts();
74 const interval = setInterval(loadAccounts, 30000); // Refresh every 30 seconds
75 return () => clearInterval(interval);
76 }, []);
77
78 const loadAccounts = async () => {
79 try {
80 const response = await fetch('/api/v1/messaging/config');
81
82 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
83 setError('⚠️ Messaging Config API not yet implemented - UI demonstration mode');
84 setAccounts([
85 {
86 f100: 'demo1',
87 f109: 'Demo SMS Account',
88 f110: 'SMS',
89 f120: 0,
90 f120s: 'Active',
91 f120s_bkcolor: '#00ff00',
92 f130: '2',
93 STAT: [{ f120: 'Connected and ready' }]
94 }
95 ]);
96 setLoading(false);
97 return;
98 }
99
100 const data = await response.json();
101
102 // Process accounts
103 const smsAccounts = (data.DATS || [])
104 .filter((acc: SmsAccount) => acc.f110 === 'SMS')
105 .map((acc: SmsAccount) => {
106 const status = acc.f120 || 0;
107 acc.f120s = (status & 1) === 1 ? 'Hold' : 'Active';
108 acc.f120s_bkcolor = (status & 1) === 1 ? '#ff8080' : '#00ff00';
109 return acc;
110 });
111
112 setAccounts(smsAccounts);
113 setError('');
114 } catch (err) {
115 console.error('Error loading accounts:', err);
116 setError('Failed to load accounts. Using demo mode.');
117 setAccounts([
118 {
119 f100: 'demo1',
120 f109: 'Demo SMS Account',
121 f110: 'SMS',
122 f120: 0,
123 f120s: 'Active',
124 f120s_bkcolor: '#00ff00',
125 f130: '2',
126 STAT: [{ f120: 'Connected and ready' }]
127 }
128 ]);
129 } finally {
130 setLoading(false);
131 }
132 };
133
134 const createNewAccount = async () => {
135 try {
136 const payload: any = {
137 f8_s: 'messaging.config.edit',
138 f11_B: 'I',
139 f109_s: newAccountForm.name,
140 f110_s: accountType,
141 };
142
143 if (accountType === 'SMTP' || accountType === 'POP3' || accountType === 'FTP') {
144 payload.f101_s = newAccountForm.server;
145 payload.f102_s = newAccountForm.port;
146 payload.f103_s = newAccountForm.username;
147 payload.f104_s = newAccountForm.password;
148 } else if (accountType === 'UPLOAD') {
149 payload.f101_s = newAccountForm.directory;
150 } else if (accountType === 'SMS') {
151 payload.f103_s = newAccountForm.smsEmail;
152 payload.f130_E = newAccountForm.smsProvider;
153 }
154
155 const response = await fetch('/api/v1/messaging/config', {
156 method: 'POST',
157 headers: { 'Content-Type': 'application/json' },
158 body: JSON.stringify(payload),
159 });
160
161 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
162 alert('⚠️ Account creation API not yet implemented. In production, this would create the account.');
163 } else {
164 await response.json();
165 alert('Account created successfully');
166 }
167
168 closeAllModals();
169 setTimeout(loadAccounts, 100);
170 } catch (err) {
171 console.error('Error creating account:', err);
172 alert('Failed to create account');
173 }
174 };
175
176 const sendTestMessage = async () => {
177 if (!selectedAccount || !testTarget || !testMessage) return;
178
179 try {
180 const response = await fetch('/api/v1/messaging/config', {
181 method: 'PUT',
182 headers: { 'Content-Type': 'application/json' },
183 body: JSON.stringify({
184 f8_s: 'messaging.config.edit',
185 f11_B: 'E',
186 f100_s: selectedAccount.f100,
187 f200_s: testTarget,
188 f201_s: testMessage,
189 }),
190 });
191
192 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
193 alert('⚠️ Test message API not yet implemented. In production, this would send: "' + testMessage.substring(0, 50) + '..." to ' + testTarget);
194 } else {
195 await response.json();
196 alert('Test message queued successfully');
197 }
198
199 closeAllModals();
200 } catch (err) {
201 console.error('Error sending test:', err);
202 alert('Failed to send test message');
203 }
204 };
205
206 const toggleAccountStatus = async (account: SmsAccount, holdStatus: number) => {
207 try {
208 const response = await fetch('/api/v1/messaging/config', {
209 method: 'PUT',
210 headers: { 'Content-Type': 'application/json' },
211 body: JSON.stringify({
212 f8_s: 'messaging.config.edit',
213 f11_B: 'E',
214 f100_s: account.f100,
215 f120_E: holdStatus,
216 }),
217 });
218
219 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
220 alert(`⚠️ Status change API not yet implemented. In production, this would ${holdStatus === 1 ? 'hold' : 'activate'} the account.`);
221 } else {
222 await response.json();
223 }
224
225 setTimeout(loadAccounts, 100);
226 } catch (err) {
227 console.error('Error toggling status:', err);
228 alert('Failed to update account status');
229 }
230 };
231
232 const openControlPanel = async (account: SmsAccount) => {
233 setSelectedAccount(account);
234 setShowControlPanelModal(true);
235 setControlPanelStatus('Contacting Server, please wait...');
236
237 try {
238 const response = await fetch('/api/v1/messaging/sms/control', {
239 method: 'POST',
240 headers: { 'Content-Type': 'application/json' },
241 body: JSON.stringify({
242 f8_s: 'messaging.sms.control',
243 f11_B: 'E',
244 f100_s: account.f100,
245 f102_E: 1,
246 }),
247 });
248
249 if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) {
250 setControlPanelStatus('⚠️ Control panel API not yet implemented. In production, this would open the SMS provider control panel.');
251 return;
252 }
253
254 const data = await response.json();
255 const url = data.f130 || '';
256
257 if (url.length > 8) {
258 window.open(url, '_blank');
259 closeAllModals();
260 } else {
261 setControlPanelStatus('Unable to connect to server at this time. Please try again later');
262 }
263 } catch (err) {
264 console.error('Error opening control panel:', err);
265 setControlPanelStatus('Error connecting to control panel');
266 }
267 };
268
269 const closeAllModals = () => {
270 setShowNewAccountModal(false);
271 setShowTestModal(false);
272 setShowLogModal(false);
273 setShowControlPanelModal(false);
274 setSelectedAccount(null);
275 setNewAccountForm({
276 name: '',
277 server: '',
278 port: '',
279 username: '',
280 password: '',
281 directory: '',
282 smsProvider: '0',
283 smsEmail: '',
284 });
285 };
286
287 const openTestModal = (account: SmsAccount) => {
288 setSelectedAccount(account);
289 setShowTestModal(true);
290 };
291
292 const openLogModal = (account: SmsAccount) => {
293 setSelectedAccount(account);
294 setShowLogModal(true);
295 };
296
297 const selectedProvider = SMS_PROVIDERS.find(p => p.value === newAccountForm.smsProvider);
298
299 if (loading) {
300 return (
301 <div className="min-h-screen bg-gray-50 p-8">
302 <div className="max-w-7xl mx-auto">
303 <div className="text-center py-12">
304 <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-[#00946b]"></div>
305 <p className="mt-2 text-gray-600">Loading accounts...</p>
306 </div>
307 </div>
308 </div>
309 );
310 }
311
312 return (
313 <div className="min-h-screen bg-gray-50 p-8">
314 <div className="max-w-7xl mx-auto">
315 {/* Header */}
316 <div className="mb-8">
317 <div className="flex items-center justify-between mb-4">
318 <div>
319 <Link href="/marketing/messaging" className="text-[#00946b] hover:text-[#007055] mb-2 inline-block">
320 ← Back to Messaging
321 </Link>
322 <h1 className="text-3xl font-bold text-gray-900">💬 Txt Messaging Options</h1>
323 </div>
324 <button
325 onClick={() => setShowNewAccountModal(true)}
326 className="bg-[#00946b] text-white px-6 py-3 rounded-lg hover:bg-[#007055] font-medium transition-colors"
327 >
328 + Add New Account
329 </button>
330 </div>
331
332 {error && (
333 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-4">
334 <p className="text-yellow-700">{error}</p>
335 </div>
336 )}
337 </div>
338
339 {/* Accounts Table */}
340 <div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
341 <div className="px-6 py-4 border-b border-gray-200">
342 <h2 className="text-xl font-semibold text-gray-900">Current SMS Accounts</h2>
343 </div>
344
345 <div className="overflow-x-auto">
346 <table className="min-w-full divide-y divide-gray-200">
347 <thead className="bg-gray-50">
348 <tr>
349 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Account Name</th>
350 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
351 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
352 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Current State</th>
353 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
354 </tr>
355 </thead>
356 <tbody className="bg-white divide-y divide-gray-200">
357 {accounts.length === 0 ? (
358 <tr>
359 <td colSpan={5} className="px-6 py-8 text-center text-gray-500">
360 No SMS accounts configured. Click "Add New Account" to create one.
361 </td>
362 </tr>
363 ) : (
364 accounts.map((account) => (
365 <tr key={account.f100} className="hover:bg-gray-50">
366 <td className="px-6 py-4 whitespace-nowrap">
367 <div className="text-sm font-medium text-gray-900">{account.f109}</div>
368 </td>
369 <td className="px-6 py-4 whitespace-nowrap">
370 <div className="text-sm text-gray-700">{account.f110}</div>
371 </td>
372 <td className="px-6 py-4 whitespace-nowrap">
373 <span
374 className="px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full"
375 style={{ backgroundColor: account.f120s_bkcolor, color: account.f120s === 'Active' ? '#000' : '#fff' }}
376 >
377 {account.f120s}
378 </span>
379 </td>
380 <td className="px-6 py-4">
381 <div className="text-sm text-gray-700">{account.STAT?.[0]?.f120 || 'Ready'}</div>
382 </td>
383 <td className="px-6 py-4 whitespace-nowrap text-sm">
384 <div className="flex flex-wrap gap-2">
385 <button
386 onClick={() => openControlPanel(account)}
387 className="text-[#00946b] hover:text-[#007055] font-medium border border-[#00946b] px-3 py-1 rounded hover:bg-[#00946b] hover:text-white transition-colors"
388 >
389 Control Panel
390 </button>
391 <button
392 onClick={() => openTestModal(account)}
393 className="text-blue-600 hover:text-blue-800 font-medium border border-blue-600 px-3 py-1 rounded hover:bg-blue-600 hover:text-white transition-colors"
394 >
395 Test
396 </button>
397 <button
398 onClick={() => openLogModal(account)}
399 className="text-gray-600 hover:text-gray-800 font-medium border border-gray-400 px-3 py-1 rounded hover:bg-gray-600 hover:text-white transition-colors"
400 >
401 View Log
402 </button>
403 {account.f120s === 'Active' ? (
404 <button
405 onClick={() => toggleAccountStatus(account, 1)}
406 className="text-orange-600 hover:text-orange-800 font-medium border border-orange-600 px-3 py-1 rounded hover:bg-orange-600 hover:text-white transition-colors"
407 >
408 Place on Hold
409 </button>
410 ) : (
411 <button
412 onClick={() => toggleAccountStatus(account, 0)}
413 className="text-green-600 hover:text-green-800 font-medium border border-green-600 px-3 py-1 rounded hover:bg-green-600 hover:text-white transition-colors"
414 >
415 Set Active
416 </button>
417 )}
418 </div>
419 </td>
420 </tr>
421 ))
422 )}
423 </tbody>
424 </table>
425 </div>
426 </div>
427 </div>
428
429 {/* New Account Modal */}
430 {showNewAccountModal && (
431 <div className="modal-overlay" onClick={closeAllModals}>
432 <div className="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
433 <div className="bg-[#00946b] text-white px-6 py-4 rounded-t-lg flex justify-between items-center">
434 <h2 className="text-xl font-semibold">Add New Messaging Account</h2>
435 <button onClick={closeAllModals} className="text-white hover:text-gray-200 text-2xl">×</button>
436 </div>
437
438 <div className="p-6">
439 <div className="mb-6">
440 <p className="text-gray-600 mb-4">Select the type of account to create:</p>
441 <div className="flex gap-2 border-b border-gray-200 mb-4">
442 {['SMTP', 'POP3', 'UPLOAD', 'FTP', 'SMS'].map((type) => (
443 <button
444 key={type}
445 onClick={() => setAccountType(type as any)}
446 className={`px-4 py-2 font-medium transition-colors ${
447 accountType === type
448 ? 'border-b-2 border-[#00946b] text-[#00946b]'
449 : 'text-gray-600 hover:text-gray-900'
450 }`}
451 >
452 {type === 'SMTP' && 'SMTP (Send Email)'}
453 {type === 'POP3' && 'POP3 (Receive Email)'}
454 {type === 'UPLOAD' && 'Upload Folder'}
455 {type === 'FTP' && 'FTP'}
456 {type === 'SMS' && 'SMS/Txt'}
457 </button>
458 ))}
459 </div>
460 </div>
461
462 <div className="space-y-4">
463 <div>
464 <label className="block text-sm font-medium text-gray-700 mb-1">Account Name</label>
465 <input
466 type="text"
467 value={newAccountForm.name}
468 onChange={(e) => setNewAccountForm({ ...newAccountForm, name: e.target.value })}
469 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
470 placeholder="Enter account name"
471 />
472 </div>
473
474 {accountType === 'SMS' && (
475 <>
476 <div>
477 <label className="block text-sm font-medium text-gray-700 mb-1">SMS Provider</label>
478 <select
479 value={newAccountForm.smsProvider}
480 onChange={(e) => setNewAccountForm({ ...newAccountForm, smsProvider: e.target.value })}
481 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
482 >
483 {SMS_PROVIDERS.map((provider) => (
484 <option key={provider.value} value={provider.value}>{provider.label}</option>
485 ))}
486 </select>
487 {selectedProvider && selectedProvider.description && (
488 <p className="mt-2 text-sm text-gray-600">{selectedProvider.description}</p>
489 )}
490 </div>
491
492 {selectedProvider?.requiresEmail && (
493 <div>
494 <label className="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
495 <input
496 type="email"
497 value={newAccountForm.smsEmail}
498 onChange={(e) => setNewAccountForm({ ...newAccountForm, smsEmail: e.target.value })}
499 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
500 placeholder="sms@provider.com"
501 />
502 </div>
503 )}
504
505 <div className="bg-blue-50 border-l-4 border-blue-400 p-4">
506 <p className="text-sm text-blue-700">
507 SMS allows you to send Txt messages from within the POS. Charges per message sent may apply.
508 </p>
509 </div>
510 </>
511 )}
512
513 {(accountType === 'SMTP' || accountType === 'POP3' || accountType === 'FTP') && (
514 <>
515 <div>
516 <label className="block text-sm font-medium text-gray-700 mb-1">Server Address</label>
517 <input
518 type="text"
519 value={newAccountForm.server}
520 onChange={(e) => setNewAccountForm({ ...newAccountForm, server: e.target.value })}
521 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
522 placeholder="mail.example.com"
523 />
524 </div>
525 <div>
526 <label className="block text-sm font-medium text-gray-700 mb-1">Port</label>
527 <input
528 type="text"
529 value={newAccountForm.port}
530 onChange={(e) => setNewAccountForm({ ...newAccountForm, port: e.target.value })}
531 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
532 placeholder={accountType === 'SMTP' ? '587' : accountType === 'POP3' ? '995' : '21'}
533 />
534 </div>
535 <div>
536 <label className="block text-sm font-medium text-gray-700 mb-1">Username</label>
537 <input
538 type="text"
539 value={newAccountForm.username}
540 onChange={(e) => setNewAccountForm({ ...newAccountForm, username: e.target.value })}
541 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
542 placeholder="username"
543 />
544 </div>
545 <div>
546 <label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
547 <input
548 type="password"
549 value={newAccountForm.password}
550 onChange={(e) => setNewAccountForm({ ...newAccountForm, password: e.target.value })}
551 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
552 placeholder="••••••••"
553 />
554 </div>
555 </>
556 )}
557
558 {accountType === 'UPLOAD' && (
559 <>
560 <div>
561 <label className="block text-sm font-medium text-gray-700 mb-1">Local Directory on Server</label>
562 <input
563 type="text"
564 value={newAccountForm.directory}
565 onChange={(e) => setNewAccountForm({ ...newAccountForm, directory: e.target.value })}
566 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
567 placeholder="C:\uploads"
568 />
569 </div>
570 <div className="bg-blue-50 border-l-4 border-blue-400 p-4">
571 <p className="text-sm text-blue-700">
572 An Upload account is a folder that is read on the local machine and any transactions present are automatically applied.
573 This is sometimes used when interfacing other systems.
574 </p>
575 </div>
576 </>
577 )}
578 </div>
579
580 <div className="mt-6 flex justify-end gap-3">
581 <button
582 onClick={closeAllModals}
583 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
584 >
585 Cancel
586 </button>
587 <button
588 onClick={createNewAccount}
589 disabled={!newAccountForm.name}
590 className="px-4 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055] disabled:bg-gray-400 disabled:cursor-not-allowed"
591 >
592 Create Account
593 </button>
594 </div>
595 </div>
596 </div>
597 </div>
598 )}
599
600 {/* Test Modal */}
601 {showTestModal && selectedAccount && (
602 <div className="modal-overlay" onClick={closeAllModals}>
603 <div className="bg-white rounded-lg shadow-xl max-w-lg w-full" onClick={(e) => e.stopPropagation()}>
604 <div className="bg-[#00946b] text-white px-6 py-4 rounded-t-lg flex justify-between items-center">
605 <h2 className="text-xl font-semibold">{selectedAccount.f109} - Test</h2>
606 <button onClick={closeAllModals} className="text-white hover:text-gray-200 text-2xl">×</button>
607 </div>
608
609 <div className="p-6">
610 <div className="space-y-4">
611 <div>
612 <label className="block text-sm font-medium text-gray-700 mb-1">Phone Number</label>
613 <input
614 type="text"
615 value={testTarget}
616 onChange={(e) => setTestTarget(e.target.value)}
617 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
618 placeholder="6421234567"
619 />
620 <p className="mt-1 text-sm text-gray-600">
621 Enter the full phone number including the country code. eg. To Txt a New Zealand phone, prefix the number with &apos;64&apos;
622 </p>
623 </div>
624
625 <div>
626 <label className="block text-sm font-medium text-gray-700 mb-1">Message</label>
627 <textarea
628 value={testMessage}
629 onChange={(e) => setTestMessage(e.target.value)}
630 rows={4}
631 className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#00946b]"
632 />
633 </div>
634
635 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">
636 <p className="text-sm text-yellow-700">
637 The system automatically blocks sending the exact same message to a phone number within a reasonable time frame.
638 You may need to slightly change the message in order to send a message.
639 </p>
640 </div>
641 </div>
642
643 <div className="mt-6 flex justify-end gap-3">
644 <button
645 onClick={closeAllModals}
646 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
647 >
648 Cancel
649 </button>
650 <button
651 onClick={sendTestMessage}
652 disabled={!testTarget || !testMessage}
653 className="px-4 py-2 bg-[#00946b] text-white rounded-md hover:bg-[#007055] disabled:bg-gray-400 disabled:cursor-not-allowed"
654 >
655 Send Txt Message
656 </button>
657 </div>
658 </div>
659 </div>
660 </div>
661 )}
662
663 {/* Log Modal */}
664 {showLogModal && selectedAccount && (
665 <div className="modal-overlay" onClick={closeAllModals}>
666 <div className="bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[80vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
667 <div className="bg-[#00946b] text-white px-6 py-4 rounded-t-lg flex justify-between items-center">
668 <h2 className="text-xl font-semibold">{selectedAccount.f109} - Log</h2>
669 <button onClick={closeAllModals} className="text-white hover:text-gray-200 text-2xl">×</button>
670 </div>
671
672 <div className="p-6">
673 <p className="text-sm text-gray-600 mb-4">Log reads bottom to top. Most recent entries are at the top</p>
674
675 <div className="bg-gray-50 border border-gray-200 rounded-md">
676 {selectedAccount.STAT?.[0]?.TRCE && selectedAccount.STAT[0].TRCE.length > 0 ? (
677 <table className="min-w-full">
678 <tbody className="divide-y divide-gray-200">
679 {[...selectedAccount.STAT[0].TRCE].reverse().map((entry, idx) => (
680 <tr key={idx} className="hover:bg-gray-100">
681 <td className="px-4 py-3 text-sm text-gray-700">{entry.f110}</td>
682 </tr>
683 ))}
684 </tbody>
685 </table>
686 ) : (
687 <div className="px-4 py-8 text-center text-gray-500">
688 No Log Entries
689 </div>
690 )}
691 </div>
692
693 <div className="mt-6 flex justify-end">
694 <button
695 onClick={closeAllModals}
696 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
697 >
698 Close
699 </button>
700 </div>
701 </div>
702 </div>
703 </div>
704 )}
705
706 {/* Control Panel Modal */}
707 {showControlPanelModal && selectedAccount && (
708 <div className="modal-overlay" onClick={closeAllModals}>
709 <div className="bg-white rounded-lg shadow-xl max-w-lg w-full" onClick={(e) => e.stopPropagation()}>
710 <div className="bg-[#00946b] text-white px-6 py-4 rounded-t-lg flex justify-between items-center">
711 <h2 className="text-xl font-semibold">{selectedAccount.f109} Control Panel</h2>
712 <button onClick={closeAllModals} className="text-white hover:text-gray-200 text-2xl">×</button>
713 </div>
714
715 <div className="p-6">
716 <div className="flex items-center gap-3 text-gray-700">
717 {controlPanelStatus.includes('⚠️') ? (
718 <span>⚠️</span>
719 ) : (
720 <div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-[#00946b]"></div>
721 )}
722 <p>{controlPanelStatus}</p>
723 </div>
724
725 <div className="mt-6 flex justify-end">
726 <button
727 onClick={closeAllModals}
728 className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
729 >
730 Close
731 </button>
732 </div>
733 </div>
734 </div>
735 </div>
736 )}
737 </div>
738 );
739}