From d5ea45a536fe9ff1c55dff130d193d8dacf0d9bc Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 13 Sep 2026 18:52:42 +0700 Subject: [PATCH] x --- .../analytics/RepeatCustomersTimelinePage.tsx | 110 +++++++++++++----- 1 file changed, 80 insertions(+), 30 deletions(-) diff --git a/apps/admin/src/pages/analytics/RepeatCustomersTimelinePage.tsx b/apps/admin/src/pages/analytics/RepeatCustomersTimelinePage.tsx index 47af434..fc7a69e 100644 --- a/apps/admin/src/pages/analytics/RepeatCustomersTimelinePage.tsx +++ b/apps/admin/src/pages/analytics/RepeatCustomersTimelinePage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, Fragment } from 'react'; import { Clock, RefreshCw, @@ -429,42 +429,92 @@ export default function RepeatCustomersTimelinePage() { - {/* Horizontal Bar with Nodes */} -
-
- {/* Connecting line */} -
- - {/* Order Nodes */} + {/* Horizontal Bar with Nodes & Inter-order Days */} +
+
{cust.orders.map((o: any, oIdx: number) => { const isLast = oIdx === cust.orders.length - 1; + const nextOrder = cust.orders[oIdx + 1]; + + // Calculate interval days between this order and next order + let diffDays: number | null = null; + if (nextOrder?.timestamp && o?.timestamp) { + diffDays = Math.max(0, Math.round((nextOrder.timestamp - o.timestamp) / 86400000)); + } + return ( -
- {/* Node Circle */} + + {/* Order Node */}
setSelectedCustomer(cust)} + title={`ออเดอร์ #${o.orderId} — ${o.date} (${fmtBaht(o.amount)})`} > - {oIdx + 1} + {/* Node Circle */} +
+ {oIdx + 1} +
+ + {/* Node Label Below */} +
+ + {o.date.slice(5, 10)} + + + {fmtBaht(o.amount)} + +
- {/* Node Label Below */} -
- - {o.date.slice(5, 10)} - - - {fmtBaht(o.amount)} - -
-
+ {/* Connector Segment & Gap Days Floating Above Line */} + {nextOrder && ( +
+ {/* Connector Line */} +
+ + {/* Days Number Floating Above the Line */} + {diffDays !== null && ( + 90 + ? 'text-amber-400 font-bold' + : 'text-slate-400 group-hover/gap:text-white' + }`} + > + {diffDays} + + )} +
+ )} + + {/* Gap to Predicted Order */} + {isLast && ( +
+ {/* Dashed Connector Line */} +
+ + {/* Predicted Days Floating Above Line */} + + {Math.round(cust.avgIntervalDays)} + +
+ )} + ); })}