ok
This commit is contained in:
@@ -1,126 +1,149 @@
|
|||||||
import React, { useRef, useEffect } from 'react';
|
import React, { useState } from "react";
|
||||||
import ReactApexChart from 'react-apexcharts';
|
import Chart from "react-apexcharts";
|
||||||
|
import styles from "./BarChart.module.css"; // Import the CSS module
|
||||||
|
|
||||||
// Colors palette
|
const BarChart = ({ transactionGraph, colors }) => {
|
||||||
const colors = [
|
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||||
"#D0E14F", "#B2B83D", "#A9C96E", "#A8B64E",
|
|
||||||
"#FF6347", "#FF4500", "#FF8C00", "#FF7F50",
|
const processData = (graphData) => {
|
||||||
"#1E90FF", "#FF00FF", "#32CD32",
|
if (!graphData) return null;
|
||||||
"#00CED1", "#FFD700", "#FF1493", "#8A2BE2",
|
return graphData.map((dayData) => {
|
||||||
"#FFDAB9", "#F0E68C", "#8B4513", "#4B0082", "#C71585",
|
const categories = [
|
||||||
"#FFB6C1", "#E6E6FA", "#98FB98", "#F0FFF0", "#D3D3D3"
|
"0-3",
|
||||||
|
"3-6",
|
||||||
|
"6-9",
|
||||||
|
"9-12",
|
||||||
|
"12-15",
|
||||||
|
"15-18",
|
||||||
|
"18-21",
|
||||||
|
"21-24",
|
||||||
];
|
];
|
||||||
|
|
||||||
const SimpleLineChart = ({ Data }) => {
|
const seriesData = [
|
||||||
const lastMonthRef = useRef('');
|
dayData.hour0To3Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
const lastYearRef = useRef('');
|
dayData.hour3To6Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
const usedColors = useRef([]); // Keep track of used colors
|
dayData.hour6To9Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
|
dayData.hour9To12Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
|
dayData.hour12To15Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
|
dayData.hour15To18Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
|
dayData.hour18To21Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
|
dayData.hour21To24Transactions.reduce((acc, t) => acc + t.sold, 0),
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
usedColors.current = [];
|
|
||||||
}, [Data]);
|
|
||||||
|
|
||||||
// Ensure Data is not null or undefined
|
|
||||||
const transformedData = (Data?.length ? Data.reduce((acc, { date, materialId, priceAtp, stockDifference, name }) => {
|
|
||||||
function isSameDay(date1, date2) {
|
|
||||||
const d1 = new Date(date1);
|
|
||||||
const d2 = new Date(date2);
|
|
||||||
|
|
||||||
return d1.getFullYear() === d2.getFullYear() &&
|
|
||||||
d1.getMonth() === d2.getMonth() &&
|
|
||||||
d1.getDate() === d2.getDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingEntry = acc.find(d => isSameDay(d.date, date));
|
|
||||||
|
|
||||||
if (existingEntry) {
|
|
||||||
existingEntry[name] = (existingEntry[name] || 0) + (priceAtp * stockDifference);
|
|
||||||
} else {
|
|
||||||
acc.push({
|
|
||||||
date,
|
|
||||||
materialId,
|
|
||||||
name,
|
|
||||||
[name]: priceAtp * stockDifference
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []) : []);
|
|
||||||
|
|
||||||
const sortedData = [...transformedData].sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
||||||
const oldestDate = sortedData[0]?.date;
|
|
||||||
|
|
||||||
const uniqueMaterials = Array.from(new Set(Data?.map(item => item.name) || []));
|
|
||||||
const uniqueDates = Array.from(new Set(transformedData.map(item => item.date)));
|
|
||||||
|
|
||||||
const getNextColor = () => {
|
|
||||||
const randomIndex = Math.floor(Math.random() * colors.length);
|
|
||||||
let color = null;
|
|
||||||
for (let i = 0; i < colors.length; i++) {
|
|
||||||
const index = (randomIndex + i) % colors.length;
|
|
||||||
if (!usedColors.current.includes(colors[index])) {
|
|
||||||
color = colors[index];
|
|
||||||
usedColors.current.push(color);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return color || '#000000';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prepare data for ApexCharts
|
|
||||||
const series = uniqueMaterials.map((material) => {
|
|
||||||
return {
|
return {
|
||||||
name: material,
|
date: new Date(dayData.date).toLocaleDateString(),
|
||||||
data: transformedData.map(item => item[material] || 0)
|
categories,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: `Transactions on ${new Date(dayData.date).toLocaleDateString()}`,
|
||||||
|
data: seriesData,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const options = {
|
const chartData = processData(transactionGraph);
|
||||||
chart: {
|
|
||||||
type: 'line',
|
let globalMax = null;
|
||||||
height: '100%',
|
if (chartData)
|
||||||
zoom: {
|
globalMax = chartData.reduce(
|
||||||
enabled: true
|
(max, data) => {
|
||||||
}
|
const localMax = Math.max(...data.series[0].data);
|
||||||
|
return localMax > max ? localMax : max;
|
||||||
},
|
},
|
||||||
xaxis: {
|
0
|
||||||
categories: uniqueDates,
|
);
|
||||||
labels: {
|
|
||||||
style: {
|
const formatDate = (dateString) => {
|
||||||
fontSize: '12px',
|
const date = new Date(dateString);
|
||||||
}
|
const monthNames = [
|
||||||
}
|
"Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"
|
||||||
},
|
];
|
||||||
colors: uniqueMaterials.map(() => getNextColor()), // Assign unique colors for each material
|
const month = monthNames[date.getMonth()];
|
||||||
legend: {
|
const day = date.getDate();
|
||||||
position: 'top',
|
return { month, day };
|
||||||
horizontalAlign: 'center',
|
|
||||||
itemMargin: {
|
|
||||||
horizontal: 10,
|
|
||||||
vertical: 0
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
colors: '#3498db', // Set legend text color
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
theme: 'light',
|
|
||||||
style: {
|
|
||||||
fontSize: '14px',
|
|
||||||
color: '#3498db'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', height: '20vh' }}>
|
<div className={styles.chartItemContainer}>
|
||||||
<ReactApexChart
|
{chartData &&
|
||||||
options={options}
|
chartData.map((data, index) => (
|
||||||
series={series}
|
<div
|
||||||
type="line"
|
key={index}
|
||||||
height="100%"
|
className={`${styles.chartItemWrapper} ${selectedIndex !== -1 && selectedIndex !== index
|
||||||
|
? styles.chartItemWrapperActive
|
||||||
|
: styles.chartItemWrapperInactive
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={styles.dateSelectorWrapper}>
|
||||||
|
{chartData.map((item, indexx) => {
|
||||||
|
const { month, day } = formatDate(item.date);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={indexx}
|
||||||
|
className={`${styles.dateSelector} ${index === indexx ? styles.dateSelectorActive : styles.dateSelectorInactive
|
||||||
|
}`}
|
||||||
|
onClick={() =>
|
||||||
|
selectedIndex !== index ? setSelectedIndex(index) : setSelectedIndex(-1)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{day}{" "}
|
||||||
|
{(indexx === 0 || formatDate(chartData[indexx - 1].date).month !== month) && month}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className={styles.chartWrapper}>
|
||||||
|
<Chart
|
||||||
|
options={{
|
||||||
|
chart: {
|
||||||
|
id: `chart-${index}`,
|
||||||
|
type: "area",
|
||||||
|
zoom: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: data.categories,
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: index === 0 ? "#000" : "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
max: globalMax,
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
colors: [colors[index % colors.length]],
|
||||||
|
}}
|
||||||
|
series={data.series}
|
||||||
|
type="area"
|
||||||
|
height={200}
|
||||||
|
width="100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SimpleLineChart;
|
export default BarChart;
|
||||||
|
|||||||
49
src/components/BarChart.module.css
Normal file
49
src/components/BarChart.module.css
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
.chartItemContainer{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.chartItemWrapper {
|
||||||
|
position: absolute;
|
||||||
|
transition: top 0.3s ease-in-out; /* Apply transition to chartItemWrapper */
|
||||||
|
|
||||||
|
left: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
.chartItemWrapperActive {
|
||||||
|
top: 200px; /* Move chartItemWrapper down when selected */
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartItemWrapperInactive {
|
||||||
|
top: 0; /* Reset to original position */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateSelectorWrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateSelector {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateSelectorActive {
|
||||||
|
color: black;
|
||||||
|
border-color: black;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateSelectorInactive {
|
||||||
|
color: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartWrapper {
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import styles from "./Transactions.module.css";
|
|||||||
import "./Switch.css";
|
import "./Switch.css";
|
||||||
|
|
||||||
import MultiSwitch from "react-multi-switch-toggle";
|
import MultiSwitch from "react-multi-switch-toggle";
|
||||||
|
import BarChart from '../components/BarChart';
|
||||||
|
|
||||||
const RoundedRectangle = ({
|
const RoundedRectangle = ({
|
||||||
onClick,
|
onClick,
|
||||||
@@ -104,7 +105,7 @@ const App = ({ cafeId,
|
|||||||
handleClose }) => {
|
handleClose }) => {
|
||||||
const [analytics, setAnalytics] = useState({});
|
const [analytics, setAnalytics] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [filter, setFilter] = useState("daily");
|
const [filter, setFilter] = useState("yesterday");
|
||||||
|
|
||||||
const fetchData = async (filter) => {
|
const fetchData = async (filter) => {
|
||||||
try {
|
try {
|
||||||
@@ -124,7 +125,7 @@ const App = ({ cafeId,
|
|||||||
fetchData(filter); // Fetch data when filter changes
|
fetchData(filter); // Fetch data when filter changes
|
||||||
}, [filter]);
|
}, [filter]);
|
||||||
|
|
||||||
const filteredItems = analytics.items || [];
|
const filteredItems = analytics.itemSales || [];
|
||||||
|
|
||||||
const colors = [
|
const colors = [
|
||||||
"#FF0000", // Red
|
"#FF0000", // Red
|
||||||
@@ -178,13 +179,13 @@ const App = ({ cafeId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onToggle(selectedItem) {
|
function onToggle(selectedItem) {
|
||||||
const filterMap = ["daily", "weekly", "monthly", "yearly"];
|
const filterMap = ["yesterday", "weekly", "monthly", "yearly"];
|
||||||
setFilter(filterMap[selectedItem]);
|
setFilter(filterMap[selectedItem]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterTexts = ["1", "7", "30", "365"];
|
const filterTexts = ["1", "7", "30", "365"];
|
||||||
const comparisonText =
|
const comparisonText =
|
||||||
filterTexts[["daily", "weekly", "monthly", "yearly"].indexOf(filter)];
|
filterTexts[["yesterday", "weekly", "monthly", "yearly"].indexOf(filter)];
|
||||||
// const formatDate = (isoDateString) => {
|
// const formatDate = (isoDateString) => {
|
||||||
// const date = new Date(isoDateString);
|
// const date = new Date(isoDateString);
|
||||||
// return date.toLocaleDateString("en-US", {
|
// return date.toLocaleDateString("en-US", {
|
||||||
@@ -213,7 +214,7 @@ const App = ({ cafeId,
|
|||||||
}}>
|
}}>
|
||||||
<MultiSwitch
|
<MultiSwitch
|
||||||
texts={["kemarin", "minggu ini", "bulan ini", "tahun ini"]}
|
texts={["kemarin", "minggu ini", "bulan ini", "tahun ini"]}
|
||||||
selectedSwitch={["daily", "weekly", "monthly", "yearly"].indexOf(
|
selectedSwitch={["yesterday", "weekly", "monthly", "yearly"].indexOf(
|
||||||
filter
|
filter
|
||||||
)}
|
)}
|
||||||
borderColor={'#b3b1b1'}
|
borderColor={'#b3b1b1'}
|
||||||
@@ -237,8 +238,8 @@ const App = ({ cafeId,
|
|||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
title="Pendapatan"
|
title="Pendapatan"
|
||||||
fontSize="12px"
|
fontSize="12px"
|
||||||
value={!loading && "Rp" + formatIncome(analytics?.totalIncome)}
|
value={!loading && "Rp" + formatIncome(analytics?.currentTotals?.income)}
|
||||||
percentage={roundToInteger(analytics.growthIncome)}
|
percentage={roundToInteger(analytics?.growth?.incomeGrowth)}
|
||||||
invert={false}
|
invert={false}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
@@ -246,28 +247,28 @@ const App = ({ cafeId,
|
|||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
title="Pengeluaran"
|
title="Pengeluaran"
|
||||||
fontSize="12px"
|
fontSize="12px"
|
||||||
value={!loading && "Rp" + formatIncome(analytics?.currentOutcome)}
|
value={!loading && "Rp" + formatIncome(analytics?.currentTotals?.outcome)}
|
||||||
percentage={roundToInteger(analytics.growthOutcome)}
|
percentage={roundToInteger(analytics?.growth?.outcomeGrowth)}
|
||||||
invert={true}
|
invert={true}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
title="Transaksi"
|
title="Transaksi"
|
||||||
value={analytics.totalTransactions}
|
value={analytics?.currentTotals?.transactions}
|
||||||
percentage={roundToInteger(analytics.growthTransactions)}
|
percentage={roundToInteger(analytics?.growth?.transactionGrowth)}
|
||||||
invert={false}
|
invert={false}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
{analytics?.items &&
|
{analytics?.itemSales &&
|
||||||
analytics?.items.length > 0 && (
|
analytics?.itemSales.length > 0 && (
|
||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
title={"Item favorit"}
|
title={"Item favorit"}
|
||||||
value={analytics?.items[0]?.name}
|
value={analytics?.itemSales[0]?.itemName}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(!analytics?.items ||
|
{(!analytics?.itemSales ||
|
||||||
analytics?.items.length === 0) && (
|
analytics?.itemSales.length === 0) && (
|
||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
title={"Item favorit"}
|
title={"Item favorit"}
|
||||||
value={"-"}
|
value={"-"}
|
||||||
@@ -314,12 +315,12 @@ const App = ({ cafeId,
|
|||||||
>
|
>
|
||||||
★
|
★
|
||||||
</div>
|
</div>
|
||||||
<h5 style={{ margin: 0, textAlign: "left" }}>{item.name}</h5>
|
<h5 style={{ margin: 0, textAlign: "left" }}>{item.itemName}</h5>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BarChart transactionGraph={analytics?.transactionGraph} colors={colors}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ItemLister_PaymentOption__YZlDL"><div style={{ marginTop: '20px' }} onClick={handleClose} class="ItemLister_Pay2Button__+MIxX">Kembali</div></div>
|
<div class="ItemLister_PaymentOption__YZlDL"><div style={{ marginTop: '20px' }} onClick={handleClose} class="ItemLister_Pay2Button__+MIxX">Kembali</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user