ok
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -21,6 +21,7 @@
|
|||||||
"react-bootstrap": "^2.10.4",
|
"react-bootstrap": "^2.10.4",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-loader-spinner": "^6.1.6",
|
"react-loader-spinner": "^6.1.6",
|
||||||
|
"react-multi-switch-toggle": "^1.0.12",
|
||||||
"react-qr-reader": "^3.0.0-beta-1",
|
"react-qr-reader": "^3.0.0-beta-1",
|
||||||
"react-router-dom": "^6.24.0",
|
"react-router-dom": "^6.24.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
@@ -16052,6 +16053,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-multi-switch-toggle": {
|
||||||
|
"version": "1.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-multi-switch-toggle/-/react-multi-switch-toggle-1.0.12.tgz",
|
||||||
|
"integrity": "sha512-SW/g2InMjQOZSUZvq8uPnblpWpHgL/uox/xGKEoRSPZNhyHbHJEb902W+R/oS5bu2udhc7OrWi8M+G7NHzMKAw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.3.0",
|
||||||
|
"react-dom": "^16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-qr-reader": {
|
"node_modules/react-qr-reader": {
|
||||||
"version": "3.0.0-beta-1",
|
"version": "3.0.0-beta-1",
|
||||||
"resolved": "https://registry.npmjs.org/react-qr-reader/-/react-qr-reader-3.0.0-beta-1.tgz",
|
"resolved": "https://registry.npmjs.org/react-qr-reader/-/react-qr-reader-3.0.0-beta-1.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"react-bootstrap": "^2.10.4",
|
"react-bootstrap": "^2.10.4",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-loader-spinner": "^6.1.6",
|
"react-loader-spinner": "^6.1.6",
|
||||||
|
"react-multi-switch-toggle": "^1.0.12",
|
||||||
"react-qr-reader": "^3.0.0-beta-1",
|
"react-qr-reader": "^3.0.0-beta-1",
|
||||||
"react-router-dom": "^6.24.0",
|
"react-router-dom": "^6.24.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
} from "../helpers/transactionHelpers.js";
|
} from "../helpers/transactionHelpers.js";
|
||||||
import CircularDiagram from "./CircularDiagram";
|
import CircularDiagram from "./CircularDiagram";
|
||||||
import styles from "./Transactions.module.css";
|
import styles from "./Transactions.module.css";
|
||||||
|
import "./Switch.css";
|
||||||
|
|
||||||
|
import MultiSwitch from "react-multi-switch-toggle";
|
||||||
|
|
||||||
const RoundedRectangle = ({
|
const RoundedRectangle = ({
|
||||||
onClick,
|
onClick,
|
||||||
@@ -14,7 +17,8 @@ const RoundedRectangle = ({
|
|||||||
title,
|
title,
|
||||||
value,
|
value,
|
||||||
percentage,
|
percentage,
|
||||||
fontSize = "20px",
|
fontSize = "15px",
|
||||||
|
loading = false,
|
||||||
}) => {
|
}) => {
|
||||||
const containerStyle = {
|
const containerStyle = {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -30,12 +34,15 @@ const RoundedRectangle = ({
|
|||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
fontFamily: "Arial, sans-serif",
|
fontFamily: "Arial, sans-serif",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
|
backgroundColor: loading ? "rgb(127 127 127)" : bgColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
const titleStyle = {
|
const titleStyle = {
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
marginBottom: "10px",
|
marginBottom: "10px",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
backgroundColor: loading ? "rgb(85 85 85)" : "inherit",
|
||||||
|
color: loading ? "transparent" : "inherit",
|
||||||
};
|
};
|
||||||
|
|
||||||
const valueAndPercentageContainerStyle = {
|
const valueAndPercentageContainerStyle = {
|
||||||
@@ -47,10 +54,15 @@ const RoundedRectangle = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const valueStyle = {
|
const valueStyle = {
|
||||||
fontSize: fontSize,
|
fontSize: loading ? "15px" : fontSize,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
flex: "1",
|
flex: "1",
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
|
color: loading ? "transparent" : "inherit",
|
||||||
|
backgroundColor: loading ? "rgb(85 85 85)" : "transparent",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis", // Add ellipsis for overflow text
|
||||||
|
whiteSpace: "nowrap", // Prevent text from wrapping
|
||||||
};
|
};
|
||||||
|
|
||||||
const percentageStyle = {
|
const percentageStyle = {
|
||||||
@@ -58,6 +70,7 @@ const RoundedRectangle = ({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
|
color: loading ? "black" : percentage > 0 ? "#007bff" : "red",
|
||||||
};
|
};
|
||||||
|
|
||||||
const arrowStyle = {
|
const arrowStyle = {
|
||||||
@@ -65,25 +78,18 @@ const RoundedRectangle = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div style={containerStyle} onClick={onClick}>
|
||||||
style={{
|
|
||||||
...containerStyle,
|
|
||||||
backgroundColor: bgColor,
|
|
||||||
}}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<div style={titleStyle}>{title}</div>
|
<div style={titleStyle}>{title}</div>
|
||||||
<div style={valueAndPercentageContainerStyle}>
|
<div style={valueAndPercentageContainerStyle}>
|
||||||
<div style={valueStyle}>{value}</div>
|
<div style={valueStyle}>{loading ? "Loading..." : value}</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
...percentageStyle,
|
...percentageStyle,
|
||||||
color: percentage > 0 ? "#007bff" : "red",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{percentage}
|
{loading ? "" : percentage}
|
||||||
{percentage != undefined && "%"}
|
{percentage != undefined && !loading && "%"}
|
||||||
{percentage != undefined && (
|
{percentage != undefined && !loading && (
|
||||||
<span style={arrowStyle}>
|
<span style={arrowStyle}>
|
||||||
{percentage > 0 ? "↗" : percentage === 0 ? "-" : "↘"}
|
{percentage > 0 ? "↗" : percentage === 0 ? "-" : "↘"}
|
||||||
</span>
|
</span>
|
||||||
@@ -98,7 +104,7 @@ const App = ({ cafeId }) => {
|
|||||||
const [favouriteItems, setFavouriteItems] = useState([]);
|
const [favouriteItems, setFavouriteItems] = useState([]);
|
||||||
const [analytics, setAnalytics] = useState({});
|
const [analytics, setAnalytics] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [filter, setFilter] = useState("monthly"); // Default filter is 'monthly'
|
const [filter, setFilter] = useState("daily"); // Default filter is 'monthly'
|
||||||
const [colors, setColors] = useState([]);
|
const [colors, setColors] = useState([]);
|
||||||
const [viewStock, setViewStock] = useState(false);
|
const [viewStock, setViewStock] = useState(false);
|
||||||
|
|
||||||
@@ -139,15 +145,6 @@ const App = ({ cafeId }) => {
|
|||||||
setColors(colors);
|
setColors(colors);
|
||||||
}, [favouriteItems, filter]);
|
}, [favouriteItems, filter]);
|
||||||
|
|
||||||
if (loading)
|
|
||||||
return (
|
|
||||||
<div className="Loader">
|
|
||||||
<div className="LoaderChild">
|
|
||||||
<ThreeDots />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const [sold, percentage] = analytics[filter] || [0, 0];
|
const [sold, percentage] = analytics[filter] || [0, 0];
|
||||||
const filteredItems = favouriteItems[filter] || [];
|
const filteredItems = favouriteItems[filter] || [];
|
||||||
|
|
||||||
@@ -173,56 +170,42 @@ const App = ({ cafeId }) => {
|
|||||||
return thousands.toFixed(1).replace(/\.0$/, "") + "k"; // One decimal place, remove trailing '.0'
|
return thousands.toFixed(1).replace(/\.0$/, "") + "k"; // One decimal place, remove trailing '.0'
|
||||||
} else {
|
} else {
|
||||||
// Less than a thousand
|
// Less than a thousand
|
||||||
return amount.toString();
|
if (amount != null) return amount.toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function roundToInteger(num) {
|
||||||
|
return Math.round(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggle(selectedItem) {
|
||||||
|
const filterMap = ["daily", "weekly", "monthly", "yearly"];
|
||||||
|
setFilter(filterMap[selectedItem]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterTexts = ["1 day", "7 days", "30 days", "365 days"];
|
||||||
|
const comparisonText =
|
||||||
|
filterTexts[["daily", "weekly", "monthly", "yearly"].indexOf(filter)];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.Transactions} style={{ backgroundColor: "#cfcfcf" }}>
|
<div className={styles.Transactions} style={{ backgroundColor: "#cfcfcf" }}>
|
||||||
<h2 className={styles["Transactions-title"]}>Reports</h2>
|
<h2 className={styles["Transactions-title"]}>Reports</h2>
|
||||||
<div style={{ textAlign: "center" }}>
|
<div style={{ textAlign: "center" }}>
|
||||||
<div>
|
<MultiSwitch
|
||||||
<label>
|
texts={["Yesterday", "Last 7", "Last 30", "Last 365"]}
|
||||||
<input
|
selectedSwitch={["daily", "weekly", "monthly", "yearly"].indexOf(
|
||||||
type="radio"
|
filter
|
||||||
name="filter"
|
)}
|
||||||
value="daily"
|
borderWidth={0}
|
||||||
checked={filter === "daily"}
|
bgColor={"rgb(229, 236, 246)"}
|
||||||
onChange={() => setFilter("daily")}
|
onToggleCallback={onToggle}
|
||||||
|
fontColor={"rgba(88, 55, 50, 1)"}
|
||||||
|
selectedFontColor={"#1e311b"}
|
||||||
|
selectedSwitchColor={"rgb(227, 245, 255)"}
|
||||||
|
eachSwitchWidth={70}
|
||||||
|
height={"25px"}
|
||||||
|
fontSize={"12px"}
|
||||||
/>
|
/>
|
||||||
Daily
|
|
||||||
</label>
|
|
||||||
<label style={{ marginLeft: "10px" }}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="filter"
|
|
||||||
value="weekly"
|
|
||||||
checked={filter === "weekly"}
|
|
||||||
onChange={() => setFilter("weekly")}
|
|
||||||
/>
|
|
||||||
Weekly
|
|
||||||
</label>
|
|
||||||
<label style={{ marginLeft: "10px" }}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="filter"
|
|
||||||
value="monthly"
|
|
||||||
checked={filter === "monthly"}
|
|
||||||
onChange={() => setFilter("monthly")}
|
|
||||||
/>
|
|
||||||
Monthly
|
|
||||||
</label>
|
|
||||||
<label style={{ marginLeft: "10px" }}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="filter"
|
|
||||||
value="yearly"
|
|
||||||
checked={filter === "yearly"}
|
|
||||||
onChange={() => setFilter("yearly")}
|
|
||||||
/>
|
|
||||||
Yearly
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -234,37 +217,55 @@ const App = ({ cafeId }) => {
|
|||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
bgColor="#E3F5FF"
|
bgColor="#E3F5FF"
|
||||||
title="Transactions"
|
title="Transactions"
|
||||||
value={sold}
|
value={analytics.transactionCount}
|
||||||
percentage={percentage}
|
percentage={roundToInteger(analytics.transactionGrowth)}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
{filteredItems[0]?.Item !== undefined && (
|
|
||||||
<RoundedRectangle
|
<RoundedRectangle
|
||||||
bgColor="#E5ECF6"
|
bgColor="#E5ECF6"
|
||||||
title={filteredItems[0]?.Item.name}
|
|
||||||
value={filteredItems[0]?.sold}
|
|
||||||
percentage={filteredItems[0]?.percentageByPreviousPeriod}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{filteredItems[0]?.Item === undefined && (
|
|
||||||
<RoundedRectangle
|
|
||||||
bgColor="#8989897a"
|
|
||||||
title={"No fav item"}
|
|
||||||
value={"-"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<RoundedRectangle
|
|
||||||
bgColor={viewStock ? "#979797" : "#E5ECF6"}
|
|
||||||
title="Stok"
|
|
||||||
value={viewStock ? " ˅" : " ˄"}
|
|
||||||
onClick={() => setViewStock(!viewStock)}
|
|
||||||
/>
|
|
||||||
<RoundedRectangle
|
|
||||||
bgColor="#E3F5FF"
|
|
||||||
title="Income"
|
title="Income"
|
||||||
fontSize="12px"
|
fontSize="12px"
|
||||||
value={"Rp" + formatIncome(512520000)}
|
value={!loading && "Rp" + formatIncome(analytics?.totalIncome)}
|
||||||
percentage={percentage}
|
percentage={roundToInteger(analytics.incomeGrowth)}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{analytics?.currentFavoriteItem !== null && (
|
||||||
|
<RoundedRectangle
|
||||||
|
bgColor="#E5ECF6"
|
||||||
|
title={"Fav item"}
|
||||||
|
value={analytics?.currentFavoriteItem?.name}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{analytics?.currentFavoriteItem === null && (
|
||||||
|
<RoundedRectangle
|
||||||
|
bgColor="#E5ECF6"
|
||||||
|
title={"No fav item"}
|
||||||
|
value={"-"}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{analytics?.previousFavoriteItem !== null && (
|
||||||
|
<RoundedRectangle
|
||||||
|
bgColor="#E3F5FF"
|
||||||
|
title={"Fav before"}
|
||||||
|
value={analytics?.previousFavoriteItem?.name}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{analytics?.previousFavoriteItem === null && (
|
||||||
|
<RoundedRectangle
|
||||||
|
bgColor="#E3F5FF"
|
||||||
|
title={"No fav item"}
|
||||||
|
value={"-"}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h5 style={{ margin: "10px" }}>
|
||||||
|
ⓘ compared to the previous {comparisonText}
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -279,16 +280,14 @@ const App = ({ cafeId }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, marginLeft: "20px" }}>
|
<div style={{ flex: 1, marginLeft: "20px" }}>
|
||||||
{filteredItems.map((item, index) => (
|
{filteredItems.map((item, index) => (
|
||||||
<h6
|
<RoundedRectangle
|
||||||
key={item.itemId}
|
key={index}
|
||||||
style={{
|
bgColor={colors[index] || "#cccccc"}
|
||||||
textAlign: "left",
|
title={item.name}
|
||||||
color: colors[index],
|
value={item.sold}
|
||||||
margin: "10px 0",
|
percentage={Math.round((item.sold / totalSold) * 100) + "%"}
|
||||||
}}
|
loading={loading}
|
||||||
>
|
/>
|
||||||
{item.Item.name}: {item.sold}
|
|
||||||
</h6>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
39
src/pages/Switch.css
Normal file
39
src/pages/Switch.css
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.filter-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item {
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease, color 0.3s ease;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #007bff;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 123, 255, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item.active::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user