+ {/* ── Top Header Bar ────────────────────────────────────────── */}
+
+
+
+
+
+
+
+
+ {t('repeatCustomersActionPlan.title', 'แผนปฏิบัติการข้อเสนอ & โปรโมชันลูกค้าซื้อซ้ำ (CRM Action Plan)')}
+
+
+ วิเคราะห์รอบการซื้อซ้ำอัตโนมัติ พร้อมข้อเสนอแนะโปรโมชัน/โค้ดลดที่ตรงจุด เพื่อป้องกันยอดขายหลุดและเพิ่ม Repurchase Rate
+
+
+
+
+
+
+ fetchData()}
+ disabled={loading}
+ className="flex items-center gap-1.5 px-3 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-medium text-slate-300 hover:text-white transition-all disabled:opacity-50"
+ >
+
+ รีเฟรช
+
+
+ {/* Bulk Import to CRM Button */}
+ {
+ if (filteredCustomers.length === 0 || isBulkImporting) return;
+ setIsBulkImporting(true);
+ const unimported = filteredCustomers.filter((c) => {
+ const mapKey = `${c.primaryChannel}:${c.identifier.toLowerCase()}`;
+ return !crmImportedMap[mapKey]?.crmId;
+ });
+
+ if (unimported.length === 0) {
+ alert('ลูกค้าในกลุ่มนี้ทั้งหมดถูกนำเข้าสู่ CRM เรียบร้อยแล้ว');
+ setIsBulkImporting(false);
+ return;
+ }
+
+ const payload = {
+ customers: unimported.map((c) => {
+ const action = getActionRecommendation(c);
+ return {
+ channel: c.primaryChannel,
+ identifier: c.identifier,
+ displayName: c.displayName,
+ location: c.location || '',
+ orderCount: c.orderCount,
+ totalSpent: c.totalSpent,
+ avgIntervalDays: c.avgIntervalDays,
+ firstOrderDate: c.orders?.[0]?.date || '',
+ lastOrderDate: c.orders?.[c.orders.length - 1]?.date || '',
+ predictedNextOrderDate: c.predictedNextOrderDate || '',
+ topProducts: c.orders?.[c.orders.length - 1]?.productNames || [],
+ initialStatus: 'pending',
+ strategyType: action.strategyKey,
+ promoCode: action.promoCode,
+ discountOffered: action.suggestedDiscount,
+ };
+ }),
+ };
+
+ try {
+ const res = await fetch(`${API_BASE}/crm/import`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${localStorage.getItem('homaroma_admin_token')}`,
+ },
+ body: JSON.stringify(payload),
+ });
+ const data = await res.json();
+ if (data?.results) {
+ const updated = { ...crmImportedMap };
+ data.results.forEach((r: any) => {
+ const key = `${r.channel}:${r.identifier.toLowerCase()}`;
+ updated[key] = {
+ id: r.id,
+ crmId: r.crmId,
+ status: 'pending',
+ };
+ });
+ setCrmImportedMap(updated);
+ alert(`นำเข้าลูกค้าเข้าสู่ CRM สำเร็จ ${data.results.length} คน!`);
+ }
+ } catch (e) {
+ console.error('Failed bulk import:', e);
+ alert('เกิดข้อผิดพลาดในการนำเข้าข้อมูล');
+ } finally {
+ setIsBulkImporting(false);
+ }
+ }}
+ disabled={loading || isBulkImporting || filteredCustomers.length === 0}
+ className="flex items-center gap-1.5 px-3 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 border border-indigo-500/30 text-xs font-semibold text-white shadow-lg shadow-indigo-600/20 transition-all disabled:opacity-50"
+ title="นำเข้าลูกค้าในกลุ่มที่เลือกนี้เข้าสู่ระบบ CRM ทั้งหมด"
+ >
+
+
+ {isBulkImporting ? 'กำลังนำเข้า CRM...' : `Import เข้า CRM (${filteredCustomers.length} คน)`}
+
+
+
+
+
+ Export Action Plan CSV
+
+
+
+
+ {/* ── Nav Tabs Bar ───────────────────────────────────────────── */}
+
+
+ {/* ── Action Strategy KPI Cards ──────────────────────────────── */}
+
+ {/* Due Soon Card */}
+
{
+ setPriorityFilter((curr) => (curr === 'due_soon' ? 'all' : 'due_soon'));
+ setPage(1);
+ }}
+ className={`bg-slate-900/50 p-4 rounded-2xl border backdrop-blur-md cursor-pointer transition-all hover:scale-[1.02] ${
+ priorityFilter === 'due_soon'
+ ? 'border-amber-400 ring-2 ring-amber-400/50 bg-amber-950/25 shadow-lg shadow-amber-500/10'
+ : 'border-amber-500/20 hover:border-amber-500/40'
+ }`}
+ >
+
+ 🔥 ถึงรอบ Restock (7 วัน)
+
+
+
+ {fmtNum(kpiStats.dueSoonCount)} คน
+
+
เตือนก่อนของหมด (โค้ดลด 10%)
+
+
+ {/* Overdue Win-back */}
+
{
+ setPriorityFilter((curr) => (curr === 'overdue' ? 'all' : 'overdue'));
+ setPage(1);
+ }}
+ className={`bg-slate-900/50 p-4 rounded-2xl border backdrop-blur-md cursor-pointer transition-all hover:scale-[1.02] ${
+ priorityFilter === 'overdue'
+ ? 'border-rose-400 ring-2 ring-rose-400/50 bg-rose-950/25 shadow-lg shadow-rose-500/10'
+ : 'border-rose-500/20 hover:border-rose-500/40'
+ }`}
+ >
+
+
⚠️ เสี่ยงหลุดยอด (Win-Back)
+
+
+
+ {fmtNum(kpiStats.overdueCount)} คน
+
+
เกินรอบเฉลี่ย (Flash Voucher 20%)
+
+
+ {/* VIP Rewards */}
+
{
+ setPriorityFilter((curr) => (curr === 'vip' ? 'all' : 'vip'));
+ setPage(1);
+ }}
+ className={`bg-slate-900/50 p-4 rounded-2xl border backdrop-blur-md cursor-pointer transition-all hover:scale-[1.02] ${
+ priorityFilter === 'vip'
+ ? 'border-purple-400 ring-2 ring-purple-400/50 bg-purple-950/25 shadow-lg shadow-purple-500/10'
+ : 'border-purple-500/20 hover:border-purple-500/40'
+ }`}
+ >
+
+ 👑 VIP Rewards
+
+
+
+ {fmtNum(kpiStats.vipCount)} คน
+
+
ยอด ≥ ฿4,000 หรือ ≥ 5 ออเดอร์
+
+
+ {/* Healthy / Nurturing */}
+
{
+ setPriorityFilter((curr) => (curr === 'healthy' ? 'all' : 'healthy'));
+ setPage(1);
+ }}
+ className={`bg-slate-900/50 p-4 rounded-2xl border backdrop-blur-md cursor-pointer transition-all hover:scale-[1.02] ${
+ priorityFilter === 'healthy'
+ ? 'border-emerald-400 ring-2 ring-emerald-400/50 bg-emerald-950/25 shadow-lg shadow-emerald-500/10'
+ : 'border-emerald-500/20 hover:border-emerald-500/40'
+ }`}
+ >
+
+ ✨ ปกติ (Active Nurture)
+
+
+
+ {fmtNum(kpiStats.healthyCount)} คน
+
+
ยังไม่ถึงรอบซื้อ (สร้างสัมพันธ์)
+
+
+
+ {/* ── Filters & Search Controls ──────────────────────────────── */}
+
+ {/* Priority Filter */}
+
+ {
+ setPriorityFilter('all');
+ setPage(1);
+ }}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ priorityFilter === 'all'
+ ? 'bg-purple-600 text-white shadow-md font-semibold'
+ : 'text-slate-400 hover:text-white hover:bg-white/5'
+ }`}
+ >
+ กลุ่มเป้าหมายทั้งหมด ({kpiStats.totalCount})
+
+ {
+ setPriorityFilter('due_soon');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ priorityFilter === 'due_soon'
+ ? 'bg-amber-600 text-white shadow-md font-semibold'
+ : 'text-slate-400 hover:text-white hover:bg-white/5'
+ }`}
+ >
+ 🔥 ถึงรอบซื้อ ({kpiStats.dueSoonCount})
+
+ {
+ setPriorityFilter('overdue');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ priorityFilter === 'overdue'
+ ? 'bg-rose-600 text-white shadow-md font-semibold'
+ : 'text-slate-400 hover:text-white hover:bg-white/5'
+ }`}
+ >
+ ⚠️ เสี่ยงหลุด ({kpiStats.overdueCount})
+
+ {
+ setPriorityFilter('vip');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ priorityFilter === 'vip'
+ ? 'bg-purple-600 text-white shadow-md font-semibold'
+ : 'text-slate-400 hover:text-white hover:bg-white/5'
+ }`}
+ >
+ 👑 VIP ({kpiStats.vipCount})
+
+ {
+ setPriorityFilter('healthy');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ priorityFilter === 'healthy'
+ ? 'bg-emerald-600 text-white shadow-md font-semibold'
+ : 'text-slate-400 hover:text-white hover:bg-white/5'
+ }`}
+ >
+ ✨ ปกติ ({kpiStats.healthyCount})
+
+
+
+ {/* Channel Selection */}
+
+ {
+ setChannel('all');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ channel === 'all' ? 'bg-white/15 text-white font-semibold' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ ทุกช่องทาง
+
+ {
+ setChannel('tiktok');
+ setPage(1);
+ }}
+ className={`px-2 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ channel === 'tiktok' ? 'bg-rose-600 text-white font-semibold' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ TikTok
+
+ {
+ setChannel('shopee');
+ setPage(1);
+ }}
+ className={`px-2 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ channel === 'shopee' ? 'bg-orange-600 text-white font-semibold' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Shopee
+
+ {
+ setChannel('lazada');
+ setPage(1);
+ }}
+ className={`px-2 py-1.5 rounded-lg text-xs font-medium transition-all ${
+ channel === 'lazada' ? 'bg-blue-600 text-white font-semibold' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ Lazada
+
+
+
+ {/* CRM Status Filter */}
+
+ สถานะ CRM:
+ {
+ setCrmStatusFilter('all');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1 rounded-lg text-xs font-medium transition-all ${
+ crmStatusFilter === 'all' ? 'bg-white/15 text-white' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ ทั้งหมด
+
+ {
+ setCrmStatusFilter('pending');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1 rounded-lg text-xs font-medium transition-all ${
+ crmStatusFilter === 'pending' ? 'bg-amber-600/80 text-white' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ ⏳ รอดำเนินการ
+
+ {
+ setCrmStatusFilter('contacted');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1 rounded-lg text-xs font-medium transition-all ${
+ crmStatusFilter === 'contacted' ? 'bg-indigo-600/80 text-white' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ 💬 ส่งโปรแล้ว
+
+ {
+ setCrmStatusFilter('converted');
+ setPage(1);
+ }}
+ className={`px-2.5 py-1 rounded-lg text-xs font-medium transition-all ${
+ crmStatusFilter === 'converted' ? 'bg-emerald-600/80 text-white' : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ ✅ ซื้อซ้ำสำเร็จ
+
+
+
+ {/* Search */}
+
+
+ setSearch(e.target.value)}
+ placeholder="ค้นหาชื่อลูกค้า, สินค้า, ID..."
+ className="w-full pl-9 pr-3 py-1.5 text-xs bg-slate-950 border border-white/10 rounded-xl text-slate-200 placeholder-slate-500 focus:outline-none focus:border-purple-500 transition-all"
+ />
+
+
+
+ {/* ── Action Plan Table List ─────────────────────────────────── */}
+
+ {loading ? (
+
+
+ กำลังวิเคราะห์ข้อมูลและสร้าง Action Plan รายบุคคล...
+
+ ) : paginatedCustomers.length === 0 ? (
+
+
+ ไม่พบข้อมูลลูกค้าที่ตรงตามเงื่อนไขที่เลือก
+
+ ) : (
+
+ {paginatedCustomers.map((cust, idx) => {
+ const action = getActionRecommendation(cust);
+ const mapKey = `${cust.primaryChannel}:${cust.identifier.toLowerCase()}`;
+ const crmStatus = crmImportedMap[mapKey]?.status || localCrmStatusMap[cust.id] || 'pending';
+ const isCopied = copiedId === cust.id;
+
+ return (
+
+ {/* Top Bar: Customer & Cycle Status */}
+
+
+ {/* Avatar */}
+
handleCustomerClick(cust)}
+ className="w-10 h-10 rounded-xl bg-slate-800/90 border border-white/10 flex items-center justify-center font-bold text-sm text-purple-400 cursor-pointer hover:bg-purple-600/20 hover:border-purple-500/40 hover:scale-105 transition-all"
+ title="คลิกเพื่อดูรายละเอียดลูกค้า"
+ >
+ {cust.displayName.slice(0, 2)}
+
+
+
+
+ handleCustomerClick(cust)}
+ className="font-bold text-base text-white hover:text-purple-300 hover:underline cursor-pointer transition-colors"
+ title="คลิกเพื่อดูรายละเอียดลูกค้า"
+ >
+ {cust.displayName}
+
+ {getChannelBadge(cust.primaryChannel)}
+
+ {action.strategyTitle}
+
+
+
+
+
+ ยอดสะสม {fmtBaht(cust.totalSpent)}
+
+ •
+
+ {cust.orderCount} คำสั่งซื้อ
+
+ {cust.location && (
+ <>
+ •
+ {cust.location}
+ >
+ )}
+
+
+
+
+ {/* Cycle Indicators */}
+
+
+ รอบซื้อเฉลี่ย
+
+ {cust.avgIntervalDays} วัน
+
+
+
+
+
คาดการณ์รอบถัดไป
+
+
+ {cust.predictedNextOrderDate}
+
+ {renderPredictedDateDiff(cust.predictedNextOrderDate)}
+
+
+
+
+
+ {/* Middle Bar: Recommended Offer & Actionable Strategy */}
+
+
+
+
+ กลยุทธ์ที่แนะนำ:
+
+
{action.recommendationText}
+
+
+
+
+
+ ข้อเสนอ / โค้ดลดที่แนะนำ:
+
+
+
+ {action.promoCode}
+
+ {action.suggestedDiscount}
+
+
+
+
+
+
+ สินค้าเดิมที่ควรเสนอเติม:
+
+
+ {cust.lastOrderProduct || 'น้ำมันหอมระเหย / น้ำหอมปรับอากาศ'}
+
+
+
+
+ {/* Bottom Actions: CRM Status Toggle, Import Button & Quick Copy Template */}
+
+ {/* CRM Status Toggle */}
+
+
สถานะ CRM:
+
+ updateCrmStatus(cust, 'pending')}
+ className={`px-2.5 py-1 rounded text-[11px] font-medium transition-all ${
+ crmStatus === 'pending'
+ ? 'bg-amber-600 text-white font-semibold'
+ : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ ⏳ รอดำเนินการ
+
+ updateCrmStatus(cust, 'contacted')}
+ className={`px-2.5 py-1 rounded text-[11px] font-medium transition-all ${
+ crmStatus === 'contacted'
+ ? 'bg-indigo-600 text-white font-semibold'
+ : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ 💬 ส่งโปรแล้ว
+
+ updateCrmStatus(cust, 'converted')}
+ className={`px-2.5 py-1 rounded text-[11px] font-medium transition-all ${
+ crmStatus === 'converted'
+ ? 'bg-emerald-600 text-white font-semibold'
+ : 'text-slate-400 hover:text-white'
+ }`}
+ >
+ ✅ ซื้อซ้ำสำเร็จ
+
+
+
+
+ {/* Action Buttons */}
+
+ {/* Import Customer Button / Badge */}
+ {crmImportedMap[`${cust.primaryChannel}:${cust.identifier.toLowerCase()}`]?.crmId ? (
+
+
+ นำเข้า CRM แล้ว
+
+ ) : (
+ importSingleCustomer(cust)}
+ disabled={importingId === cust.id}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600/20 hover:bg-indigo-600/30 text-indigo-300 hover:text-white text-xs font-medium border border-indigo-500/30 transition-all disabled:opacity-50"
+ title="นำเข้าลูกค้ารายนี้เข้าสู่ระบบ CRM เพื่อบันทึกติดตามผลจริง"
+ >
+
+ {importingId === cust.id ? 'กำลังนำเข้า...' : 'Import เข้า CRM'}
+
+ )}
+
+ {/* Copy Chat Message Template */}
+ handleCopyTemplate(cust)}
+ className={`flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-medium border transition-all ${
+ isCopied
+ ? 'bg-emerald-600/30 text-emerald-300 border-emerald-500/40'
+ : 'bg-white/5 hover:bg-white/10 text-slate-300 hover:text-white border-white/10'
+ }`}
+ title="คัดลอกข้อความแนะนำส่ง Chat / Inbox"
+ >
+ {isCopied ? (
+ <>
+
+ คัดลอกข้อความแล้ว!
+ >
+ ) : (
+ <>
+
+ Copy ข้อความทักแชท
+ >
+ )}
+
+
+ {/* View Customer Details Full Page */}
+ handleCustomerClick(cust)}
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 hover:text-white text-xs font-medium border border-purple-500/30 transition-all"
+ >
+
+ ดูโปรไฟล์ลูกค้า
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ {/* ── Pagination Bar ─────────────────────────────────────────── */}
+ {totalPages > 1 && (
+
+
+
+ กำลังแสดงหน้า {page} จาก{' '}
+ {totalPages} หน้า (ทั้งหมด {fmtNum(totalItems)} รายการ)
+
+
+ แสดง:
+ {
+ setLimit(Number(e.target.value));
+ setPage(1);
+ }}
+ className="bg-slate-950 border border-white/10 text-slate-300 text-xs rounded-lg px-2 py-1 focus:outline-none focus:border-purple-500"
+ >
+ 10 คน
+ 20 คน
+ 50 คน
+
+
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ disabled={page <= 1 || loading}
+ className="p-1.5 rounded-lg bg-white/5 hover:bg-white/10 text-slate-300 hover:text-white disabled:opacity-30 border border-white/5"
+ >
+
+
+
+
+ {page} / {totalPages}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ disabled={page >= totalPages || loading}
+ className="p-1.5 rounded-lg bg-white/5 hover:bg-white/10 text-slate-300 hover:text-white disabled:opacity-30 border border-white/5"
+ >
+
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/admin/src/pages/analytics/RepeatCustomersPage.tsx b/apps/admin/src/pages/analytics/RepeatCustomersPage.tsx
index 14e0d4e..04cf18b 100644
--- a/apps/admin/src/pages/analytics/RepeatCustomersPage.tsx
+++ b/apps/admin/src/pages/analytics/RepeatCustomersPage.tsx
@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
+import { useNavigate } from 'react-router-dom';
import {
Users,
UserCheck,
@@ -27,6 +28,7 @@ type SortBy = 'orders' | 'totalSpent' | 'lastOrderDate' | 'daysBetween';
export default function RepeatCustomersPage() {
const { t } = useTranslation();
+ const navigate = useNavigate();
// Filters state
const [channel, setChannel] = useState