Skip to content

Commit f68e0c9

Browse files
authored
Merge pull request #411 from IndustryFusion/IFDPP-358-Frontend-Backend_CRUD_Contract
added last 10 days and intra day endpoint and chart.
2 parents 304cec1 + a6c9213 commit f68e0c9

6 files changed

Lines changed: 172 additions & 32 deletions

File tree

backend/src/endpoints/pgrest/pgrest.controller.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ export class PgRestController {
2525
private readonly tokenService: TokenService
2626
) {}
2727

28+
@Get('machine-state/10-days')
29+
async getTenDaysMachineState() {
30+
try{
31+
let token = await this.tokenService.getToken();
32+
return this.pgRestService.getTenDaysMachineState(token);
33+
} catch(err) {
34+
throw new NotFoundException("Error finding the details: " + err);
35+
}
36+
}
37+
38+
@Get('machine-state/intra-day')
39+
async getIntraDayMachineState() {
40+
try{
41+
let token = await this.tokenService.getToken();
42+
return this.pgRestService.getIntraDayMachineState(token);
43+
} catch(err) {
44+
throw new NotFoundException("Error finding the details: " + err);
45+
}
46+
}
47+
2848
@Get()
2949
async findAll(@Query() queryParams: any, key: string) {
3050
try{

backend/src/endpoints/pgrest/pgrest.service.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ import axios from 'axios';
1919
import { RedisService } from '../redis/redis.service';
2020
import * as moment from 'moment';
2121
import { AssetService } from '../asset/asset.service';
22+
import { HttpException, HttpStatus } from '@nestjs/common';
2223
@Injectable()
2324
export class PgRestService {
2425
private readonly timescaleUrl = process.env.TIMESCALE_URL;
26+
private readonly machineState10DaysUrl = process.env.MACHINE_STATE_10_DAYS_URL;
27+
private readonly machineStateIntraDayUrl = process.env.MACHINE_STATE_INTRA_DAY_URL;
2528
constructor(
2629
private redisService: RedisService,
2730
private readonly assetService: AssetService
@@ -122,4 +125,64 @@ export class PgRestService {
122125
}
123126

124127
}
128+
129+
async getTenDaysMachineState(token: string) {
130+
try {
131+
if (!token) {
132+
throw new Error("Authorization token is missing");
133+
}
134+
135+
const headers = {
136+
Authorization: `Bearer ${token}`
137+
};
138+
139+
const response = await axios.get(this.machineState10DaysUrl, { headers });
140+
return response.data;
141+
} catch(err) {
142+
if (err instanceof HttpException) {
143+
throw err;
144+
} else if (err.response) {
145+
throw new HttpException(err.response.data.message, err.response.status);
146+
} else {
147+
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
148+
}
149+
}
150+
}
151+
152+
async getIntraDayMachineState(token: string) {
153+
try {
154+
if (!token) {
155+
throw new Error("Authorization token is missing");
156+
}
157+
158+
const headers = {
159+
Authorization: `Bearer ${token}`
160+
};
161+
162+
const response = await axios.get(this.machineStateIntraDayUrl, { headers });
163+
const data = response.data;
164+
165+
// return only last 24hr data
166+
const endTime = new Date();
167+
const startTime = new Date(endTime.getTime() - 24 * 60 * 60 * 1000);
168+
169+
const filtered = data.filter((item) => {
170+
const [day, month, year] = item.date.split(".");
171+
const time = item.time;
172+
173+
const dateObj = new Date(`${year}-${month}-${day}T${time}:00`);
174+
175+
return dateObj >= startTime;
176+
});
177+
return filtered;
178+
} catch(err) {
179+
if (err instanceof HttpException) {
180+
throw err;
181+
} else if (err.response) {
182+
throw new HttpException(err.response.data.message, err.response.status);
183+
} else {
184+
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
185+
}
186+
}
187+
}
125188
}

frontend/src/components/dashboard/dashboard-charts.tsx

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,47 @@
1-
import React from 'react';
1+
import React, { useEffect, useState, useRef } from 'react';
22
import ReactECharts from 'echarts-for-react';
3+
import { getMachineState10Days, getMachineStateIntraDays } from '@/utility/chartUtility';
4+
import { showToast } from "@/utility/toast";
5+
import { Toast } from "primereact/toast";
6+
import axios from "axios";
37

4-
const StackedPercentageBarChart: React.FC = () => {
5-
const simulatedData = [
6-
{ date: '2025-05-17', Offline: 3, Error: 2, Maintenance: 1, Idle: 6, Running: 12 },
7-
{ date: '2025-05-18', Offline: 4, Error: 1, Maintenance: 2, Idle: 7, Running: 10 },
8-
{ date: '2025-05-19', Offline: 2, Error: 1, Maintenance: 3, Idle: 5, Running: 13 },
9-
{ date: '2025-05-20', Offline: 1, Error: 3, Maintenance: 2, Idle: 8, Running: 10 },
10-
{ date: '2025-05-21', Offline: 5, Error: 2, Maintenance: 1, Idle: 4, Running: 12 },
11-
{ date: '2025-05-22', Offline: 3, Error: 1, Maintenance: 2, Idle: 9, Running: 9 },
12-
{ date: '2025-05-23', Offline: 2, Error: 2, Maintenance: 1, Idle: 7, Running: 12 },
13-
{ date: '2025-05-24', Offline: 4, Error: 1, Maintenance: 3, Idle: 6, Running: 10 },
14-
{ date: '2025-05-25', Offline: 1, Error: 2, Maintenance: 2, Idle: 5, Running: 14 },
15-
{ date: '2025-05-26', Offline: 3, Error: 1, Maintenance: 2, Idle: 6, Running: 12 }
16-
];
8+
interface StackedPercentageBarChartProps {
9+
activityInterval: string;
10+
}
1711

18-
const states = ["Offline", "Error", "Maintenance", "Idle", "Running"]
12+
const StackedPercentageBarChart: React.FC<StackedPercentageBarChartProps> = ({activityInterval}) => {
13+
const [machineState, setMachineState] = useState<Record<string,any>[]>([]);
14+
const toast = useRef<Toast>(null);
15+
const fetchData = async () => {
16+
try {
17+
if(activityInterval === "10-days") {
18+
const response = await getMachineState10Days();
19+
setMachineState(response);
20+
} else {
21+
const response = await getMachineStateIntraDays();
22+
setMachineState(response);
23+
}
24+
} catch(error) {
25+
if (axios.isAxiosError(error) && error.response?.data?.message) {
26+
showToast(toast, "error", "Error", error.response.data.message);
27+
} else {
28+
showToast(toast, "error", "Error", "Error fetching machine state data");
29+
}
30+
}
31+
}
1932

20-
const series = states.slice().reverse().map((state, index, arr) => ({
33+
useEffect(() => {
34+
fetchData();
35+
},[activityInterval])
36+
37+
const stateMap = {
38+
"Offline": "hours_0",
39+
"Online Idle": "hours_1",
40+
"Online Running": "hours_2",
41+
};
42+
const states = Object.keys(stateMap);
43+
44+
const series = states.map((state, index, arr) => ({
2145
name: state,
2246
type: 'bar',
2347
stack: 'total',
@@ -26,7 +50,7 @@ const StackedPercentageBarChart: React.FC = () => {
2650
itemStyle: {
2751
borderRadius: index === 0 ? [0, 0, 4, 4] : index === arr.length - 1 ? [4, 4, 0, 0] : 0
2852
},
29-
data: simulatedData.map((d) => d[state]),
53+
data: machineState.map((d) => d[stateMap[state]]),
3054
}));
3155

3256
const option = {
@@ -37,15 +61,16 @@ const StackedPercentageBarChart: React.FC = () => {
3761
trigger: 'axis',
3862
formatter: (params: any) => {
3963
const dateStr = params[0].dataIndex !== undefined
40-
? simulatedData[params[0].dataIndex].date
64+
? machineState[params[0].dataIndex].date
4165
: '';
42-
const date = new Date(dateStr);
66+
67+
const [day, month, year] = dateStr.split('.');
68+
const validDateStr = `${year}-${month}-${day}`;
69+
70+
const date = new Date(validDateStr);
4371

4472
const dayNames = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
4573
const dayName = dayNames[date.getDay()];
46-
const day = String(date.getDate()).padStart(2, '0');
47-
const month = String(date.getMonth() + 1).padStart(2, '0');
48-
const year = date.getFullYear();
4974

5075
const total = params.reduce((sum: number, p: any) => sum + p.value, 0);
5176
const header = `${dayName}. ${day}.${month}.${year} <span>(${total}h)<span>`;
@@ -54,7 +79,7 @@ const StackedPercentageBarChart: React.FC = () => {
5479
.slice().reverse().map((p: any) => {
5580
const hours = p.value;
5681
const percent = ((hours / total) * 100).toFixed(1);
57-
return `<div class="muct_row"><div>${p.marker} ${p.seriesName === "Maintenance" ? "Maint." : p.seriesName}</div><div><span>${percent}%</span></div><div>${hours}<span>h</span></div></div>`;
82+
return `<div class="muct_row"><div>${p.marker} ${p.seriesName}</div><div><span>${percent}%</span></div><div>${hours}<span>h</span></div></div>`;
5883
})
5984
.join('');
6085
return `<div class="machine_uptime_chart_tooltip"><div class="muct_header">${header}</div><div class="muct_details">${details}<div></div>`;
@@ -63,11 +88,10 @@ const StackedPercentageBarChart: React.FC = () => {
6388
xAxis: {
6489
type: 'category',
6590
axisLine: { show: false },
66-
data: simulatedData.map((d) => {
67-
const date = new Date(d.date);
68-
const day = String(date.getDate()).padStart(2, '0');
69-
const month = String(date.getMonth() + 1).padStart(2, '0');
70-
return `${day}.${month}`;
91+
data: machineState.map((d) => {
92+
const [day, month, year] = d.date.split(".");
93+
const formatted = `${day}.${month}`;
94+
return formatted;
7195
}),
7296
axisTick: { show: false }
7397
},

frontend/src/pages/dashboard.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const DashboardPage: React.FC = () => {
3737
const [country, setCountry] = useState("")
3838
const [userImage, setUserImage] = useState("")
3939
const avatarLetter = (userName ?? "").trim().charAt(0).toUpperCase() || "";
40+
const [activityInterval, setActivityInterval] = useState<string>("10-days");
41+
4042
useEffect(() => {
4143
const fetchUserData = async (): Promise<void> => {
4244
try {
@@ -189,11 +191,11 @@ const DashboardPage: React.FC = () => {
189191
<div className="card-header">
190192
<span className="card-title">{t("asset_activity")}</span>
191193
<div className="tab-buttons">
192-
<button className="tab-button active">{t("chart_btn_1")}</button>
193-
<button className="tab-button">{t("chart_btn_2")}</button>
194+
<button className={`tab-button ${activityInterval === "10-days" ? "active" : ""}`} onClick={() => setActivityInterval("10-days")}>{t("chart_btn_1")}</button>
195+
<button className={`tab-button ${activityInterval === "intra-day" ? "active" : ""}`} onClick={() => setActivityInterval("intra-day")}>{t("chart_btn_2")}</button>
194196
</div>
195197
</div>
196-
<StackedPercentageBarChart />
198+
<StackedPercentageBarChart activityInterval={activityInterval}/>
197199
</div>
198200

199201
<div className="notification-card">

frontend/src/styles/dashboard-page.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ div:has(>.machine_uptime_chart_tooltip) {
381381
.muct_row {
382382
display: grid;
383383
gap: 12px;
384-
grid-template-columns: 76px 27px 34px;
384+
grid-template-columns: 76px 70px 34px;
385385
}
386386

387387
.muct_row>div:not(:first-child) {

frontend/src/utility/chartUtility.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,34 @@ export const fetchAssets = async (assetId: string) => {
119119

120120
return finalDays;
121121
};
122+
123+
export const getMachineState10Days = async () => {
124+
try {
125+
const response = await axios.get(API_URL + '/pgrest/machine-state/10-days', {
126+
headers: {
127+
"Content-Type": "application/json",
128+
Accept: "application/json",
129+
},
130+
withCredentials: true,
131+
});
132+
return response.data;
133+
} catch(err) {
134+
throw err;
135+
}
136+
}
137+
138+
export const getMachineStateIntraDays = async () => {
139+
try {
140+
const response = await axios.get(API_URL + '/pgrest/machine-state/intra-day', {
141+
headers: {
142+
"Content-Type": "application/json",
143+
Accept: "application/json",
144+
},
145+
withCredentials: true,
146+
});
147+
return response.data;
148+
} catch(err) {
149+
throw err;
150+
}
151+
}
152+

0 commit comments

Comments
 (0)