1+ "use client" ;
2+
3+ import React from "react" ;
4+ import dynamic from "next/dynamic" ;
5+ import type { ApexOptions } from "apexcharts" ;
6+
7+ const ReactApexChart = dynamic ( ( ) => import ( "react-apexcharts" ) , {
8+ ssr : false ,
9+ } ) ;
10+
11+ type EventCountChartProps = {
12+ height ?: number ;
13+ } ;
14+
15+ type ApexSeries = NonNullable < ApexOptions [ "series" ] > ;
16+
17+ // ===== DATA =====
18+ const EVENTS = [
19+ "Event 1" ,
20+ "Event 2" ,
21+ "Event 3" ,
22+ "Event 4" ,
23+ "Event 5" ,
24+ "Event 6" ,
25+ "Event 7" ,
26+ "Event 8" ,
27+ "Event 9" ,
28+ "Event 10" ,
29+ "Event 11" ,
30+ "Event 12" ,
31+ "Event 13" ,
32+ "Event 14" ,
33+ "Event 15" ,
34+ "Event 16" ,
35+ ] ;
36+
37+ const EVENT_COUNTS = [
38+ 80 , 35 , 70 , 65 , 20 , 55 , 75 , 95 , 60 , 80 , 30 , 15 , 45 , 45 , 90 , 50 ,
39+ ] ;
40+
41+ // ===== Scroll / height settings =====
42+ const MAX_VISIBLE_EVENTS = 10 ; // จำนวนแถวที่โชว์ก่อนจะเริ่ม scroll
43+ const ROW_HEIGHT = 38 ; // px ต่อแถว
44+ const NEED_SCROLL = EVENTS . length > MAX_VISIBLE_EVENTS ;
45+
46+ const computedMaxHeight = NEED_SCROLL
47+ ? MAX_VISIBLE_EVENTS * ROW_HEIGHT
48+ : undefined ;
49+
50+ const fullChartHeight = Math . max ( EVENTS . length * ROW_HEIGHT , 120 ) ;
51+
52+ const AXIS_LABEL_STYLE = {
53+ colors : "#6b7280" ,
54+ fontSize : "12px" ,
55+ } ;
56+
57+ // ===== MAX_VALUE (อิงจาก data) =====
58+ // หา max จาก data แล้วเผื่อ 10% เพื่อไม่ให้แท่งแตะขอบแกนพอดี
59+ const maxData = Math . max ( ...EVENT_COUNTS , 0 ) ;
60+
61+ // ถ้า maxData = 0 จะกันหาร 0/undefined
62+ const PADDING_RATIO = 1.1 ; // เผื่อ 10%
63+ const roundTo = 10 ; // ปัดเป็นเลขกลมตามหน่วยนี้ (10 => 270 -> 280)
64+ const padded = Math . ceil ( maxData * PADDING_RATIO || 1 ) ;
65+ const MAX_VALUE = Math . max (
66+ padded % roundTo === 0 ? padded : Math . ceil ( padded / roundTo ) * roundTo ,
67+ roundTo
68+ ) ;
69+ const TOTAL_EVENTS = EVENT_COUNTS . reduce ( ( sum , v ) => sum + v , 0 ) ;
70+
71+ // ===== CHART OPTIONS =====
72+ const chartOptions = ( maxValue : number ) : ApexOptions => ( {
73+ chart : {
74+ type : "bar" ,
75+ stacked : true ,
76+ toolbar : { show : false } ,
77+ background : "transparent" ,
78+ parentHeightOffset : 0 ,
79+ } ,
80+
81+ plotOptions : {
82+ bar : {
83+ horizontal : true ,
84+ barHeight : "60%" ,
85+ borderRadius : 4 ,
86+ } ,
87+ } ,
88+
89+ dataLabels : {
90+ enabled : false ,
91+ } ,
92+
93+ grid : {
94+ borderColor : "#e5e7eb" ,
95+ strokeDashArray : 4 ,
96+ xaxis : { lines : { show : true } } ,
97+ yaxis : { lines : { show : true } } ,
98+ } ,
99+
100+ xaxis : {
101+ position : "top" ,
102+ categories : EVENTS ,
103+ min : 0 ,
104+ max : maxValue , // ใช้ MAX_VALUE ที่คำนวณแล้ว
105+ tickAmount : 10 ,
106+ labels : {
107+ style : AXIS_LABEL_STYLE ,
108+ } ,
109+ axisBorder : { show : false } ,
110+ axisTicks : { show : false } ,
111+ } ,
112+
113+ yaxis : {
114+ labels : {
115+ style : AXIS_LABEL_STYLE ,
116+ } ,
117+ } ,
118+
119+ tooltip : {
120+ theme : "light" ,
121+ y : {
122+ formatter : ( val : number ) => `${ val } ` ,
123+ } ,
124+ } ,
125+
126+ legend : {
127+ show : false ,
128+ } ,
129+ } ) ;
130+
131+ // ===== SERIES (ค่าจริง + remaining) =====
132+ const chartSeries : ApexSeries = [
133+ {
134+ name : "Event Count" ,
135+ data : EVENT_COUNTS ,
136+ color : "#a0a1ff" ,
137+ } ,
138+ {
139+ name : "Remaining" ,
140+ data : EVENT_COUNTS . map ( ( v ) => Math . max ( 0 , MAX_VALUE - v ) ) ,
141+ color : "#f3f4f6" ,
142+ } ,
143+ ] ;
144+
145+ export default function EventCountChart ( { height = 380 } : EventCountChartProps ) {
146+ const chartHeight = NEED_SCROLL ? fullChartHeight : height ;
147+
148+ return (
149+ < div className = "w-full rounded-3xl bg-white px-6 py-5 shadow-sm border border-[#e5e7f5]" >
150+ < h2 className = "text-lg font-bold text-[var(--color-primary,#111827)]" >
151+ Event Count
152+ </ h2 >
153+
154+ { /* Scroll Container */ }
155+ < div
156+ className = "w-full md:overflow-visible overflow-x-auto"
157+ style = { {
158+ maxHeight : computedMaxHeight ? `${ computedMaxHeight } px` : "auto" ,
159+ overflowY : NEED_SCROLL ? "auto" : "visible" ,
160+ } }
161+ >
162+ < div className = "min-w-[700px] md:min-w-0" >
163+ < ReactApexChart
164+ options = { chartOptions ( MAX_VALUE ) }
165+ series = { chartSeries }
166+ type = "bar"
167+ height = { chartHeight }
168+ />
169+ </ div >
170+ </ div >
171+
172+ { /* Legend */ }
173+ < div className = "mt-4 flex flex-wrap items-center justify-center gap-3 text-xs text-slate-600" >
174+ < div className = "flex items-center gap-2" >
175+ < span className = "h-2.5 w-2.5 rounded-full bg-[#a0a1ff]" />
176+ < span className = "whitespace-nowrap" > Event Count</ span >
177+ </ div >
178+ < div className = "flex items-center gap-2" >
179+ { /* <span className="h-2.5 w-2.5 rounded-full bg-[#f3f4f6]" /> */ }
180+ < span className = "whitespace-nowrap" >
181+ Total: { TOTAL_EVENTS . toLocaleString ( ) }
182+ </ span >
183+ </ div >
184+ </ div >
185+ </ div >
186+ ) ;
187+ }
0 commit comments