This commit is contained in:
client perkafean
2024-08-26 06:34:56 +00:00
parent 696ac38e33
commit 3a2e388a33
16 changed files with 1162 additions and 43 deletions

View File

@@ -0,0 +1,62 @@
import React from "react";
const CircularDiagram = ({ segments }) => {
const radius = 100; // Radius of the circle
const strokeWidth = 30; // Width of each portion
const circumference = 2 * Math.PI * (radius - strokeWidth / 2);
// Calculate total value to be displayed
const totalSold = segments.reduce((sum, segment) => sum + segment.value, 0);
let startOffset = 0; // Initial offset for each segment
const svgStyles = {
display: "block",
margin: "0 auto",
};
return (
<svg
width={radius * 2}
height={radius * 2}
viewBox={`0 0 ${radius * 2} ${radius * 2}`}
style={svgStyles}
>
<circle
cx={radius}
cy={radius}
r={radius - strokeWidth / 2}
stroke="#eee"
strokeWidth={strokeWidth}
fill="none"
/>
{segments.map((segment, index) => {
const { value, color } = segment;
const segmentLength = (circumference * value) / totalSold;
const strokeDashoffset = circumference - startOffset;
startOffset += segmentLength;
return (
<circle
key={index}
cx={radius}
cy={radius}
r={radius - strokeWidth / 2}
stroke={color}
strokeWidth={strokeWidth}
fill="none"
strokeDasharray={`${segmentLength} ${
circumference - segmentLength
}`}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round" // Rounds the edges of each segment
transform={`rotate(-90 ${radius} ${radius})`}
/>
);
})}
</svg>
);
};
export default CircularDiagram;

View File

@@ -113,7 +113,7 @@ const Dashboard = ({ user, setModal }) => {
className={styles.rectangle}
onClick={() => setIsCreating(true)}
>
Create Cafe
+
</div>
)}
</div>

View File

@@ -23,6 +23,7 @@ const MaterialList = ({ cafeId }) => {
const [selectedMaterialId, setSelectedMaterialId] = useState(null);
const [currentQuantity, setCurrentQuantity] = useState(0);
const [quantityChange, setQuantityChange] = useState(0);
const [sortOrder, setSortOrder] = useState("desc");
useEffect(() => {
const fetchMaterials = async () => {
@@ -63,7 +64,7 @@ const MaterialList = ({ cafeId }) => {
if (materialMutations.length > 0) {
const latestMutation = materialMutations.reduce(
(latest, current) =>
new Date(current.timestamp) > new Date(latest.timestamp)
new Date(current.createdAt) > new Date(latest.createdAt)
? current
: latest,
materialMutations[0]
@@ -75,10 +76,24 @@ const MaterialList = ({ cafeId }) => {
}
}, [selectedMaterialId, mutations]);
const handleSortChange = (e) => {
setSortOrder(e.target.value);
};
const filteredMutations = selectedMaterialId
? mutations.filter((mutation) => mutation.materialId === selectedMaterialId)
: [];
const sortedMutations = filteredMutations
.filter((mutation) => mutation.materialId === selectedMaterialId)
.sort((a, b) => {
if (sortOrder === "asc") {
return new Date(a.createdAt) - new Date(b.createdAt);
} else {
return new Date(b.createdAt) - new Date(a.createdAt);
}
});
const handleCreateMaterial = async (e) => {
e.preventDefault();
setLoading(true);
@@ -166,11 +181,10 @@ const MaterialList = ({ cafeId }) => {
try {
const newStock = currentQuantity + quantityChange;
const formData = new FormData();
formData.append("materialId", selectedMaterialId);
formData.append("newStock", newStock);
formData.append("reason", "Stock update");
await createMaterialMutation(cafeId, formData);
await createMaterialMutation(selectedMaterialId, formData);
setQuantityChange(0);
const updatedMutations = await getMaterialMutations(cafeId);
setMutations(updatedMutations);
@@ -188,7 +202,11 @@ const MaterialList = ({ cafeId }) => {
const currentMaterial = materials.find(
(material) => material.materialId === selectedMaterialId
);
const formatDate = (timestamp) => {
const date = new Date(timestamp);
return date.toLocaleString();
};
return (
<div style={styles.container}>
<h1 style={styles.heading}>Materials List</h1>
@@ -299,7 +317,7 @@ const MaterialList = ({ cafeId }) => {
-
</button>
<button style={styles.quantityDisplay}>
{currentQuantity + quantityChange}
{currentQuantity + quantityChange}
</button>
<button
onClick={() => handleQuantityChange(1)}
@@ -346,15 +364,27 @@ const MaterialList = ({ cafeId }) => {
</div>
)}
<div style={styles.sortContainer}>
<label htmlFor="sortOrder">Sort by : </label>
<select
id="sortOrder"
value={sortOrder}
onChange={handleSortChange}
style={styles.sortSelect}
>
<option value="desc">latest</option>
<option value="asc">oldest</option>
</select>
</div>
{selectedMaterialId && !loading && (
<div style={styles.mutationContainer}>
{filteredMutations.length > 0 ? (
filteredMutations.map((mutation) => (
{sortedMutations.length > 0 ? (
sortedMutations.map((mutation) => (
<div key={mutation.id} style={styles.mutationCard}>
<h4 style={styles.mutationTitle}>Mutation ID: {mutation.id}</h4>
<h4 style={styles.mutationTitle}>{formatDate(mutation.createdAt)}</h4>
<p>Details: {mutation.reason}</p>
<p>dari {mutation.oldStock}</p>
<p>ke {mutation.newStock}</p>
<p>stok {mutation.newStock}</p>
</div>
))
) : (

259
src/pages/Reports.js Normal file
View File

@@ -0,0 +1,259 @@
import React, { useEffect, useState } from "react";
import { ThreeDots } from "react-loader-spinner";
import { getFavourite, getAnalytics } from "../helpers/transactionHelpers.js";
import CircularDiagram from "./CircularDiagram";
import styles from "./Transactions.module.css";
const RoundedRectangle = ({ bgColor, title, value, percentage }) => {
const containerStyle = {
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
width: "calc(100% / 2 - 10px)",
maxWidth: "250px",
height: "auto",
borderRadius: "15px",
padding: "20px",
margin: "5px",
textAlign: "left",
fontFamily: "Arial, sans-serif",
boxSizing: "border-box",
};
const titleStyle = {
fontWeight: "bold",
marginBottom: "10px",
width: "100%",
};
const valueAndPercentageContainerStyle = {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
};
const valueStyle = {
fontSize: "20px",
fontWeight: "bold",
flex: "1",
textAlign: "left",
};
const percentageStyle = {
fontSize: "16px",
display: "flex",
alignItems: "center",
textAlign: "right",
};
const arrowStyle = {
marginLeft: "5px",
};
return (
<div
style={{
...containerStyle,
backgroundColor: bgColor,
}}
>
<div style={titleStyle}>{title}</div>
<div style={valueAndPercentageContainerStyle}>
<div style={valueStyle}>{value}</div>
<div
style={{
...percentageStyle,
color: percentage > 0 ? "#007bff" : "red",
}}
>
{percentage}%
<span style={arrowStyle}>{percentage > 0 ? "↗" : "↘"}</span>
</div>
</div>
</div>
);
};
const App = ({ cafeId }) => {
const [favouriteItems, setFavouriteItems] = useState([]);
const [analytics, setAnalytics] = useState({});
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState("monthly"); // Default filter is 'monthly'
const [colors, setColors] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const items = await getFavourite(cafeId);
const analyticsData = await getAnalytics(cafeId);
if (items) setFavouriteItems(items);
if (analyticsData) setAnalytics(analyticsData);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, [cafeId]);
useEffect(() => {
const getRandomColor = () => {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
const r = parseInt(color.substring(1, 3), 16);
const g = parseInt(color.substring(3, 5), 16);
const b = parseInt(color.substring(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, 1)`;
};
const colors = favouriteItems[filter]?.map(() => getRandomColor()) || [];
setColors(colors);
}, [favouriteItems, filter]);
if (loading)
return (
<div className="Loader">
<div className="LoaderChild">
<ThreeDots />
</div>
</div>
);
const [sold, percentage] = analytics[filter] || [0, 0];
const filteredItems = favouriteItems[filter] || [];
const totalSold = filteredItems.reduce((sum, item) => sum + item.sold, 0);
const segments = filteredItems.map((item, index) => ({
value: item.sold,
color: colors[index] || "#cccccc",
}));
return (
<div className={styles.Transactions} style={{ backgroundColor: "#cfcfcf" }}>
<h2 className={styles["Transactions-title"]}>Reports</h2>
<div style={{ marginTop: "30px", textAlign: "center" }}>
<div>
<label>
<input
type="radio"
name="filter"
value="daily"
checked={filter === "daily"}
onChange={() => setFilter("daily")}
/>
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
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
marginTop: "20px",
padding: "20px",
}}
>
<RoundedRectangle
bgColor="#E3F5FF"
title="Transactions"
value={sold}
percentage={percentage}
/>
{filteredItems[0]?.Item != undefined && (
<RoundedRectangle
bgColor="#E5ECF6"
title={filteredItems[0]?.Item.name}
value={filteredItems[0]?.sold}
percentage={filteredItems[0]?.percentageByPreviousPeriod}
/>
)}
{filteredItems[0]?.Item == undefined && (
<RoundedRectangle bgColor="#E5ECF6" value={"No item"} />
)}
<RoundedRectangle
bgColor="#E5ECF6"
title="Transactions"
value={sold}
percentage={percentage}
/>
<RoundedRectangle
bgColor="#E3F5FF"
title="Transactions"
value={sold}
percentage={percentage}
/>
</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "20px",
}}
>
<div style={{ flex: 1 }}>
<CircularDiagram segments={segments} />
</div>
<div style={{ flex: 1, marginLeft: "20px" }}>
{filteredItems.map((item, index) => (
<h6
key={item.itemId}
style={{
textAlign: "left",
color: colors[index],
margin: "5px 0",
}}
>
{item.Item.name}: {item.sold}
</h6>
))}
</div>
</div>
</div>
</div>
);
};
export default App;

View File

@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
import { QrReader } from "react-qr-reader"; // Import QrReader as named import
import styles from "./GuestSideLogin.module.css"; // Import module CSS file for styles
import { getLocalStorage } from "../helpers/localStorageHelpers";
import { getTableByCode } from "../helpers/tableHelper";
const GuestSideLogin = ({ socket }) => {
const navigate = useNavigate();
@@ -15,9 +15,8 @@ const GuestSideLogin = ({ socket }) => {
navigate("/" + shopId);
});
const setLoginGuestSide = () => {
const token = getLocalStorage("auth");
socket.emit("read_qrCode", { qrCode, token });
const setLoginGuestSide = async () => {
window.location.href = qrCode;
};
// Function to handle QR code scan
@@ -39,7 +38,7 @@ const GuestSideLogin = ({ socket }) => {
};
useEffect(() => {
if (qrCode.length === 11) {
if (qrCode.length === 40) {
const timer = setTimeout(() => {
setLoginGuestSide();
}, 1000); // Delay of 1 second (1000 milliseconds)