Skip to content

Commit fb5bd0b

Browse files
authored
Merge pull request #205 from SynTech-T5/feature/ui-event-count
feature/ui-event-count_V1.0.0
2 parents 6daf0d1 + f8bdf99 commit fb5bd0b

File tree

2 files changed

+206
-10
lines changed

2 files changed

+206
-10
lines changed

client/my-app/src/app/(with-layout)/analytics/page.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
11
import AnalyticsView from "@/components/features/analytics/AnalyticsView";
22
import TimeBasedAlertDistribution from "@/components/features/analytics/chart/TimeBasedAlertDistribution";
33
import AiAccuracyChart from "@/components/features/analytics/chart/AiAccuracyChart";
4+
import EventCount from "@/components/features/analytics/chart/EventCount";
45
import AlertResolutionRate from "@/components/features/analytics/chart/AlertResolutionRate";
56

67
export default function Analytics() {
78
return (
89
<div className="space-y-6">
10+
11+
{/* Top Section */}
912
<div className="grid grid-cols-[repeat(auto-fit,minmax(320px,1fr))] gap-6">
1013
<AnalyticsView />
1114
</div>
1215

13-
14-
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6">
15-
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6 mt-6">
16-
<TimeBasedAlertDistribution />
17-
</div>
18-
</div>
16+
{/* Main 2 Columns Layout */}
17+
<div className="grid grid-cols-1 lg:grid-cols-[2fr,1fr] gap-6">
1918

20-
21-
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6">
22-
<AiAccuracyChart />
23-
</div>
19+
{/* LEFT SIDE */}
20+
<div className="space-y-6">
21+
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6">
22+
<TimeBasedAlertDistribution />
23+
</div>
2424

25+
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6">
26+
<AiAccuracyChart />
27+
</div>
28+
</div>
2529

30+
{/* RIGHT SIDE */}
31+
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6">
32+
<EventCount />
33+
</div>
34+
</div>
2635

2736
<div className="rounded-lg bg-[var(--color-white)] shadow-md p-6">
2837
<AlertResolutionRate />
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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

Comments
 (0)