This commit is contained in:
zadit
2024-11-19 00:31:28 +07:00
parent b8c1d30d14
commit 7daa8d6653
6 changed files with 284 additions and 255 deletions

View File

@@ -1,16 +1,14 @@
import React, { useState, useEffect } from 'react';
import styles from './LinktreePage.module.css'; // Import the module.css file
import { loginUser } from "../helpers/userHelpers";
import { ThreeDots } from "react-loader-spinner";
import { useNavigate } from "react-router-dom";
import { getLocalStorage, removeLocalStorage } from "../helpers/localStorageHelpers";
import { getAllCafeOwner, createCafeOwner } from "../helpers/userHelpers";
import styles from './LinktreePage.module.css';
import { loginUser, getAllCafeOwner, createCafeOwner } from "../helpers/userHelpers";
import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers";
import { getMyTransactions } from "../helpers/transactionHelpers";
import { unsubscribeUser } from "../helpers/subscribeHelpers.js";
import { getLocalStorage, removeLocalStorage } from "../helpers/localStorageHelpers";
import { ThreeDots } from "react-loader-spinner";
import Header from '../components/Header';
import CircularDiagram from "./CircularDiagram";
const LinktreePage = ({ user, setModal }) => {
const navigate = useNavigate();
@@ -19,203 +17,238 @@ const LinktreePage = ({ user, setModal }) => {
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [items, setItems] = useState([]);
const [isCreating, setIsCreating] = useState(false);
const [newItem, setNewItem] = useState({ name: "", type: "" });
const [isModalOpen, setIsModalOpen] = useState(false);
const [expanded, setIsExpand] = useState(false);
const [expandedCafeId, setExpandedCafeId] = useState(null); // Track which cafe is expanded
const [expandedCafeId, setExpandedCafeId] = useState(null);
const [selectedItemId, setSelectedItemId] = useState(0);
// Handle expand/collapse of cafe details
const handleToggleExpand = (cafeId) => {
setExpandedCafeId(expandedCafeId === cafeId ? null : cafeId); // Toggle expand for a specific cafe
setExpandedCafeId(expandedCafeId === cafeId ? null : cafeId);
};
// Handle user transactions
const handleMyTransactions = async () => {
try {
setError(false);
setLoading(true);
const response = await getMyTransactions();
if (response) {
console.log(response)
return response;
setItems(response);
} else {
setError(true); // Trigger error state in the button
console.error('Login failed');
setError(true);
}
} catch (error) {
setError(true);
console.error('Error occurred while logging in:', error.message);
} finally {
setLoading(false); // Ensure loading state is cleared
setLoading(false);
console.log(items);
}
};
// Handle login
const handleLogin = async () => {
try {
setError(false);
setLoading(true);
const response = await loginUser(username, password);
if (response.success) {
localStorage.setItem('auth', response.token);
if (response.cafeId !== null) {
window.location.href = response.cafeId;
} else {
let destination = '/';
window.location.href = destination;
}
window.location.href = response.cafeId ? `/${response.cafeId}` : '/';
} else {
setError(true); // Trigger error state in the button
console.error('Login failed');
setError(true);
}
} catch (error) {
setError(true);
console.error('Error occurred while logging in:', error.message);
} finally {
setLoading(false); // Ensure loading state is cleared
setLoading(false);
}
};
const handleModalClose = () => {
setIsModalOpen(false);
};
// Handle logout
const handleLogout = () => {
removeLocalStorage("auth");
unsubscribeUser();
navigate(0);
};
// Fetch data when the user changes
useEffect(() => {
if (user && user.roleId === 0) {
if (user) {
setLoading(true);
getAllCafeOwner()
.then((data) => {
setItems(data);
switch (user.roleId) {
case 0:
getAllCafeOwner().then(setItems).catch(console.error).finally(() => setLoading(false));
break;
case 1:
getOwnedCafes(user.userId).then(setItems).catch(console.error).finally(() => setLoading(false));
break;
case 3:
handleMyTransactions();
break;
default:
setLoading(false);
})
.catch((error) => {
console.error("Error fetching cafe owners:", error);
setLoading(false);
});
break;
}
}
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);
});
}
if (user && user.roleId == 3) {
handleMyTransactions()
.then((data) => {
setItems(data);
setLoading(false);
})
.catch((error) => {
console.error("Error fetching owned cafes:", error);
setLoading(false);
});
}
console.log(items);
}, [user]);
const handleCreateItem = () => {
if (user.roleId < 1) {
// Create admin functionality
createCafeOwner(newItem.email, newItem.username, newItem.password)
.then((newitem) => {
setItems([...items, { userId: newitem.userId, name: newitem.username }]);
setIsCreating(false);
setNewItem({ name: "", type: "" });
})
.catch((error) => {
console.error("Error creating admin:", error);
});
// Handle create item (admin or cafe owner)
const handleCreateItem = async () => {
try {
if (user.roleId < 1) {
const newOwner = await createCafeOwner(newItem.email, newItem.username, newItem.password);
setItems([...items, { userId: newOwner.userId, name: newOwner.username }]);
} else {
const newCafe = await createCafe(newItem.name);
setItems([...items, { cafeId: newCafe.cafeId, name: newCafe.name }]);
}
setIsCreating(false);
setNewItem({ name: "", type: "" });
} catch (error) {
console.error("Error creating item:", error);
}
};
const formatIncome = (amount) => {
if (amount >= 1_000_000_000) {
// Format for billions
const billions = amount / 1_000_000_000;
return billions.toFixed(0) + "b"; // No decimal places for billions
} else if (amount >= 1_000_000) {
// Format for millions
const millions = amount / 1_000_000;
return millions.toFixed(2).replace(/\.00$/, "") + "m"; // Two decimal places, remove trailing '.00'
} else if (amount >= 1_000) {
// Format for thousands
const thousands = amount / 1_000;
return thousands.toFixed(1).replace(/\.0$/, "") + "k"; // One decimal place, remove trailing '.0'
} else {
// Create cafe functionality
createCafe(newItem.name)
.then((newitem) => {
setItems([...items, { cafeId: newitem.cafeId, name: newitem.name }]);
setIsCreating(false);
setNewItem({ name: "", type: "" });
})
.catch((error) => {
console.error("Error creating cafe:", error);
});
// Less than a thousand
if (amount != null) return amount.toString();
}
};
return (
<div className={styles.linktreePage}>
{/* SVG Icon */}
<>
{user && user.roleId < 2 ? (
<div className={styles.nonCenteredLinktreePage}>
{user && user.roleId < 2 && (
<div className={styles.dashboard}>
{loading && <ThreeDots />}
{items.map((item, index) => (
<div
key={index}
onClick={() => navigate("/" + item.cafeId)}
className={styles.rectangle}
>
<h1>{item.name || item.username}</h1>
<div><h1>{item.report?.totalIncome}</h1></div>
<div className={styles.dashboard}>
<div className={styles.header}>
<Header
HeaderText={"selamat siang " + user.username}
isEdit={() => setIsModalOpen(true)}
isLogout={handleLogout}
user={user}
showProfile={true}
setModal={setModal}
HeaderMargin='0px'
/>
</div>
))}
{user && user.roleId < 1 ? (
<div
className={styles.rectangle}
onClick={() => setIsCreating(true)}
>
Create Client
<div className={styles.headerCardWrapper}>
<div className={styles.headerCard}>
<h1>Total pemasukan</h1>
</div>
</div>
) : (
<div
className={styles.rectangle}
onClick={() => setIsCreating(true)}
>
+
</div>
<div style={{ flex: 1 }}>
<CircularDiagram segments={[]} />
</div>
<div style={{ flex: 1 }}>
<CircularDiagram segments={[]} />
</div>
<div className={styles.cafeListWrapper}>
<div className={styles.cafeListHeader}>
Semua {user.roleId < 1 ? 'penyewa' : 'kedai yang dikau miliki'}
</div>
)}
<div className={styles.cafeList}>
</div>
{user.roleId < 1 &&
<div className={styles.rectangle}
style={{ backgroundColor: selectedItemId == -1 ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }}
onClick={() => setSelectedItemId(selectedItemId == 0 ? -1 : 0)}
>
Tambah penyewa
</div>
}
{
items && Array.isArray(items.tenants) && items.tenants.length > 0 ? (
items.tenants.map((tenant, tenantIndex) => (
<>
<div key={tenant.userId} onClick={() => setSelectedItemId(selectedItemId == tenant.userId ? 0 : tenant.userId)} style={{ backgroundColor: selectedItemId == tenant.userId ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }} className={styles.rectangle}>
<h3>{tenant.username}</h3>
<div>
Total Pendapatan: {formatIncome(tenant.totalIncome)} {/* Total Income for the tenant */}
</div>
</div>
{selectedItemId == tenant.userId && tenant.cafes && tenant.cafes.length > 0 && tenant.cafes.map((cafe, cafeIndex) => (
<div>
<h5 ></h5>
<div key={cafe.cafeId} className={styles.subRectangle}
onClick={() => setSelectedItemId(selectedItemId === cafe.cafeId ? -1 : cafe.cafeId)}
style={{
backgroundColor: selectedItemId === cafe.cafeId ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)'
}}
>
{cafe.name}
&nbsp;pendapatan {formatIncome(cafe.report?.totalIncome || 0)}
</div>
</div>
))}
</>
))
) : (
<div>No tenants available</div>
)
}
{user.roleId > 0 &&
<div className={styles.rectangle}
style={{ backgroundColor: selectedItemId == 0 ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }}
onClick={() => { setSelectedItemId(selectedItemId == 0 ? -1 : 0); setModal('create-cafe'); }}
>
Tambah kedai
</div>
}
</div>
</div>
)}
{(user.length == 0 || user.roleId > 1) &&
<>
) : (
<div className={styles.centeredLinktreePage}>
<div className={styles.dashboardLine}></div>
<div className={styles.dashboardContainer}>
{/* Main Heading */}
<div className={styles.mainHeading}>
COBA KEDAIMASTER
<div className={styles.swipeContainer}>
<div className={styles.swipeContent}>
<div className={styles.swipeItem}>pemesanan langsung dari meja</div>
<div className={styles.swipeItem}>pengelolaan pesanan dan keuangan</div>
<div className={styles.swipeItem}>tentukan suasana musik</div>
<div className={styles.swipeItem}>pengelolaan stok dan manajemen</div>
<div className={styles.swipeItem}>jangan pernah ragukan pelanggan</div>
{['pemesanan langsung dari meja', 'pengelolaan pesanan dan keuangan', 'tentukan suasana musik', 'pengelolaan stok dan manajemen', 'jangan pernah ragukan pelanggan'].map((item, index) => (
<div key={index} className={styles.swipeItem}>{item}</div>
))}
</div>
</div>
diskon 0%
</div>
{/* Sub Heading */}
<div className={styles.subHeading}>
Solusi berbasis web untuk memudahkan pengelolaan kedai, dengan fitur yang mempermudah pemilik, kasir, dan tamu berinteraksi.
</div>
{getLocalStorage('auth') == null &&
{getLocalStorage('auth') == null && (
<div className={styles.LoginForm}>
<div className={`${styles.FormUsername} ${inputtingPassword ? styles.animateForm : styles.reverseForm}`}>
<label htmlFor="username" className={styles.usernameLabel}>
---- masuk -------------------------------
</label>
<label htmlFor="username" className={styles.usernameLabel}>---- masuk -------------------------------</label>
<input
id="username"
placeholder="username"
@@ -231,13 +264,12 @@ const LinktreePage = ({ user, setModal }) => {
<div className={`${styles.FormPassword} ${inputtingPassword ? styles.animateForm : styles.reverseForm}`}>
<span>
<label onClick={() => setInputtingPassword(false)} htmlFor="password" className={styles.usernameLabel}>
&lt;--- &lt;-- kembali
</label><label htmlFor="password" className={styles.usernameLabel}>
&nbsp; ------ &nbsp;
</label><label onClick={() => setModal('reset-password', { username: username })} htmlFor="password" className={styles.usernameLabel}>
<label onClick={() => setInputtingPassword(false)} htmlFor="password" className={styles.usernameLabel}> &lt;--- &lt;-- kembali </label>
<label htmlFor="password" className={styles.usernameLabel}> &nbsp; ----- &nbsp; </label>
<label onClick={() => setModal('reset-password', { username: username })} className={styles.usernameLabel}>
lupa password? -
</label></span>
</label>
</span>
<input
id="password"
placeholder="password"
@@ -256,133 +288,34 @@ const LinktreePage = ({ user, setModal }) => {
</button>
</div>
</div>
)}
}
{/* Footer Links */}
<div className={styles.footer}>
<div className={styles.footerLinks}>
<a
href="https://linktr.ee/discover/trending"
target="_blank"
rel="noreferrer"
className={styles.footerLink}
>
Pelajari lebih lanjut
</a>
<a
href="https://linktr.ee"
target="_blank"
rel="noreferrer"
className={styles.footerLink}
>
Tentang kedaimaster.com
</a>
<a href="https://linktr.ee/discover/trending" target="_blank" rel="noreferrer" className={styles.footerLink}>Pelajari lebih lanjut</a>
<a href="https://linktr.ee" target="_blank" rel="noreferrer" className={styles.footerLink}>Tentang kedaimaster.com</a>
<a
target="_blank"
rel="noreferrer"
className={styles.signupButton}
style={{ textDecoration: 'none' }}
onClick={() => setModal('join')}
>
Daftarkan kedaimu
</a>
</div>
<div className={styles.footerImage}>
<img
style={{ height: '226px', width: '150px', objectFit: 'cover' }}
src="/kedai.png"
alt="Linktree visual"
onError={(e) => e.target.src = '/fallback-image.png'}
/>
</div>
</div>
{user.length != 0 &&
<div className={` ${expanded ? styles.userInfoExpanded : styles.userInfo}`}>
<div onClick={() => setIsExpand(!expanded)} className={styles.userInfoExpandButton}>
{!expanded ?
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="#000000"
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path d="m 1 11 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 l 6 -6 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 6 6 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -5.292969 -5.292969 l -5.292969 5.292969 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={"#2e3436"}></path>
</g>
</svg>
:
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="#000000"
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path d="m 1 5 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 5.292969 5.292969 l 5.292969 -5.292969 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 l -6 6 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -6 -6 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={"#2e3436"}></path>
</g>
</svg>
}
</div>
<Header
HeaderText={"Kedai yang pernah kamu kunjungi"}
HeaderSize={'4vw'}
showProfile={true}
setModal={setModal}
isLogout={handleLogout}
user={user}
<img
style={{ height: '226px', width: '150px', objectFit: 'cover' }}
src="/kedai.png"
alt="Linktree visual"
onError={(e) => e.target.src = '/fallback-image.png'}
/>
<div className={styles.ItemContainer} style={{height: expanded?'85%':'43.5%'}}>
{items.map((item, index) => (
<div className={styles.Item}onClick={() => handleToggleExpand(item.cafeId)} key={index}>
{/* Render cafes */}
<div
// Toggle expansion on cafe click
className={styles.rectangle}
>
<h1>{item.name || item.username}</h1>
</div>
{/* Render transactions if the cafe is expanded */}
{expandedCafeId === item.cafeId && item.transactions && (
<div className={styles.Item}>
{item.transactions.map((transaction, transactionIndex) => (
<div
key={transactionIndex}
className={styles.transaction}
style={{ backgroundColor: 'orange' }}
>
{transaction.detailedTransactions && transaction.detailedTransactions.map((detailedTransaction, detailedTransactionIndex) => (
<div key={detailedTransactionIndex}>
<p>Quantity: {detailedTransaction.qty}</p>
{detailedTransaction.Item && (
<div>
<h4>Item Name: {detailedTransaction.Item.name}</h4>
<p>Price: {detailedTransaction.Item.price}</p>
</div>
)}
</div>
))}
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
}
</div>
</>
}
</div>
</div>
)}
</>
);
};

View File

@@ -1,5 +1,5 @@
/* General container */
.linktreePage {
.centeredLinktreePage {
height: 100vh;
display: flex;
flex-direction: column;
@@ -7,6 +7,14 @@
background-color: rgb(210, 232, 35);
}
.nonCenteredLinktreePage {
height: 100vh;
display: flex;
flex-direction: column;
justify-content: start;
background-color: rgb(193 201 134);
}
.dashboardLine {
position: fixed;
left: 0px;
@@ -319,11 +327,90 @@
.rectangle {
height: 150px; /* Height of each rectangle */
height: 50px; /* Height of each rectangle */
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px; /* Rounded corners */
font-size: 24px;
font-size: 20px;
background-color: rgb(114, 114, 114);
margin: 5%;
position: relative;
font-weight: 500;
}
.subRectangle {
height: 50px; /* Height of each rectangle */
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px; /* Rounded corners */
font-size: 20px;
background-color: rgb(114, 114, 114);
margin: 5%;
position: relative;
font-weight: 500;
margin-left: 60px;
}
.header {
background-color: white;
top: 0;
width: 100%;
height: calc(25vh - 25px);
margin-bottom: 15vh;
padding-top: 25px;
}
.headerCardWrapper {
top: 0;
position: absolute;
width: 100%;
bottom: 50vh;
justify-content: center;
display: flex;
align-items: center;
}
.headerCard {
background-color: #947257;
position: relative;
width: 90%;
border-radius: 20px;
height: 40%;
}
.cafeListWrapper{
background-color: white;
border-radius: 20px 20px 0 0;
bottom: 0;
position: absolute;
width: 100%;
max-height: 164.5px;
}
.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%;
height: 60%;
border-radius: 14px;
}

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { ThreeDots } from "react-loader-spinner";
import {
getFavourite,
getAnalytics,
getReports,
getIncome,
} from "../helpers/transactionHelpers.js";
import CircularDiagram from "./CircularDiagram";
@@ -115,7 +115,7 @@ const App = ({ cafeId,
try {
setLoading(true);
// Fetch the analytics data with the selected filter
const analyticsData = await getAnalytics(cafeId, filter);
const analyticsData = await getReports(cafeId, filter);
console.log(analyticsData);
if (analyticsData) setAnalytics(analyticsData);
} catch (error) {