This commit is contained in:
zadit
2025-01-01 17:32:58 +07:00
parent e49cc3812a
commit 3a899e197a
3 changed files with 202 additions and 129 deletions

View File

@@ -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;

View 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;
}

View File

@@ -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>