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 React, { useState, useEffect } from "react";
4import Link from "next/link";
5import { fieldpineServerApi } from "@/lib/server/fieldpineApi";
6
7interface EmailLog {
8 f108: string; // Account
9 f109: string; // Send Date
10 f111: string; // To
11 f112: string; // Message/Subject (fallback)
12 f132: string; // Subject
13 f133: string; // Confirmed delivered
14 f141?: number; // Bounce flag
15}
16
17interface SmsLog {
18 f108: string; // Account
19 f109: string; // Date
20 f111: string; // Phone
21 f112: string; // Message
22 f400?: number; // Status (1=sent, other=failed)
23 f401?: string; // Status string
24 f402?: string; // ISP Response (JSON)
25 f420?: string; // Delivered date
26 f421?: string; // Hard bounce
27 f422?: string; // Soft bounce
28 f423?: string; // Replied
29 f426?: number; // Direction (1=sent, 2=received)
30}
31
32export default function MessagingPage() {
33 const [activeTab, setActiveTab] = useState<"email" | "sms">("email");
34 const [emailLogs, setEmailLogs] = useState<EmailLog[]>([]);
35 const [smsLogs, setSmsLogs] = useState<SmsLog[]>([]);
36 const [loading, setLoading] = useState(false);
37
38 // Email filters
39 const [emailFromDate, setEmailFromDate] = useState("");
40 const [emailToDate, setEmailToDate] = useState("");
41
42 // SMS filters
43 const [smsRowLimit, setSmsRowLimit] = useState(500);
44 const [smsShowSent, setSmsShowSent] = useState(true);
45 const [smsShowReceived, setSmsShowReceived] = useState(true);
46
47 useEffect(() => {
48 loadEmailLogs();
49 }, [emailFromDate, emailToDate]);
50
51 useEffect(() => {
52 if (activeTab === "sms" && smsLogs.length === 0) {
53 loadSmsLogs();
54 }
55 }, [activeTab]);
56
57 const loadEmailLogs = async () => {
58 try {
59 setLoading(true);
60 const params: Record<string, string> = {};
61
62 if (emailFromDate) params["101"] = emailFromDate;
63 if (emailToDate) params["102"] = emailToDate;
64
65 const response = await fetch("/api/v1/messaging/email/sendlog?" + new URLSearchParams(params));
66 const data = await response.json();
67
68 if (data.success && data.data?.DATS) {
69 setEmailLogs(data.data.DATS);
70 }
71 } catch (error) {
72 console.error("Error loading email logs:", error);
73 } finally {
74 setLoading(false);
75 }
76 };
77
78 const loadSmsLogs = async () => {
79 try {
80 setLoading(true);
81 const params: Record<string, string> = {
82 "8": smsRowLimit.toString(),
83 };
84
85 let onlyFilter = 0;
86 if (smsShowSent) onlyFilter |= 1;
87 if (smsShowReceived) onlyFilter |= 2;
88 if (onlyFilter > 0 && onlyFilter !== 3) {
89 params["160"] = onlyFilter.toString();
90 }
91
92 const response = await fetch("/api/v1/messaging/sms/sendlog?" + new URLSearchParams(params));
93 const data = await response.json();
94
95 if (data.success && data.data?.DATS) {
96 setSmsLogs(data.data.DATS);
97 }
98 } catch (error) {
99 console.error("Error loading SMS logs:", error);
100 } finally {
101 setLoading(false);
102 }
103 };
104
105 const formatDate = (dateStr: string) => {
106 if (!dateStr) return "";
107 const date = new Date(dateStr);
108 return date.toLocaleDateString() + " " + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
109 };
110
111 const getIspError = (jsonStr: string) => {
112 try {
113 const data = JSON.parse(jsonStr);
114 if (data.error?.description) {
115 const desc = data.error.description;
116 return typeof desc === 'object' ? (desc.reason || JSON.stringify(desc)) : desc;
117 }
118 } catch {
119 return "";
120 }
121 return "";
122 };
123
124 return (
125 <div className="min-h-screen bg-gray-50">
126 <div className="max-w-7xl mx-auto p-6">
127 {/* Header */}
128 <div className="mb-8">
129 <div className="flex items-center gap-3 mb-2">
130 <Link href="/marketing" className="text-gray-600 hover:text-gray-800">
131 Marketing
132 </Link>
133 <span className="text-gray-400">/</span>
134 <span className="text-gray-900 font-semibold">Email & Interfaces</span>
135 </div>
136 <h1 className="text-3xl font-bold text-gray-900 mb-2">Email &amp; Interfaces</h1>
137 <p className="text-gray-600 text-sm">
138 Email and Interfaces is all about external communications in and out of the POS. It covers email and Txt messaging,
139 and also more technical aspects such as FTP or automatic upload folders.
140 </p>
141 </div>
142
143 {/* Action Tiles */}
144 <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4 mb-8">
145 <Link href="/marketing/messaging/configure" className="bg-white rounded-lg border-2 border-[#00946b] shadow-md p-4 text-center hover:shadow-lg transition">
146 <div className="text-4xl mb-2">⚙️</div>
147 <span className="text-sm font-medium text-gray-900 hover:text-[#00946b]">
148 Configure Accounts
149 </span>
150 </Link>
151
152 <Link href="/marketing/messaging/bulk-email" className="bg-white rounded-lg border-2 border-[#00946b] shadow-md p-4 text-center hover:shadow-lg transition">
153 <div className="text-4xl mb-2">📧</div>
154 <span className="text-sm font-medium text-gray-900 hover:text-[#00946b]">
155 Send Bulk Email
156 </span>
157 </Link>
158
159 <Link href="/marketing/messaging/bulk-sms" className="bg-white rounded-lg border-2 border-[#00946b] shadow-md p-4 text-center hover:shadow-lg transition">
160 <div className="text-4xl mb-2">💬</div>
161 <span className="text-sm font-medium text-gray-900 hover:text-[#00946b]">
162 Send Bulk SMS
163 </span>
164 </Link>
165
166 <Link href="/marketing/messaging/txt-options" className="bg-white rounded-lg border-2 border-[#00946b] shadow-md p-4 text-center hover:shadow-lg transition">
167 <div className="text-4xl mb-2">📱</div>
168 <span className="text-sm font-medium text-gray-900 hover:text-[#00946b]">
169 Txt Options
170 </span>
171 </Link>
172
173 <Link href="/marketing/messaging/api-users" className="bg-white rounded-lg border-2 border-[#00946b] shadow-md p-4 text-center hover:shadow-lg transition">
174 <div className="text-4xl mb-2">🔑</div>
175 <span className="text-sm font-medium text-gray-900 hover:text-[#00946b]">
176 API Users
177 </span>
178 </Link>
179
180 <Link href="/marketing/messaging/pending" className="bg-white rounded-lg border-2 border-[#00946b] shadow-md p-4 text-center hover:shadow-lg transition">
181 <div className="text-4xl mb-2">⏳</div>
182 <span className="text-sm font-medium text-gray-900 hover:text-[#00946b]">
183 Pending Messages
184 </span>
185 </Link>
186
187 <Link href="/marketing/messaging/inbound" className="bg-white rounded-lg border-2 border-[#00946b] shadow-md p-4 text-center hover:shadow-lg transition">
188 <div className="text-4xl mb-2">📥</div>
189 <div className="text-sm font-medium text-gray-900 hover:text-[#00946b]">
190 Inbound Messages
191 </div>
192 </Link>
193 </div>
194
195 {/* Recent Activity Section */}
196 <div className="bg-white rounded-lg shadow-md">
197 <div className="border-b border-gray-200">
198 <h2 className="text-2xl font-bold text-gray-900 p-6">Recent Activity</h2>
199 <div className="flex border-t border-gray-200">
200 <button
201 onClick={() => setActiveTab("email")}
202 className={`px-6 py-3 font-medium transition ${
203 activeTab === "email"
204 ? "bg-white text-[#00946b] border-b-2 border-[#00946b]"
205 : "bg-gray-50 text-gray-600 hover:text-gray-900"
206 }`}
207 >
208 Email
209 </button>
210 <button
211 onClick={() => setActiveTab("sms")}
212 className={`px-6 py-3 font-medium transition ${
213 activeTab === "sms"
214 ? "bg-white text-[#00946b] border-b-2 border-[#00946b]"
215 : "bg-gray-50 text-gray-600 hover:text-gray-900"
216 }`}
217 >
218 Txt Messages
219 </button>
220 </div>
221 </div>
222
223 {/* Email Tab */}
224 {activeTab === "email" && (
225 <div className="p-6">
226 <div className="mb-4 flex gap-4 items-center">
227 <label className="text-sm text-gray-700">
228 From (incl):
229 <input
230 type="date"
231 value={emailFromDate}
232 onChange={(e) => setEmailFromDate(e.target.value)}
233 className="ml-2 px-3 py-1 border border-gray-300 rounded"
234 />
235 </label>
236 <label className="text-sm text-gray-700">
237 To (excl):
238 <input
239 type="date"
240 value={emailToDate}
241 onChange={(e) => setEmailToDate(e.target.value)}
242 className="ml-2 px-3 py-1 border border-gray-300 rounded"
243 />
244 </label>
245 </div>
246
247 {loading ? (
248 <div className="text-center py-8 text-gray-600">Loading...</div>
249 ) : (
250 <div className="overflow-x-auto">
251 <table className="w-full text-sm">
252 <thead className="bg-gray-50 border-b border-gray-200">
253 <tr>
254 <th className="px-4 py-3 text-left font-semibold text-gray-700">Send Date</th>
255 <th className="px-4 py-3 text-left font-semibold text-gray-700">Account</th>
256 <th className="px-4 py-3 text-left font-semibold text-gray-700" title="If ticked, indicates a copy of the message was received by Fieldpine, or otherwise confirmed as delivered">
257 Tx
258 </th>
259 <th className="px-4 py-3 text-left font-semibold text-gray-700">To</th>
260 <th className="px-4 py-3 text-left font-semibold text-gray-700">Subject</th>
261 </tr>
262 </thead>
263 <tbody>
264 {emailLogs.map((log, idx) => (
265 <tr
266 key={idx}
267 className={`border-b border-gray-100 hover:bg-gray-50 ${
268 log.f141 && log.f141 > 0 ? "bg-pink-50" : ""
269 }`}
270 >
271 <td className="px-4 py-3 text-gray-800">{formatDate(log.f109)}</td>
272 <td className="px-4 py-3 text-gray-800">{log.f108}</td>
273 <td className="px-4 py-3">
274 {log.f133 && <span className="text-green-600">✅</span>}
275 {log.f141 && log.f141 > 0 && (
276 <span className="ml-1" title="Email bounced and was not delivered">💥</span>
277 )}
278 </td>
279 <td className="px-4 py-3 text-gray-800">{log.f111}</td>
280 <td className="px-4 py-3 text-gray-800">{log.f132 || log.f112}</td>
281 </tr>
282 ))}
283 {emailLogs.length === 0 && (
284 <tr>
285 <td colSpan={5} className="px-4 py-8 text-center text-gray-500">
286 No email logs found
287 </td>
288 </tr>
289 )}
290 </tbody>
291 </table>
292 </div>
293 )}
294 </div>
295 )}
296
297 {/* SMS Tab */}
298 {activeTab === "sms" && (
299 <div className="p-6">
300 <div className="mb-4 flex gap-4 items-center flex-wrap">
301 <label className="text-sm text-gray-700">
302 Row Limit:
303 <input
304 type="number"
305 value={smsRowLimit}
306 onChange={(e) => setSmsRowLimit(parseInt(e.target.value) || 500)}
307 onBlur={loadSmsLogs}
308 className="ml-2 w-20 px-3 py-1 border border-gray-300 rounded"
309 />
310 </label>
311 <label className="flex items-center gap-2 text-sm text-gray-700">
312 <input
313 type="checkbox"
314 checked={smsShowSent}
315 onChange={(e) => {
316 setSmsShowSent(e.target.checked);
317 setTimeout(loadSmsLogs, 100);
318 }}
319 className="rounded"
320 />
321 Show Sent
322 </label>
323 <label className="flex items-center gap-2 text-sm text-gray-700">
324 <input
325 type="checkbox"
326 checked={smsShowReceived}
327 onChange={(e) => {
328 setSmsShowReceived(e.target.checked);
329 setTimeout(loadSmsLogs, 100);
330 }}
331 className="rounded"
332 />
333 Show Received
334 </label>
335 </div>
336
337 {loading ? (
338 <div className="text-center py-8 text-gray-600">Loading...</div>
339 ) : (
340 <div className="overflow-x-auto">
341 <table className="w-full text-sm">
342 <thead className="bg-gray-50 border-b border-gray-200">
343 <tr>
344 <th className="px-2 py-3 text-center font-semibold text-gray-700">Dir</th>
345 <th className="px-4 py-3 text-left font-semibold text-gray-700">Date</th>
346 <th className="px-4 py-3 text-left font-semibold text-gray-700">Account</th>
347 <th className="px-4 py-3 text-left font-semibold text-gray-700">Phone</th>
348 <th className="px-4 py-3 text-left font-semibold text-gray-700">Status</th>
349 <th className="px-4 py-3 text-left font-semibold text-gray-700">ISP Status</th>
350 <th className="px-4 py-3 text-left font-semibold text-gray-700">Delivered</th>
351 <th className="px-4 py-3 text-left font-semibold text-gray-700">Hard Bounce</th>
352 <th className="px-4 py-3 text-left font-semibold text-gray-700">Soft Bounce</th>
353 <th className="px-4 py-3 text-left font-semibold text-gray-700">Replied</th>
354 <th className="px-4 py-3 text-left font-semibold text-gray-700">Message</th>
355 </tr>
356 </thead>
357 <tbody>
358 {smsLogs.map((log, idx) => {
359 const direction = log.f426 || 0;
360 const statusOk = log.f400 === 1;
361 const deliveredDate = log.f420 || "";
362 const deliveredYear = deliveredDate ? new Date(deliveredDate).getFullYear() : 0;
363
364 return (
365 <tr key={idx} className="border-b border-gray-100 hover:bg-gray-50">
366 <td className="px-2 py-3 text-center text-xl">
367 {direction === 1 && (
368 <span className={statusOk ? "text-blue-500" : "text-pink-500"}>
369
370 </span>
371 )}
372 {direction === 2 && <span className="text-green-500">⇦</span>}
373 </td>
374 <td className="px-4 py-3 text-gray-800">{formatDate(log.f109)}</td>
375 <td className="px-4 py-3 text-gray-800">{log.f108}</td>
376 <td className="px-4 py-3 text-gray-800 font-mono">{log.f111}</td>
377 <td className="px-4 py-3 text-gray-800">{log.f401}</td>
378 <td className="px-4 py-3 text-gray-800">{getIspError(log.f402 || "")}</td>
379 <td className="px-4 py-3 text-gray-800">
380 {deliveredYear > 2020 && <span className="text-green-600">✅ </span>}
381 {deliveredDate}
382 </td>
383 <td className="px-4 py-3 text-gray-800">{log.f421}</td>
384 <td className="px-4 py-3 text-gray-800">{log.f422}</td>
385 <td className="px-4 py-3 text-gray-800">{log.f423}</td>
386 <td className="px-4 py-3 text-gray-800">{log.f112}</td>
387 </tr>
388 );
389 })}
390 {smsLogs.length === 0 && (
391 <tr>
392 <td colSpan={11} className="px-4 py-8 text-center text-gray-500">
393 No SMS logs found
394 </td>
395 </tr>
396 )}
397 </tbody>
398 </table>
399 </div>
400 )}
401 </div>
402 )}
403 </div>
404 </div>
405 </div>
406 );
407}