3import React, { useState, useEffect } from "react";
4import Link from "next/link";
5import { fieldpineServerApi } from "@/lib/server/fieldpineApi";
8 f108: string; // Account
9 f109: string; // Send Date
11 f112: string; // Message/Subject (fallback)
12 f132: string; // Subject
13 f133: string; // Confirmed delivered
14 f141?: number; // Bounce flag
18 f108: string; // Account
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)
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);
39 const [emailFromDate, setEmailFromDate] = useState("");
40 const [emailToDate, setEmailToDate] = useState("");
43 const [smsRowLimit, setSmsRowLimit] = useState(500);
44 const [smsShowSent, setSmsShowSent] = useState(true);
45 const [smsShowReceived, setSmsShowReceived] = useState(true);
49 }, [emailFromDate, emailToDate]);
52 if (activeTab === "sms" && smsLogs.length === 0) {
57 const loadEmailLogs = async () => {
60 const params: Record<string, string> = {};
62 if (emailFromDate) params["101"] = emailFromDate;
63 if (emailToDate) params["102"] = emailToDate;
65 const response = await fetch("/api/v1/messaging/email/sendlog?" + new URLSearchParams(params));
66 const data = await response.json();
68 if (data.success && data.data?.DATS) {
69 setEmailLogs(data.data.DATS);
72 console.error("Error loading email logs:", error);
78 const loadSmsLogs = async () => {
81 const params: Record<string, string> = {
82 "8": smsRowLimit.toString(),
86 if (smsShowSent) onlyFilter |= 1;
87 if (smsShowReceived) onlyFilter |= 2;
88 if (onlyFilter > 0 && onlyFilter !== 3) {
89 params["160"] = onlyFilter.toString();
92 const response = await fetch("/api/v1/messaging/sms/sendlog?" + new URLSearchParams(params));
93 const data = await response.json();
95 if (data.success && data.data?.DATS) {
96 setSmsLogs(data.data.DATS);
99 console.error("Error loading SMS logs:", error);
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' });
111 const getIspError = (jsonStr: string) => {
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;
125 <div className="min-h-screen bg-gray-50">
126 <div className="max-w-7xl mx-auto p-6">
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">
133 <span className="text-gray-400">/</span>
134 <span className="text-gray-900 font-semibold">Email & Interfaces</span>
136 <h1 className="text-3xl font-bold text-gray-900 mb-2">Email & 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.
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]">
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]">
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]">
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]">
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]">
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]">
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]">
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">
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"
211 onClick={() => setActiveTab("sms")}
212 className={`px-6 py-3 font-medium transition ${
214 ? "bg-white text-[#00946b] border-b-2 border-[#00946b]"
215 : "bg-gray-50 text-gray-600 hover:text-gray-900"
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">
231 value={emailFromDate}
232 onChange={(e) => setEmailFromDate(e.target.value)}
233 className="ml-2 px-3 py-1 border border-gray-300 rounded"
236 <label className="text-sm text-gray-700">
241 onChange={(e) => setEmailToDate(e.target.value)}
242 className="ml-2 px-3 py-1 border border-gray-300 rounded"
248 <div className="text-center py-8 text-gray-600">Loading...</div>
250 <div className="overflow-x-auto">
251 <table className="w-full text-sm">
252 <thead className="bg-gray-50 border-b border-gray-200">
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">
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>
264 {emailLogs.map((log, idx) => (
267 className={`border-b border-gray-100 hover:bg-gray-50 ${
268 log.f141 && log.f141 > 0 ? "bg-pink-50" : ""
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>
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>
283 {emailLogs.length === 0 && (
285 <td colSpan={5} className="px-4 py-8 text-center text-gray-500">
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">
306 onChange={(e) => setSmsRowLimit(parseInt(e.target.value) || 500)}
308 className="ml-2 w-20 px-3 py-1 border border-gray-300 rounded"
311 <label className="flex items-center gap-2 text-sm text-gray-700">
314 checked={smsShowSent}
316 setSmsShowSent(e.target.checked);
317 setTimeout(loadSmsLogs, 100);
323 <label className="flex items-center gap-2 text-sm text-gray-700">
326 checked={smsShowReceived}
328 setSmsShowReceived(e.target.checked);
329 setTimeout(loadSmsLogs, 100);
338 <div className="text-center py-8 text-gray-600">Loading...</div>
340 <div className="overflow-x-auto">
341 <table className="w-full text-sm">
342 <thead className="bg-gray-50 border-b border-gray-200">
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>
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;
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"}>
372 {direction === 2 && <span className="text-green-500">⇦</span>}
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>}
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>
390 {smsLogs.length === 0 && (
392 <td colSpan={11} className="px-4 py-8 text-center text-gray-500">