ok banget

This commit is contained in:
zadit
2024-11-21 17:26:05 +07:00
parent 9c700c4729
commit 89b12737b7
11 changed files with 1345 additions and 513 deletions

120
package-lock.json generated
View File

@@ -27,6 +27,7 @@
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-switch": "^7.0.0", "react-switch": "^7.0.0",
"react-youtube": "^10.1.0", "react-youtube": "^10.1.0",
"recharts": "^2.13.3",
"smooth-scroll-into-view-if-needed": "^2.0.2", "smooth-scroll-into-view-if-needed": "^2.0.2",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.7.5",
"styled-components": "^6.1.11", "styled-components": "^6.1.11",
@@ -4648,6 +4649,11 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
},
"node_modules/@types/d3-color": { "node_modules/@types/d3-color": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
@@ -4658,6 +4664,11 @@
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="
}, },
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
},
"node_modules/@types/d3-interpolate": { "node_modules/@types/d3-interpolate": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
@@ -4692,6 +4703,11 @@
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
}, },
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
"node_modules/@types/eslint": { "node_modules/@types/eslint": {
"version": "8.56.10", "version": "8.56.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
@@ -7534,6 +7550,14 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": { "node_modules/d3-format": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
@@ -7609,6 +7633,14 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
},
"node_modules/damerau-levenshtein": { "node_modules/damerau-levenshtein": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -7696,6 +7728,11 @@
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
}, },
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/dedent": { "node_modules/dedent": {
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
@@ -9201,6 +9238,14 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
}, },
"node_modules/fast-equals": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -16199,6 +16244,20 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/react-smooth": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
"integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==",
"dependencies": {
"fast-equals": "^5.0.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-switch": { "node_modules/react-switch": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/react-switch/-/react-switch-7.0.0.tgz", "resolved": "https://registry.npmjs.org/react-switch/-/react-switch-7.0.0.tgz",
@@ -16274,6 +16333,41 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/recharts": {
"version": "2.13.3",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.3.tgz",
"integrity": "sha512-YDZ9dOfK9t3ycwxgKbrnDlRC4BHdjlY73fet3a0C1+qGMjXVZe6+VXmpOIIhzkje5MMEL8AN4hLIe4AMskBzlA==",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
"react-is": "^18.3.1",
"react-smooth": "^4.0.0",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/recharts/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"node_modules/recursive-readdir": { "node_modules/recursive-readdir": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
@@ -18136,6 +18230,11 @@
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
}, },
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
},
"node_modules/tmpl": { "node_modules/tmpl": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -18640,6 +18739,27 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/w3c-hr-time": { "node_modules/w3c-hr-time": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",

View File

@@ -22,6 +22,7 @@
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-switch": "^7.0.0", "react-switch": "^7.0.0",
"react-youtube": "^10.1.0", "react-youtube": "^10.1.0",
"recharts": "^2.13.3",
"smooth-scroll-into-view-if-needed": "^2.0.2", "smooth-scroll-into-view-if-needed": "^2.0.2",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.7.5",
"styled-components": "^6.1.11", "styled-components": "^6.1.11",

178
src/components/BarChart.js Normal file
View File

@@ -0,0 +1,178 @@
import React, { useRef, useEffect } from 'react';
import { BarChart, Bar, XAxis, Tooltip, Legend, CartesianGrid, ResponsiveContainer, LabelList } from 'recharts';
// Colors palette
const colors = [
// **Analogous Colors** (near greens, yellows)
"#D0E14F", // Light green-yellow
"#B2B83D", // Olive yellow
"#A9C96E", // Muted olive green (your bg color itself)
"#A8B64E", // Olive green with yellow undertones
// **Complementary Colors** (contrast with green)
"#FF6347", // Tomato red
"#FF4500", // Orange red
"#FF8C00", // Dark orange
"#FF7F50", // Coral
// **Triadic Colors** (balanced vibrant combination)
"#1E90FF", // Dodger blue
"#FF00FF", // Magenta
"#32CD32", // Lime green
// **Tetradic Colors** (4-color palette: 2 complementary pairs)
"#00CED1", // Dark turquoise
"#FFD700", // Gold
"#FF1493", // Deep pink
"#8A2BE2", // Blue violet
// **Neutral Tones** (earthy, muted tones to balance the palette)
"#FFDAB9", // Peach
"#F0E68C", // Khaki
"#8B4513", // Saddle brown
"#4B0082", // Indigo
"#C71585", // Medium violet red
// **Pastels for softer contrast**
"#FFB6C1", // Light pink
"#E6E6FA", // Lavender
"#98FB98", // Pale green
"#F0FFF0", // Honeydew
"#D3D3D3", // Light grey
];
const SimpleBarChart = ({ Data }) => {
const lastMonthRef = useRef('');
const lastYearRef = useRef('');
const usedColors = useRef([]); // Keep track of used colors
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();
}
// Assuming `acc` is an array of entries and `date` is the input date you're checking
const existingEntry = acc.find(d => isSameDay(d.date, date));
console.log(existingEntry)
if (existingEntry) {
// If it exists, sum the stockDifference (priceAtp * stockDifference)
existingEntry[name] = (existingEntry[name] || 0) + (priceAtp * stockDifference);
} else {
// If it doesn't exist yet, add a new entry
acc.push({
date,
materialId,
name,
[name]: priceAtp * stockDifference
});
}
return acc;
}, []) : []);
// Sort the data by date (ascending) to easily find the oldest date
const sortedData = [...transformedData].sort((a, b) => new Date(a.date) - new Date(b.date));
// The first date in the sorted array will be the oldest
const oldestDate = sortedData[0]?.date;
// Get the unique materials (names) from the data
const uniqueMaterials = Array.from(new Set(Data?.map(item => item.name) || []));
// Function to format date and handle month and year changes
const formatDate = (date) => {
const formattedDate = new Date(date);
const day = formattedDate.getDate(); // Get the day of the month
const month = formattedDate.toLocaleString('en-US', { month: 'short' }); // Get the abbreviated month (e.g., "Nov")
const year = formattedDate.getFullYear().toString().slice(2); // Get the last two digits of the year (e.g., "24")
// Extract the month and year from the date
const currentMonth = formattedDate.toLocaleDateString('en-US', { month: 'short' });
const currentYear = formattedDate.getFullYear();
// Check if it's the oldest date (first entry)
if (date === oldestDate) {
lastMonthRef.current = currentMonth; // Update lastMonthRef for the first entry
lastYearRef.current = currentYear; // Update lastYearRef for the first entry
return `${day} ${month} ${year}`; // Format as "day month year" (e.g., "4 Nov 24")
}
// If the year changes, show the full date with year
if (currentYear !== lastYearRef.current) {
lastYearRef.current = currentYear; // Update year reference
return `${day} ${month} ${year}`; // Show full date: day month year
}
// If the month changes, show the full date with month and year
if (currentMonth !== lastMonthRef.current) {
lastMonthRef.current = currentMonth; // Update month reference
return `${day} ${month} ${year}`; // Show full date: day month year
}
// Only show the day if the month hasn't changed
return `${day}`; // Show just the day if the month remains the same
};
// Function to get the next available color from the palette, starting from a random index
const getNextColor = () => {
// Randomly pick a starting index from the color palette
const randomIndex = Math.floor(Math.random() * colors.length);
// Find the first unused color from the random starting point
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); // Mark color as used
break;
}
}
return color || '#000000'; // Fallback color if no colors are available
};
// Extract unique dates for the XAxis
const uniqueDates = Array.from(new Set(transformedData.map(item => item.date)));
return (
<div style={{ width: '100%', height: '20vh' }}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={transformedData}>
{/* Format the XAxis ticks to show only the month when it changes */}
<XAxis
dataKey="date"
tickFormatter={formatDate} // Custom formatting function
interval={0} // Set interval to 0 to display all dates in data
ticks={uniqueDates} // Only use unique dates for the XAxis
/>
<Tooltip />
<Legend />
<CartesianGrid strokeDasharray="3 3" />
{/* Dynamically create bars and labels for each unique material */}
{uniqueMaterials.map((material) => (
<React.Fragment key={material}>
<Bar dataKey={material} fill={getNextColor()}>
</Bar>
</React.Fragment>
))}
</BarChart>
</ResponsiveContainer>
</div>
);
};
export default SimpleBarChart;

View File

@@ -311,7 +311,7 @@ const Header = ({
</Title> </Title>
<div style={{ visibility: showProfile ? "visible" : "hidden" }}> <div style={{ visibility: showProfile ? "visible" : "hidden" }}>
<ProfileImage <ProfileImage
src={user.username == undefined? shopImage : "https://static-00.iconduck.com/assets.00/profile-major-icon-1024x1024-9rtgyx30.png"} src={user.username == undefined? shopImage : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
alt="Profile" alt="Profile"
onClick={user.username !== undefined?handleImageClick: null} onClick={user.username !== undefined?handleImageClick: null}
animate={showRectangle && animate} animate={showRectangle && animate}

View File

@@ -392,7 +392,7 @@ export const getFavourite = async (cafeId) => {
export const getReports = async (cafeId, filter) => { export const getReports = async (cafeId, filter) => {
const response = await fetch( const response = await fetch(
API_BASE_URL + "/transaction/reports/" + cafeId + "?type=" + filter, API_BASE_URL + "/transaction/get-reports/" + cafeId + "?type=" + filter,
{ {
method: "POST", method: "POST",
headers: { headers: {

View File

@@ -171,7 +171,7 @@ export const updateUser = async (formData) => {
}; };
//for super //for super
export const getAllCafeOwner = async (formData) => { export const getAnalytics = async (formData) => {
const token = getLocalStorage("auth"); const token = getLocalStorage("auth");
if (token) { if (token) {
try { try {

View File

@@ -1,17 +1,17 @@
import React from "react"; import React from "react";
const CircularDiagram = ({ segments }) => { const CircularDiagram = ({ segments }) => {
const radius = 100; // Radius of the circle const radius = 70; // Radius of the circle
const strokeWidth = 30; // Width of each portion const strokeWidth = 20; // Width of each portion
const circumference = 2 * Math.PI * (radius - strokeWidth / 2); const circumference = 2 * Math.PI * (radius - strokeWidth / 2);
let startOffset = 0; // Initial offset for each segment let startOffset = -63; // Initial offset for each segment
const svgStyles = { const svgStyles = {
display: "block", display: "block",
margin: "0 auto", margin: "0 auto",
}; };
console.log(segments)
return ( return (
<svg <svg
width={radius * 2} width={radius * 2}
@@ -29,7 +29,10 @@ const CircularDiagram = ({ segments }) => {
/> />
{segments.map((segment, index) => { {segments.map((segment, index) => {
const { percentage, color } = segment; const { percentage, color } = segment;
const segmentLength = (circumference * percentage) / 100; console.log(percentage)
let p = percentage;
if(p == 'Infinity' || isNaN(p)) p = 0;
const segmentLength = (circumference * p) / 100;
const strokeDashoffset = circumference - startOffset; const strokeDashoffset = circumference - startOffset;
startOffset += segmentLength; startOffset += segmentLength;

View File

@@ -4,7 +4,7 @@ import Header from "../components/Header";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import AccountUpdateModal from "../components/AccountUpdateModal"; import AccountUpdateModal from "../components/AccountUpdateModal";
import { removeLocalStorage } from "../helpers/localStorageHelpers"; import { removeLocalStorage } from "../helpers/localStorageHelpers";
import { getAllCafeOwner, createCafeOwner } from "../helpers/userHelpers"; import { getAnalytics, createCafeOwner } from "../helpers/userHelpers";
import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers"; import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers";
import { ThreeDots } from "react-loader-spinner"; import { ThreeDots } from "react-loader-spinner";
@@ -19,11 +19,12 @@ const Dashboard = ({ user, setModal }) => {
const [newItem, setNewItem] = useState({ name: "", type: "" }); const [newItem, setNewItem] = useState({ name: "", type: "" });
useEffect(() => { useEffect(() => {
if (user && user.roleId === 0) { if (user && user.roleId < 2) {
setLoading(true); setLoading(true);
getAllCafeOwner() getAnalytics()
.then((data) => { .then((data) => {
setItems(data); setItems(data);
console.log(data)
setLoading(false); setLoading(false);
}) })
.catch((error) => { .catch((error) => {
@@ -31,18 +32,6 @@ const Dashboard = ({ user, setModal }) => {
setLoading(false); setLoading(false);
}); });
} }
if (user && user.roleId === 1) {
setLoading(true);
getOwnedCafes(user.userId)
.then((data) => {
setItems(data);
setLoading(false);
})
.catch((error) => {
console.error("Error fetching owned cafes:", error);
setLoading(false);
});
}
}, [user]); }, [user]);
const handleModalClose = () => { const handleModalClose = () => {

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import styles from './LinktreePage.module.css'; import styles from './LinktreePage.module.css';
import { loginUser, getAllCafeOwner, createCafeOwner } from "../helpers/userHelpers"; import { loginUser, getAnalytics, createCafeOwner } from "../helpers/userHelpers";
import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers"; import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers";
import { getMyTransactions } from "../helpers/transactionHelpers"; import { getMyTransactions } from "../helpers/transactionHelpers";
import { unsubscribeUser } from "../helpers/subscribeHelpers.js"; import { unsubscribeUser } from "../helpers/subscribeHelpers.js";
@@ -9,6 +9,8 @@ import { getLocalStorage, removeLocalStorage } from "../helpers/localStorageHelp
import { ThreeDots } from "react-loader-spinner"; import { ThreeDots } from "react-loader-spinner";
import Header from '../components/Header'; import Header from '../components/Header';
import CircularDiagram from "./CircularDiagram"; import CircularDiagram from "./CircularDiagram";
import API_BASE_URL from '../config';
import BarChart from '../components/BarChart';
const LinktreePage = ({ user, setModal }) => { const LinktreePage = ({ user, setModal }) => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -82,10 +84,10 @@ const LinktreePage = ({ user, setModal }) => {
setLoading(true); setLoading(true);
switch (user.roleId) { switch (user.roleId) {
case 0: case 0:
getAllCafeOwner().then(setItems).catch(console.error).finally(() => setLoading(false)); getAnalytics().then(setItems).catch(console.error).finally(() => setLoading(false));
break; break;
case 1: case 1:
getOwnedCafes(user.userId).then(setItems).catch(console.error).finally(() => setLoading(false)); getAnalytics().then(setItems).catch(console.error).finally(() => setLoading(false));
break; break;
case 3: case 3:
handleMyTransactions(); handleMyTransactions();
@@ -135,60 +137,61 @@ const LinktreePage = ({ user, setModal }) => {
} }
}; };
const colors = [ const colors = [
"#FF0000", // Red // Complementary (for contrast with olive green)
"#FF6F00", // Dark Orange "#FF6347", // Tomato red (complementary to olive green)
"#FFD700", // Gold "#FF4500", // Orange red (complementary to olive green)
"#32CD32", // Lime Green
"#00CED1", // Dark Turquoise // Analogous to olive green
"#1E90FF", // Dodger Blue "#D0E14F", // Light green-yellow
"#8A2BE2", // BlueViolet "#A9C96E", // Muted olive green (your bg color itself)
"#FF00FF", // Magenta "#A5B24F", // Earthy olive green
"#FF1493", // Deep Pink
"#FF4500", // OrangeRed
"#FFDAB9", // Peach Puff // Triadic (balanced and vibrant palette)
"#4B0082", // Indigo "#FF00FF", // Magenta (triadic color)
"#00FF7F", // Spring Green "#1E90FF", // Dodger blue (triadic color)
"#C71585", // Medium Violet Red "#32CD32", // Lime green (triadic color)
"#F0E68C", // Khaki
"#FF6347", // Tomato // Neutral tones
"#006400", // Dark Green "#FFDAB9", // Peach (light neutral tone)
"#8B4513", // SaddleBrown "#4B0082", // Indigo (dark neutral tone)
"#00BFFF", // Deep Sky Blue "#8B4513", // Saddle brown (earthy neutral)
"#FF69B4", // Hot Pink
]; ];
const selectedTenant = items.tenants?.find(tenant => tenant.userId === selectedItemId); const selectedItems = items.items?.find(item => (item.userId || item.cafeId) === selectedItemId);
// If the selected tenant is found, extract the cafes // If the selected tenant is found, extract the cafes
const selectedTenantCafes = selectedTenant?.cafes || []; const selectedSubItems = selectedItems?.subItems || [];
// 1. Optionally combine all report items from cafes of the selected tenant // 1. Optionally combine all report items from cafes of the selected tenant
const allSelectedTenantCafeItems = selectedTenantCafes.flatMap(cafe => cafe.report?.items || []); const allSelectedSubItems = selectedSubItems.flatMap(cafe => cafe.report?.items || selectedItems.report.items || []);
// 2. Retrieve the specific cafe's report items if needed // 2. Retrieve the specific cafe's report items if needed
const filteredItems = selectedTenantCafes.find(cafe => cafe.cafeId === selectedSubItemId) || { report: { items: [] } }; const filteredItems = selectedSubItems.find(cafe => cafe.cafeId == selectedSubItemId) || { report: { items: [] } };
// 3. Decide whether to use combined items or individual cafe items // 3. Decide whether to use combined items or individual cafe items
const segments = (selectedItemId != 0 && selectedItemId != -1 && selectedSubItemId == 0 ? allSelectedTenantCafeItems : filteredItems.report.items || []).map((item, index) => ({ const segments = (selectedItemId != 0 && selectedItemId != -1 && selectedSubItemId == 0 ? allSelectedSubItems : selectedItemId != 0 && selectedItemId != -1 ? filteredItems.report?.items || [] : items?.items || []).map((item, index) => ({
percentage: item.percentage, percentage: item.percentage || (items.totalIncome / item.totalIncome || items.totalIncome / item.report.totalIncome) * 100,
value: item.username || item.name,
color: (colors && colors[index]) || "#cccccc", // Safe check for colors array color: (colors && colors[index]) || "#cccccc", // Safe check for colors array
})) || []; // Ensure segments is an empty array if no items are available })) || []; // Ensure segments is an empty array if no items are availabled
// Function to combine items of all cafes for the selected tenant // Function to combine items of all cafes for the selected tenant
function combineSelectedTenantCafeReports(selectedTenant) { console.log(selectedItems)
return selectedTenant.cafes.flatMap(cafe => cafe.report?.items || []); function combineSelectedTenantCafeReports(selectedItems) {
return selectedItems.cafes.flatMap(cafe => cafe.report?.items || []);
} }
return ( return (
<> <>
{user && user.roleId < 2 ? ( {user && user.roleId < 2 ? (
<>
<div className={styles.nonCenteredLinktreePage}> <div className={styles.nonCenteredLinktreePage}>
<div className={styles.dashboard}> <div className={styles.dashboard}>
<div className={styles.header}> <div className={styles.header}>
<Header <Header
HeaderText={"selamat siang " + user.username} HeaderText={"selamat sore " + user.username}
isEdit={() => setIsModalOpen(true)} isEdit={() => setIsModalOpen(true)}
isLogout={handleLogout} isLogout={handleLogout}
user={user} user={user}
@@ -199,15 +202,489 @@ const LinktreePage = ({ user, setModal }) => {
</div> </div>
<div className={styles.headerCardWrapper}> <div className={styles.headerCardWrapper}>
<div className={styles.headerCard}> <div className={styles.headerCard}>
<h1>Total pemasukan {items?.totalIncomeFromAllTenant}</h1> {user.roleId == 0 ? (
selectedItemId == 0 || selectedItemId == -1 ? (
<div className={styles.cardBody}>
<div className={styles.cardItem}>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>Total pemasukan</p>
<p>{items?.totalIncome}</p>
</div>
<div className={styles.cardItem}>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>Total keuntungan</p>
<p>{items?.totalIncome * 0.02}</p>
</div>
</div>
) : (
<div className={styles.cardBody}>
<div className={styles.cardItem} >
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>{selectedItems?.totalIncome}</p>
<p>pemasukan</p>
</div>
<div className={styles.cardItem}>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>{selectedItems?.totalOutcome}</p>
<p>pengeluaran</p>
</div>
<div className={styles.cardImg}>
<img
src={filteredItems?.welcomePageConfig ?
API_BASE_URL + "/" + (JSON.parse(filteredItems.welcomePageConfig)?.image || '') : 'https://png.pngtree.com/png-vector/20221125/ourmid/pngtree-no-image-available-icon-flatvector-illustration-pic-design-profile-vector-png-image_40966566.jpg'
}
/>
<p>{filteredItems.name}</p>
</div>
</div>
)
) : (
selectedItemId == 0 || selectedItemId == -1 ?
(
<div className={styles.cardBody}>
<div className={styles.cardItem}>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>Total pemasukan</p>
<p>{formatIncome(items?.totalIncome)}</p>
</div>
<div className={styles.cardItem}>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>Total pengeluaran</p>
<p>{formatIncome(items?.totalOutcome)}</p>
</div>
</div>
) : (
<div className={styles.cardBody}>
<div className={styles.cardItem}>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>{formatIncome(selectedItems?.report.totalIncome)}</p>
<p>pemasukan</p>
</div>
<div className={styles.cardItem} >
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="44.000000pt" height="44.000000pt" viewBox="0 0 224.000000 246.000000"
preserveAspectRatio="xMidYMid meet">
<rect x="5" y="5" width="216" height="236" rx="200" ry="500" fill="rgb(255,255,255)" />
<g transform="translate(0.000000,246.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1001 2449 c-459 -53 -835 -377 -957 -824 -25 -92 -28 -118 -32 -331
-4 -251 4 -328 54 -485 119 -376 418 -666 782 -760 131 -33 259 -44 370 -31
273 32 484 133 672 322 173 172 277 375 325 635 14 71 16 133 13 319 -4 213
-7 239 -32 331 -124 453 -498 771 -971 825 -101 11 -119 11 -224 -1z m326
-115 c254 -52 490 -212 637 -432 59 -89 98 -174 133 -287 25 -84 27 -103 27
-270 1 -158 -2 -189 -22 -259 -108 -376 -367 -628 -752 -733 -50 -13 -103 -17
-230 -17 -127 0 -180 4 -230 17 -385 105 -644 357 -752 733 -20 70 -23 101
-22 259 0 167 2 186 27 270 70 228 191 404 372 538 156 116 314 176 525 201
56 6 209 -4 287 -20z m-854 -1898 c56 -39 61 -49 48 -94 -6 -22 -9 -21 -68 33
-61 55 -169 189 -198 246 -10 18 17 -4 72 -59 49 -48 114 -105 146 -126z
m1512 185 c-44 -87 -245 -311 -278 -311 -4 0 -7 18 -5 40 2 38 7 44 72 92 39
28 105 86 146 129 41 43 76 78 78 78 1 1 -4 -12 -13 -28z m-1269 -315 c83 -32
182 -56 299 -74 l50 -7 0 -55 0 -55 -50 4 c-84 6 -227 47 -313 90 l-82 41 0
45 c0 25 2 45 4 45 2 0 44 -15 92 -34z m892 -18 c-3 -42 -4 -44 -76 -81 -74
-39 -213 -79 -309 -90 l-53 -6 0 58 0 59 65 7 c82 8 237 47 295 73 71 32 82
30 78 -20z"/>
<path d="M995 2216 c-5 -2 -43 -11 -82 -21 -211 -48 -397 -178 -523 -365 -195
-288 -189 -700 14 -984 235 -329 663 -457 1038 -310 195 76 380 249 468 436
129 275 105 614 -60 858 -123 182 -297 306 -515 366 -55 15 -313 30 -340 20z
m311 -116 c226 -58 403 -200 507 -405 118 -235 108 -512 -25 -740 -53 -90
-183 -220 -273 -273 -244 -144 -546 -144 -790 0 -90 53 -220 183 -273 273
-133 228 -143 505 -25 740 103 204 280 346 503 404 105 28 270 28 376 1z"/>
<path d="M630 1401 l0 -331 80 0 80 0 0 125 0 125 28 0 c25 -1 31 -10 83 -123
l56 -122 91 -3 c51 -1 92 0 92 3 0 3 -30 64 -66 136 l-66 132 41 41 c22 22 45
54 50 69 25 66 5 155 -47 210 -53 56 -73 61 -254 65 l-168 4 0 -331z m297 167
c29 -27 29 -65 1 -95 -18 -19 -32 -23 -80 -23 l-58 0 0 70 0 70 57 0 c44 0 62
-5 80 -22z"/>
<path d="M1220 1240 l0 -330 75 0 75 0 0 96 c0 88 2 95 18 89 74 -32 132 -30
190 5 52 32 65 75 65 221 0 154 -13 192 -83 227 -54 27 -101 28 -157 2 -43
-19 -43 -19 -43 0 0 18 -7 20 -70 20 l-70 0 0 -330z m252 183 c25 -23 27 -177
1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/>
</g>
</svg>
<p>{formatIncome(selectedItems?.report.currentOutcome)}</p>
<p>pengeluaran</p>
</div>
<div className={styles.cardImg}>
<img
src={selectedItems?.welcomePageConfig ?
API_BASE_URL + "/" + (JSON.parse(selectedItems.welcomePageConfig)?.image || '') : 'https://png.pngtree.com/png-vector/20221125/ourmid/pngtree-no-image-available-icon-flatvector-illustration-pic-design-profile-vector-png-image_40966566.jpg'
}
/>
<p>{selectedItems.name}</p>
</div>
</div>
)
)}
</div> </div>
</div> </div>
</div> </div>
<div className={styles.dashboardBody}>
<button className={styles.goCafeButton} style={{ visibility: (selectedItems?.cafeId || selectedSubItems.find(cafe => cafe.cafeId == selectedSubItemId)?.cafeId) == null ? 'hidden' : 'visible' }} onClick={() => window.location.href = window.location.origin + '/' + (selectedItems?.cafeId || selectedSubItems.find(cafe => cafe.cafeId == selectedSubItemId)?.cafeId)}>Kunjungi kedai</button>
<div style={{ flex: 1 }}> <h3>terlakuu</h3>
<CircularDiagram segments={segments} /> <div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
margin: '5px'
}}
>
<div style={{ flex: 1 }}>
<CircularDiagram segments={segments} />
</div>
<div style={{ flex: "1 1 20%" }}>
{segments && segments.map((item, index) => (
<div
key={index}
style={{
display: "flex",
alignItems: "center",
}}
>
<div
style={{
marginRight: "5px",
fontSize: "1.2em",
color: colors[index],
}}
>
</div>
<h5 style={{ margin: 0, textAlign: "left" }}>{item.percentage == 'Infinity' || isNaN(item.percentage) ? 0 : item.percentage}% &nbsp; {item.value} </h5>
</div>
))}
{segments.length < 1 &&
<>
<div
style={{
display: "flex",
alignItems: "center",
}}
>
<h5 style={{
width: '100%', margin: 0, textAlign: "left", backgroundColor: 'gray', color: 'gray'
}}>-</h5>
</div>
<div
style={{
display: "flex",
alignItems: "center",
}}
>
<h5 style={{
width: '100%', margin: 0, textAlign: "left", backgroundColor: 'gray', color: 'gray'
}}>-</h5>
</div>
<div
style={{
display: "flex",
alignItems: "center",
}}
>
<h5 style={{
width: '100%', margin: 0, textAlign: "left", backgroundColor: 'gray', color: 'gray'
}}>-</h5>
</div>
<div
style={{
display: "flex",
alignItems: "center",
}}
>
<h5 style={{
width: '100%', margin: 0, textAlign: "left", backgroundColor: 'gray', color: 'gray'
}}>-</h5>
</div>
</>
}
</div>
</div>
<h3>penambahan stok</h3>
<BarChart Data={selectedItems?.report?.materialsPurchased} />
<div style={{ height: '24vh' }}></div>
</div> </div>
</div>
<div className={styles.cafeListWrapper}> <div className={styles.cafeListWrapper}>
<div className={styles.cafeListHeader}> <div className={styles.cafeListHeader}>
Semua {user.roleId < 1 ? 'penyewa' : 'kedai yang dikau miliki'} Semua {user.roleId < 1 ? 'penyewa' : 'kedai yang dikau miliki'}
@@ -218,54 +695,64 @@ const LinktreePage = ({ user, setModal }) => {
{user.roleId < 1 && {user.roleId < 1 &&
<div className={styles.rectangle} <div className={styles.rectangle}
style={{ backgroundColor: selectedItemId == -1 ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }} style={{ backgroundColor: selectedItemId == -1 ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }}
onClick={() => setSelectedItemId(selectedItemId == -1 ? 0 : -1)} onClick={() => { setSelectedItemId(selectedItemId == -1 ? 0 : -1); setModal('add-tenant') }}
> >
Tambah penyewa Tambah penyewa
</div> </div>
} }
{ {
items?.tenants?.length > 0 ? ( items?.items?.length > 0 ? (
items.tenants.map((tenant) => { items.items.map((item) => {
const isTenantSelected = selectedItemId === tenant.userId; const isTenantSelected = selectedItemId === (item.userId || item.cafeId);
const tenantBackgroundColor = isTenantSelected && !selectedSubItemId ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)'; const tenantBackgroundColor = isTenantSelected && !selectedSubItemId ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)';
const hasCafes = tenant?.cafes?.length > 0; const hasSubItems = item?.subItems?.length > 0;
return ( return (
<div key={tenant.userId}> <div key={item.userId || item.cafeId}>
<div <div
onClick={() => { onClick={() => {
setSelectedItemId(isTenantSelected && !selectedSubItemId ? 0 : tenant.userId); setSelectedItemId(isTenantSelected && !selectedSubItemId ? 0 : (item.userId || item.cafeId));
setSelectedSubItemId(0); // Reset subitem selection when changing tenant setSelectedSubItemId(0); // Reset subitem selection when changing tenant
}} }}
style={{ backgroundColor: tenantBackgroundColor }} style={{ backgroundColor: tenantBackgroundColor }}
className={isTenantSelected ? styles.rectangleNLine : styles.rectangle} className={isTenantSelected ? styles.rectangleNLine : styles.rectangle}
> >
<h3>{tenant.username}</h3> <h3>{item.username || item.name} &nbsp;</h3>
<div> <h3>
Total Pendapatan: {formatIncome(tenant.totalIncome)} Rp{formatIncome(item.totalIncome || item.report?.totalIncome) || 0}
</div> </h3>
</div> </div>
{/* Only show cafes if the tenant is selected */} {/* Only show cafes if the tenant is selected */}
{selectedItemId === tenant.userId && hasCafes && tenant.cafes.map((cafe) => { {selectedItemId === (item.userId || item.cafeId) && hasSubItems && item.subItems.map((subItem) => {
const isCafeSelected = selectedSubItemId === cafe.cafeId; const isCafeSelected = selectedSubItemId == (subItem.cafeId || subItem.userId);
const cafeBackgroundColor = isCafeSelected ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)'; const cafeBackgroundColor = isCafeSelected ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)';
return ( return (
<div <div
key={cafe.cafeId} key={subItem.cafeId || subItem.userId}
className={styles.subRectangle} className={styles.subRectangle}
onClick={() => { onClick={() => {
setSelectedSubItemId(isCafeSelected ? 0 : cafe.cafeId); // Toggle subitem selection setSelectedSubItemId(isCafeSelected ? 0 : (subItem.cafeId || subItem.userId)); // Toggle subitem selection
setSelectedItemId(tenant.userId); // Ensure tenant stays selected setSelectedItemId(item.userId || item.cafeId); // Ensure tenant stays selected
}} }}
style={{ backgroundColor: cafeBackgroundColor }} style={{ backgroundColor: cafeBackgroundColor }}
> >
{cafe.name} {subItem.name || subItem.username}
&nbsp;pendapatan {formatIncome(cafe.report?.totalIncome || 0)} {/* &nbsp;pendapatan {formatIncome(subItem.report?.totalIncome || 0)} */}
</div> </div>
); );
})} })}
{selectedItemId == item.cafeId &&
<div
className={styles.subRectangle}
onClick={() => {
}}
style={{}}
>
tambah kasir
</div>
}
</div> </div>
); );
}) })
@@ -273,18 +760,17 @@ const LinktreePage = ({ user, setModal }) => {
<div>No tenants available</div> <div>No tenants available</div>
) )
} }
<div style={{height:'2px'}}></div>
{user.roleId > 0 && {user.roleId > 0 &&
<div className={styles.rectangle} <div className={styles.rectangle}
style={{ backgroundColor: selectedItemId == 0 ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }} style={{ backgroundColor: selectedItemId == -1 ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }}
onClick={() => { setSelectedItemId(selectedItemId == 0 ? -1 : 0); setModal('create-cafe'); }} onClick={() => setSelectedItemId(selectedItemId == -1 ? 0 : -1)}
> >
Tambah kedai Tambah kedai
</div> </div>
} }
</div> <div style={{ height: '2px' }}></div>
</div> </div></>
) : ( ) : (
<div className={styles.centeredLinktreePage}> <div className={styles.centeredLinktreePage}>
<div className={styles.dashboardLine}></div> <div className={styles.dashboardLine}></div>

View File

@@ -1,11 +1,11 @@
/* General container */ /* General container */
.centeredLinktreePage { .centeredLinktreePage {
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
background-color: rgb(210, 232, 35); background-color: rgb(210, 232, 35);
} }
.nonCenteredLinktreePage { .nonCenteredLinktreePage {
height: 100vh; height: 100vh;
@@ -13,274 +13,298 @@
flex-direction: column; flex-direction: column;
justify-content: start; justify-content: start;
background-color: rgb(193 201 134); background-color: rgb(193 201 134);
position: relative;
} }
.dashboardLine { .dashboardLine {
position: fixed; position: fixed;
left: 0px; left: 0px;
height: 100vh; height: 100vh;
width: 30px; width: 30px;
border-right: 1px solid black; border-right: 1px solid black;
z-index: 5; z-index: 5;
}
.dashboardContainer {
background-color: rgb(210, 232, 35);
z-index: 6;
padding: 0 1rem;
padding-top: 40px;
}
/* Main Heading */
.mainHeading {
font-weight: 700;
font-size: 32px;
line-height: 2.25rem;
margin-bottom: 1rem;
letter-spacing: -1px;
color: rgb(37, 79, 26);
}
.swipeContainer {
height: 75px;
overflow: hidden;
width: 100%;
color: rgb(59 130 246);
}
.swipeContent {
animation: swipeUp 12s infinite;
}
.swipeItem {
overflow: hidden;
height: 75px;
max-width: 300px;
text-wrap: balance;
line-height: 34px;
}
/* Swipe Animation */
@keyframes swipeUp {
0%,
15% {
transform: translateY(0);
} }
.dashboardContainer { 20%,
background-color: rgb(210, 232, 35); 35% {
z-index: 6; transform: translateY(-20%);
padding: 0 1rem;
padding-top: 40px;
} }
/* Main Heading */ 40%,
.mainHeading { 55% {
font-weight: 700; transform: translateY(-40%);
font-size: 32px;
line-height: 2.25rem;
margin-bottom: 1rem;
letter-spacing: -1px;
color: rgb(37, 79, 26);
} }
.swipeContainer { 60%,
height: 75px; 75% {
overflow: hidden; transform: translateY(-60%);
width: 100%;
color: rgb(59 130 246);
} }
.swipeContent { 80%,
animation: swipeUp 12s infinite; 95% {
transform: translateY(-80%);
} }
}
.swipeItem { /* Sub Heading */
overflow: hidden; .subHeading {
height: 75px; font-weight: 400;
max-width: 300px; line-height: 1.5rem;
text-wrap: balance; font-size: 14px;
line-height: 34px; font-family: 'poppins';
} color: black;
margin-bottom: 1.5rem;
}
/* Swipe Animation */ .LoginForm {
@keyframes swipeUp { display: inline-flex;
0%, 15% { position: relative;
transform: translateY(0); height: 148px;
} }
20%, 35% {
transform: translateY(-20%);
}
40%, 55% {
transform: translateY(-40%);
}
60%, 75% {
transform: translateY(-60%);
}
80%, 95% {
transform: translateY(-80%);
}
}
/* Sub Heading */ /* Form */
.subHeading { .FormUsername {
font-weight: 400; display: flex;
line-height: 1.5rem; flex-direction: column;
font-size: 14px; align-items: flex-start;
font-family: 'poppins'; position: absolute;
color: black; left: 0vw;
margin-bottom: 1.5rem; }
}
.LoginForm {
display: inline-flex;
position: relative;
height: 148px;
}
/* Form */ .FormUsername.animateForm {
.FormUsername { animation: FormUsernameProgress 0.5s forwards;
display: flex; /* Apply the animation when inputtingPassword is true */
flex-direction: column; }
align-items: flex-start;
position: absolute; .FormUsername.reverseForm {
animation: FormUsernameReverse 0.5s forwards;
/* Reverse animation when inputtingPassword is false */
}
@keyframes FormUsernameProgress {
0% {
left: 0vw; left: 0vw;
} }
.FormUsername.animateForm { 100% {
animation: FormUsernameProgress 0.5s forwards; /* Apply the animation when inputtingPassword is true */ left: -100vw;
}
}
@keyframes FormUsernameReverse {
0% {
left: -100vw;
} }
.FormUsername.reverseForm { 100% {
animation: FormUsernameReverse 0.5s forwards; /* Reverse animation when inputtingPassword is false */ left: 0vw;
} }
}
@keyframes FormUsernameProgress { .FormPassword {
0% { display: flex;
left: 0vw; flex-direction: column;
} align-items: flex-start;
100% { position: absolute;
left: -100vw; left: 100vw;
} }
}
@keyframes FormUsernameReverse { .FormPassword.animateForm {
0% { animation: FormPasswordProgress 0.5s forwards;
left: -100vw; /* Apply the animation when inputtingPassword is true */
} }
100% {
left: 0vw;
}
}
.FormPassword { .FormPassword.reverseForm {
display: flex; animation: FormPasswordReverse 0.5s forwards;
flex-direction: column; /* Reverse animation when inputtingPassword is false */
align-items: flex-start; }
position: absolute;
@keyframes FormPasswordProgress {
0% {
left: 100vw; left: 100vw;
} }
.FormPassword.animateForm { 100% {
animation: FormPasswordProgress 0.5s forwards; /* Apply the animation when inputtingPassword is true */ left: 0vw;
}
}
@keyframes FormPasswordReverse {
0% {
left: 0vw;
} }
.FormPassword.reverseForm { 100% {
animation: FormPasswordReverse 0.5s forwards; /* Reverse animation when inputtingPassword is false */ left: 100vw;
}
}
.usernameLabel {
font-size: 0.875rem;
color: #444;
margin-bottom: 5px;
position: relative;
}
.usernameInput {
width: 250px;
height: 55px;
padding-left: 10px;
font-size: 1rem;
background-color: #f0f0f0;
border-radius: 5px;
border: 1px solid #ccc;
margin-top: 5px;
margin-bottom: 15px;
}
.usernameInputError {
width: 250px;
height: 55px;
padding-left: 10px;
font-size: 1rem;
background-color: #f0f0f0;
border-radius: 5px;
border: 2px solid red;
/* Red border when error is true */
margin-top: 5px;
margin-bottom: 15px;
/* Apply keyframe animation for border color transition */
animation: borderTransition 2s ease-in-out forwards;
}
/* Keyframe animation for border color transition */
@keyframes borderTransition {
0% {
border-color: red;
/* Initial red border */
} }
@keyframes FormPasswordProgress { 100% {
0% { border-color: transparent;
left: 100vw; /* Transition to transparent */
}
100% {
left: 0vw;
}
} }
}
@keyframes FormPasswordReverse { .claimButton {
0% { width: 200px;
left: 0vw; height: 45px;
} background-color: #254F1A;
100% { color: #D2E823;
left: 100vw; text-align: center;
} font-size: 1rem;
} padding: 10px;
border-radius: 30px;
border: none;
cursor: pointer;
}
.claimButton span {
font-weight: 600;
}
.usernameLabel { /* Footer */
font-size: 0.875rem; .footer {
color: #444; display: flex;
margin-bottom: 5px; flex-wrap: wrap;
position: relative; justify-content: space-between;
} margin-top: 2rem;
}
.usernameInput { .footerLinks {
width: 250px; flex: 1;
height: 55px; display: flex;
padding-left: 10px; flex-direction: column;
font-size: 1rem; }
background-color: #f0f0f0;
border-radius: 5px;
border: 1px solid #ccc;
margin-top: 5px;
margin-bottom: 15px;
}
.usernameInputError { .footerLink {
width: 250px; font-size: 0.875rem;
height: 55px; color: #254F1A;
padding-left: 10px; margin-bottom: 0.5rem;
font-size: 1rem; text-decoration: underline;
background-color: #f0f0f0; }
border-radius: 5px;
border: 2px solid red; /* Red border when error is true */
margin-top: 5px;
margin-bottom: 15px;
/* Apply keyframe animation for border color transition */ .signupButton {
animation: borderTransition 2s ease-in-out forwards; background-color: transparent;
} border: 1px solid #254F1A;
color: #254F1A;
padding: 12px 30px;
border-radius: 30px;
text-align: center;
font-size: 0.875rem;
margin-top: 1.5rem;
cursor: pointer;
}
/* Keyframe animation for border color transition */ .footerImage {
@keyframes borderTransition { flex: 1;
0% { text-align: center;
border-color: red; /* Initial red border */ }
}
100% {
border-color: transparent; /* Transition to transparent */
}
}
.claimButton { .footerImage img {
width: 200px; width: 150px;
height: 45px; height: 226px;
background-color: #254F1A; margin-top: -50px;
color: #D2E823; }
text-align: center;
font-size: 1rem;
padding: 10px;
border-radius: 30px;
border: none;
cursor: pointer;
}
.claimButton span { .userInfo {
font-weight: 600; width: 100vw;
} background-color: white;
left: 0;
position: fixed;
border-radius: 20px 20px 0 0;
bottom: 0;
overflow: hidden;
/* Footer */ top: 75vh;
.footer { transition: top 0.5s ease, padding 0.5s ease;
display: flex; }
flex-wrap: wrap;
justify-content: space-between;
margin-top: 2rem;
}
.footerLinks {
flex: 1;
display: flex;
flex-direction: column;
}
.footerLink {
font-size: 0.875rem;
color: #254F1A;
margin-bottom: 0.5rem;
text-decoration: underline;
}
.signupButton {
background-color: transparent;
border: 1px solid #254F1A;
color: #254F1A;
padding: 12px 30px;
border-radius: 30px;
text-align: center;
font-size: 0.875rem;
margin-top: 1.5rem;
cursor: pointer;
}
.footerImage {
flex: 1;
text-align: center;
}
.footerImage img {
width: 150px;
height: 226px;
margin-top: -50px;
}
.userInfo {
width: 100vw;
background-color: white;
left: 0;
position: fixed;
border-radius: 20px 20px 0 0;
bottom: 0;
overflow: hidden;
top: 75vh;
transition: top 0.5s ease, padding 0.5s ease;
}
.userInfoExpanded { .userInfoExpanded {
width: 100vw; width: 100vw;
@@ -294,117 +318,100 @@
top: 15vh; top: 15vh;
transition: top 0.5s ease, padding 0.5s ease; transition: top 0.5s ease, padding 0.5s ease;
} }
.userInfoExpandButton {
width: 100%; .userInfoExpandButton {
background-color: #d1ecdf; width: 100%;
height: 30px; background-color: #d1ecdf;
position: absolute; height: 30px;
} position: absolute;
}
.ItemContainer { .ItemContainer {
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
font-size: 10px; font-size: 10px;
} }
.Item { .Item {
background-color: #fff2a3; background-color: #fff2a3;
border-radius: 20px; border-radius: 20px;
margin: 20px; margin: 20px;
padding: 10px; padding: 10px;
} }
.transactionContainer {
margin-left: 20px;
margin-top: 10px;
}
.transaction { .transactionContainer {
padding: 10px; margin-left: 20px;
margin-bottom: 5px; margin-top: 10px;
border-radius: 10px; }
}
.transaction {
padding: 10px;
margin-bottom: 5px;
border-radius: 10px;
}
.rectangle { .rectangle {
height: 50px; height: 50px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 10px; border-radius: 10px;
font-size: 20px; font-size: 20px;
background-color: rgb(114, 114, 114); background-color: rgb(114, 114, 114);
margin: 5%; margin: 5%;
position: relative; position: relative;
font-weight: 500; font-weight: 500;
margin-bottom: 20px; /* Add some space below the rectangle */ margin-bottom: 20px;
} /* Add some space below the rectangle */
.rectangleNLine { }
height: 50px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
font-size: 20px;
background-color: rgb(114, 114, 114);
margin: 5%;
position: relative;
font-weight: 500;
margin-bottom: 20px; /* Add some space below the rectangle */
}
.subRectangle { .rectangleNLine {
height: 50px; height: 50px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 10px; border-radius: 10px;
font-size: 20px; font-size: 20px;
background-color: rgb(114, 114, 114); background-color: rgb(114, 114, 114);
margin: 5%; margin: 5%;
position: relative; position: relative;
font-weight: 500; font-weight: 500;
margin-top: 10px; /* Move it down a little */ margin-bottom: 20px;
margin-left: 50px; /* Move it down a little */ /* Add some space below the rectangle */
} }
/* Arrow + line connection */ .subRectangle {
.rectangleNLine::after { height: 50px;
content: ""; display: flex;
position: absolute; justify-content: center;
left: -17px; align-items: center;
top: 50%; border-radius: 10px;
width: 18px; font-size: 20px;
height: 2px; /* Line thickness */ background-color: rgb(114, 114, 114);
background-color: rgb(114, 114, 114); /* Line color */ margin: 5%;
transform: translateY(-50%); /* Center the line vertically */ position: relative;
} font-weight: 500;
margin-top: 10px;
/* Move it down a little */
margin-left: 50px;
/* Move it down a little */
}
/* Arrow + line connection */
.subRectangle::after {
content: "";
position: absolute;
left: -45px; /* Position it to the left of the rectangle */
top: 50%; /* Center vertically */
width: 45px; /* Length of the horizontal line */
height: 2px; /* Line thickness */
background-color: rgb(114, 114, 114); /* Line color */
transform: translateY(-50%); /* Center the line vertically */
}
/* The arrow at the bottom */
.subRectangle::before { /* The arrow at the bottom */
content: "-"; .subRectangle::before {
position: absolute; left: -14px;
left: -45px; top: 48%;
top: -21%; width: 2px;
width: 2px; height: 54px;
height: 70px; content: "";
content: ""; position: absolute;
position: absolute; background-color: rgb(114, 114, 114);
background-color: rgb(114, 114, 114); transform: translateY(-50%);
transform: translateY(-50%); }
}
@@ -413,58 +420,112 @@
top: 0; top: 0;
width: 100%; width: 100%;
height: calc(25vh - 25px); height: calc(25vh - 25px);
margin-bottom: 15vh;
padding-top: 25px; padding-top: 25px;
margin-bottom: 85px;
} }
.headerCardWrapper { .headerCardWrapper {
top: 0; top: 0;
position: absolute; position: absolute;
width: 100%; width: 100%;
bottom: 50vh; bottom: 50vh;
justify-content: center; justify-content: center;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.headerCard { .headerCard {
background-color: #947257; background-color: #779f94;
position: relative; position: relative;
width: 90%; width: 90%;
border-radius: 20px; border-radius: 20px;
height: 40%; height: 35%;
}
.cafeListWrapper{
background-color: white;
border-radius: 20px 20px 0 0;
bottom: 0;
position: absolute;
width: 100%;
max-height: 164.5px;
} }
.cafeListHeader{ .cardBody {
background-color: white; display: flex;
border-radius: 20px 20px 0 0; width: 100%;
bottom: 0; height: 100%;
position: absolute; align-items: center;
width: 100%; justify-content: center; /* Center the content */
height: 100%;
top: -45px;
background-color: #b09c72;
text-align: center;
padding-top: 10px;
font-weight: 500;
}
.cafeList{
background-color: white;
border-radius: 20px 20px 0 0;
bottom: 0;
position: absolute;
width: 100%;
height: 100%;
} }
.itemInput{ .cardImg {
border-radius: 20px;
font-size: 15px;
text-align: center;
margin: 10px;
padding: 5px;
height: 100%;
place-content: center;
}
.cardImg img {
width: 100%;
height: auto;
max-width: 85px;
object-fit: contain;
border-radius: 50%; /* Keep the circular shape */
}
.cardBody p {
margin: 1vh;
margin-top: 0;
margin-bottom: 0;
}
.cardItem{
border-radius: 20px;
font-size: 15px;
text-align: center;
margin: 10px;
padding: 5px;
}
.dashboardBody {
padding-left: 5vw;
padding-right: 5vw;
}
.goCafeButton {
width: 100%;
background-color: rgb(255, 215, 176);
border-radius: 20px;
position: relative;
z-index: 1;
}
.cafeListWrapper {
background-color: white;
border-radius: 20px 20px 0 0;
top: 83vh;
position: absolute;
width: 100%;
}
.cafeListHeader {
background-color: white;
border-radius: 20px 20px 0 0;
bottom: 0;
position: absolute;
width: 100%;
height: 100%;
top: -45px;
background-color: #b09c72;
text-align: center;
padding-top: 10px;
font-weight: 500;
}
.cafeList {
background-color: white;
border-radius: 20px 20px 0 0;
bottom: 0;
position: absolute;
width: 100%;
height: 100%;
}
.itemInput {
width: 50%; width: 50%;
height: 60%; height: 60%;
border-radius: 14px; border-radius: 14px;

View File

@@ -1,9 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { ThreeDots } from "react-loader-spinner";
import { import {
getFavourite,
getReports, getReports,
getIncome,
} 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";
@@ -89,8 +86,8 @@ const RoundedRectangle = ({
<div style={valueStyle}>{loading ? "Loading..." : value}</div> <div style={valueStyle}>{loading ? "Loading..." : value}</div>
<div style={percentageStyle}> <div style={percentageStyle}>
{loading ? "" : percentage} {loading ? "" : percentage}
{percentage != undefined && !loading && "%"} {percentage !== undefined && !loading && "%"}
{percentage != undefined && !loading && ( {percentage !== undefined && !loading && (
<span style={arrowStyle}> <span style={arrowStyle}>
{percentage > 0 ? "↗" : percentage === 0 ? "-" : "↘"} {percentage > 0 ? "↗" : percentage === 0 ? "-" : "↘"}
</span> </span>
@@ -105,11 +102,9 @@ const RoundedRectangle = ({
const App = ({ cafeId, const App = ({ cafeId,
handleClose }) => { handleClose }) => {
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("daily"); const [filter, setFilter] = useState("daily");
const [viewStock, setViewStock] = useState(false);
const fetchData = async (filter) => { const fetchData = async (filter) => {
try { try {
@@ -129,10 +124,8 @@ const App = ({ cafeId,
fetchData(filter); // Fetch data when filter changes fetchData(filter); // Fetch data when filter changes
}, [filter]); }, [filter]);
const [sold, percentage] = analytics[filter] || [0, 0];
const filteredItems = analytics.items || []; const filteredItems = analytics.items || [];
const totalSold = filteredItems.reduce((sum, item) => sum + item.count, 0);
const colors = [ const colors = [
"#FF0000", // Red "#FF0000", // Red
"#FF6F00", // Dark Orange "#FF6F00", // Dark Orange
@@ -192,15 +185,15 @@ const App = ({ cafeId,
const filterTexts = ["1", "7", "30", "365"]; const filterTexts = ["1", "7", "30", "365"];
const comparisonText = const comparisonText =
filterTexts[["daily", "weekly", "monthly", "yearly"].indexOf(filter)]; filterTexts[["daily", "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", {
year: "numeric", // year: "numeric",
month: "long", // month: "long",
day: "numeric", // day: "numeric",
hour: "numeric", // hour: "numeric",
}); // });
}; // };
return ( return (
<div style={{ <div style={{
@@ -209,7 +202,8 @@ const App = ({ cafeId,
width: '100vw', width: '100vw',
top: 0, top: 0,
right: 0, right: 0,
backgroundColor: 'rgb(207, 207, 207)'}} backgroundColor: 'rgb(207, 207, 207)',
overflowY: 'auto'}}
> >
<h2 className={styles["Transactions-title"]}>Laporan</h2> <h2 className={styles["Transactions-title"]}>Laporan</h2>
<div style={{ textAlign: "center", <div style={{ textAlign: "center",