This commit is contained in:
everythingonblack
2025-08-19 18:52:12 +07:00
parent 56961ef8f6
commit 37fca895bf

View File

@@ -1,8 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import styles from "../Styles.module.css"; import { Container, Row, Col, Card, Button, Tabs, Tab, Form } from "react-bootstrap";
import { Box, Settings, ShoppingCart } from "lucide-react"; import { useNavigate } from "react-router-dom";
import { useNavigate } from 'react-router-dom';
const Dashboard = ({ const Dashboard = ({
subscriptions, subscriptions,
@@ -13,10 +11,9 @@ const Dashboard = ({
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [activeTab, setActiveTab] = useState("products"); const [activeTab, setActiveTab] = useState("products");
const [hoveredCard, setHoveredCard] = useState(null);
const [products, setProducts] = useState([]); const [products, setProducts] = useState([]);
const [hoveredCard, setHoveredCard] = useState(null);
// User Settings form state
const [settings, setSettings] = useState({ const [settings, setSettings] = useState({
username: "", username: "",
email: "", email: "",
@@ -30,16 +27,32 @@ const Dashboard = ({
} }
}); });
useEffect(() => { // 🔹 Handle input settings
if (userData) { const handleSettingsChange = (field, value, nested = false) => {
setSettings(userData); if (nested) {
setSettings((prev) => ({
...prev,
profile_data: { ...prev.profile_data, [field]: value }
}));
} else {
setSettings((prev) => ({ ...prev, [field]: value }));
} }
}, [userData]); };
useEffect(() => { // 🔹 Save profile
if (!subscriptions) return; const saveSettings = () => {
fetch("https://bot.kediritechnopark.com/webhook-test/user-production/data", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(settings)
})
.then((res) => res.json())
.then(() => alert("Settings updated successfully!"))
.catch((err) => alert("Error updating settings: " + err));
};
function groupSubscriptionsByProductName(subs) { // 🔹 Group subscriptions
const groupSubscriptionsByProductName = (subs) => {
const result = {}; const result = {};
subs.forEach((sub) => { subs.forEach((sub) => {
const name = sub.product_name; const name = sub.product_name;
@@ -54,52 +67,43 @@ const Dashboard = ({
subscriptions: [] subscriptions: []
}; };
} }
const currentEnd = new Date(result[name].end_date); const currentEnd = new Date(result[name].end_date);
const thisEnd = new Date(sub.end_date); const thisEnd = new Date(sub.end_date);
if (thisEnd > currentEnd) { if (thisEnd > currentEnd) result[name].end_date = sub.end_date;
result[name].end_date = sub.end_date;
}
if (sub.unit_type === "token") { if (sub.unit_type === "token") {
result[name].quantity += sub.quantity ?? 0; result[name].quantity += sub.quantity ?? 0;
} else { } else {
result[name].quantity += 1; result[name].quantity += 1;
} }
result[name].subscriptions.push(sub); result[name].subscriptions.push(sub);
}); });
return result; return result;
} };
// 🔹 Fetch produk berdasarkan subscription
useEffect(() => {
if (!subscriptions) return;
const groupedSubs = groupSubscriptionsByProductName(subscriptions); const groupedSubs = groupSubscriptionsByProductName(subscriptions);
const productIds = [...new Set(subscriptions.map((s) => s.product_id))]; const productIds = [...new Set(subscriptions.map((s) => s.product_id))];
fetch( fetch("https://bot.kediritechnopark.com/webhook/store-production/products", {
"https://bot.kediritechnopark.com/webhook/store-production/products",
{
method: "POST", method: "POST",
headers: { headers: { "Content-Type": "application/json" },
"Content-Type": "application/json"
},
body: JSON.stringify({ itemsId: productIds }) body: JSON.stringify({ itemsId: productIds })
} })
)
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .then((data) => {
// Step 1: Enrich base products (without children yet)
const enrichedData = Object.values(groupedSubs) const enrichedData = Object.values(groupedSubs)
.filter((group) => data.some((p) => p.id === group.product_id)) .filter((group) => data.some((p) => p.id === group.product_id))
.map((group) => { .map((group) => {
const productData = data.find((p) => p.id == group.product_id); const productData = data.find((p) => p.id === group.product_id);
let image = productData?.image || ""; let image = productData?.image || "";
let description = productData?.description || ""; let description = productData?.description || "";
let site_url = productData?.site_url || ""; let site_url = productData?.site_url || "";
if (!image && productData?.sub_product_of) { if (!image && productData?.sub_product_of) {
const parent = data.find( const parent = data.find((p) => p.id === productData.sub_product_of);
(p) => p.id === productData.sub_product_of
);
image = parent?.image || ""; image = parent?.image || "";
description = parent?.description || ""; description = parent?.description || "";
site_url = parent?.site_url || ""; site_url = parent?.site_url || "";
@@ -124,301 +128,203 @@ const Dashboard = ({
}; };
}); });
// Step 2: Create a quick lookup table for enrichedData
const productMap = {};
enrichedData.forEach((p) => {
console.log(p)
productMap[p.name.split("%%%")[1]] = p;
});
// Step 3: Find children in API `data` and attach to parents
data
.filter((p) => p.sub_product_of) // only those with a parent
.forEach((child) => {
// ✅ Current logic — attach to the real parent
const parent = productMap[child.sub_product_of];
if (parent) {
parent.children.push(child);
}
// New logic — attach to other products with the same sub_product_of
Object.values(productMap).forEach((possibleParent) => {
if (
possibleParent.id !== child.id && // not itself
possibleParent.sub_product_of === child.sub_product_of // same parent reference
) {
possibleParent.children.push(child);
}
});
});
console.log(enrichedData)
setProducts(enrichedData); setProducts(enrichedData);
}) })
.catch((err) => console.error("Fetch error:", err)); .catch((err) => console.error("Fetch error:", err));
}, [subscriptions]); }, [subscriptions]);
const handleSettingsChange = (field, value, nested = false) => {
if (nested) {
setSettings((prev) => ({
...prev,
profile_data: { ...prev.profile_data, [field]: value }
}));
} else {
setSettings((prev) => ({ ...prev, [field]: value }));
}
};
const saveSettings = () => {
fetch(
"https://bot.kediritechnopark.com/webhook-test/user-production/data",
{
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(settings)
}
)
.then((res) => res.json())
.then(() => {
alert("Settings updated successfully!");
})
.catch((err) => alert("Error updating settings: " + err));
};
return ( return (
<div style={{ fontFamily: "Inter, system-ui, sans-serif" }}> <Container fluid className="mt-4">
{/* Tabs Navigation */} <Tabs
<div className={styles.navTabs}> activeKey={activeTab}
<button onSelect={(k) => setActiveTab(k)}
className={`${styles.floatMenuItem} ${activeTab === "products" ? styles.floatMenuItemActive : "" className="mb-3"
}`}
onClick={() => setActiveTab("products")}
> >
<span className={styles.floatMenuTitle}>Produk Saya</span> {/* Produk Saya */}
<Box size={16} className={styles.floatMenuIcon} /> <Tab eventKey="products" title="Produk Saya">
</button> <Row>
<button
className={`${styles.floatMenuItem} ${activeTab === "settings" ? styles.floatMenuItemActive : ""
}`}
onClick={() => setActiveTab("settings")}
>
<span className={styles.floatMenuTitle}>Profil Pengguna</span>
<Settings size={16} className={styles.floatMenuIcon} />
</button>
<button
className={`${styles.floatMenuItem} ${activeTab === "orders" ? styles.floatMenuItemActive : ""
}`}
onClick={() => setActiveTab("orders")}
>
<span className={styles.floatMenuTitle}>Pembelian</span>
<ShoppingCart size={16} className={styles.floatMenuIcon} />
</button>
</div>
{/* Tab Content */}
{activeTab === "products" && (
<section className={styles.Section}>
<div className={styles.coursesContainer}>
<div className={styles.coursesGrid}>
{products.map((product) => ( {products.map((product) => (
<div <Col md={4} key={product.id} className="mb-4">
key={product.name} <Card
className={`${styles.courseCard} ${hoveredCard === product.name ? styles.courseCardHover : "" className={`h-100 shadow-sm ${hoveredCard === product.name ? "border-primary" : ""}`}
}`} onMouseEnter={() => setHoveredCard(product.name)}
onMouseLeave={() => setHoveredCard(null)}
onClick={() => { onClick={() => {
setSelectedProduct(product); setSelectedProduct(product);
setShowedModal("product"); setShowedModal("product");
}} }}
onMouseEnter={() => setHoveredCard(product.name)}
onMouseLeave={() => setHoveredCard(null)}
>
<div>
<div
className={styles.courseImage}
style={{
backgroundImage: `url(${product.image})`
}}
/>
<div className={styles.courseContentTop}>
<h3 className={styles.courseTitle}>
{product.name.split("%%%")[0]}
</h3>
<p className={styles.courseDesc}>
{product.description}
</p>
</div>
</div>
<div className={styles.courseContentBottom}>
<div className={styles.coursePrice}>
<span
className={
product.price === 0
? styles.freePrice
: styles.currentPrice
}
> >
{product.image && (
<Card.Img variant="top" src={product.image} />
)}
<Card.Body>
<Card.Title>{product.name.split("%%%")[0]}</Card.Title>
<Card.Text>{product.description}</Card.Text>
</Card.Body>
<Card.Footer>
<small className="text-muted">
{product.unit_type === "duration" {product.unit_type === "duration"
? `Valid until: ${product.end_date ? `Valid until: ${
? new Date(
product.end_date product.end_date
).toLocaleDateString() ? new Date(product.end_date).toLocaleDateString()
: "N/A" : "N/A"
}` }`
: `SISA TOKEN ${product.quantity || 0}`} : `SISA TOKEN ${product.quantity || 0}`}
</span> </small>
</div> <Button
variant="primary"
<button size="sm"
className="px-4 py-2 rounded-pill text-white" className="float-end"
style={{
background:
"linear-gradient(to right, #6a59ff, #8261ee)",
border: "none"
}}
onClick={() => { onClick={() => {
setSelectedProduct(product); setSelectedProduct(product);
setShowedModal("product"); setShowedModal("product");
setWillDo('checkout'); setWillDo("checkout");
}} }}
> >
Perpanjang Perpanjang
</button> </Button>
</div> </Card.Footer>
</div> </Card>
</Col>
))} ))}
<div
className={`${styles.courseCard} ${hoveredCard === 0 ? styles.courseCardHover : "" {/* Tambah produk baru */}
}`} <Col md={4} className="mb-4">
onMouseEnter={() => setHoveredCard(0)} <Card
onMouseLeave={() => setHoveredCard(null)} className={`h-100 shadow-sm text-center align-items-center justify-content-center`}
onClick={() => navigate('/?tab=products') onClick={() => navigate("/?tab=products")}
}
> >
<div> <Card.Body>
+ Tambah produk baru</div> <h5>+ Tambah produk baru</h5>
</div> </Card.Body>
</div> </Card>
</div> </Col>
</section> </Row>
)} </Tab>
{activeTab === "settings" && ( {/* Profil Pengguna */}
<section className={styles.profileSection}> <Tab eventKey="settings" title="Profil Pengguna">
<h2 className={styles.profileHeading}>Profil</h2> <Form>
<div className={styles.sectionDivider}></div> <Row>
<Col md={6}>
<div className={styles.formGrid}> <Form.Group className="mb-3">
<div> <Form.Label>Username</Form.Label>
<label className={styles.label}>Username</label> <Form.Control
<input
className={styles.inputField}
value={settings.username} value={settings.username}
onChange={(e) => onChange={(e) =>
handleSettingsChange("username", e.target.value) handleSettingsChange("username", e.target.value)
} }
/> />
</div> </Form.Group>
<div> </Col>
<label className={styles.label}>Email</label>
<input <Col md={6}>
className={styles.inputField} <Form.Group className="mb-3">
<Form.Label>Email</Form.Label>
<Form.Control
value={settings.email} value={settings.email}
onChange={(e) => onChange={(e) =>
handleSettingsChange("email", e.target.value) handleSettingsChange("email", e.target.value)
} }
/> />
</div> </Form.Group>
<div> </Col>
<label className={styles.label}>Full Name</label> </Row>
<input
className={styles.inputField} <Row>
<Col md={6}>
<Form.Group className="mb-3">
<Form.Label>Full Name</Form.Label>
<Form.Control
value={settings.profile_data.name} value={settings.profile_data.name}
onChange={(e) => onChange={(e) =>
handleSettingsChange("name", e.target.value, true) handleSettingsChange("name", e.target.value, true)
} }
/> />
</div> </Form.Group>
<div> </Col>
<label className={styles.label}>Phone</label> <Col md={6}>
<input <Form.Group className="mb-3">
className={styles.inputField} <Form.Label>Phone</Form.Label>
<Form.Control
value={settings.profile_data.phone} value={settings.profile_data.phone}
onChange={(e) => onChange={(e) =>
handleSettingsChange("phone", e.target.value, true) handleSettingsChange("phone", e.target.value, true)
} }
/> />
</div> </Form.Group>
<div className={styles.fullWidth}> </Col>
<label className={styles.label}>Address</label> </Row>
<input
className={styles.inputField} <Form.Group className="mb-3">
<Form.Label>Address</Form.Label>
<Form.Control
value={settings.profile_data.address} value={settings.profile_data.address}
onChange={(e) => onChange={(e) =>
handleSettingsChange("address", e.target.value, true) handleSettingsChange("address", e.target.value, true)
} }
/> />
</div> </Form.Group>
<div>
<label className={styles.label}>Company</label> <Row>
<input <Col md={6}>
className={styles.inputField} <Form.Group className="mb-3">
<Form.Label>Company</Form.Label>
<Form.Control
value={settings.profile_data.company} value={settings.profile_data.company}
onChange={(e) => onChange={(e) =>
handleSettingsChange("company", e.target.value, true) handleSettingsChange("company", e.target.value, true)
} }
/> />
</div> </Form.Group>
<div> </Col>
<label className={styles.label}>Profile Image URL</label> <Col md={6}>
<input <Form.Group className="mb-3">
className={styles.inputField} <Form.Label>Profile Image URL</Form.Label>
<Form.Control
value={settings.profile_data.image} value={settings.profile_data.image}
onChange={(e) => onChange={(e) =>
handleSettingsChange("image", e.target.value, true) handleSettingsChange("image", e.target.value, true)
} }
/> />
</div> </Form.Group>
</div> </Col>
</Row>
<h2 className={styles.profileHeading}>Ganti password</h2> <Row>
<div className={styles.sectionDivider}></div> <Col md={6}>
<div className={styles.formGrid}> <Form.Group className="mb-3">
<div> <Form.Label>New Password</Form.Label>
<label className={styles.label}>New Password</label> <Form.Control
<input
className={styles.inputField}
type="password" type="password"
value={settings.password} value={settings.password}
onChange={(e) => onChange={(e) =>
handleSettingsChange("password", e.target.value) handleSettingsChange("password", e.target.value)
} }
/> />
</div> </Form.Group>
<div> </Col>
<label className={styles.label}>Re-type New Password</label> <Col md={6}>
<input <Form.Group className="mb-3">
className={styles.inputField} <Form.Label>Re-type New Password</Form.Label>
type="password" <Form.Control type="password" />
/> </Form.Group>
</div> </Col>
</div> </Row>
<button className={styles.saveButton} onClick={saveSettings}> <Button variant="success" onClick={saveSettings}>
Save Changes Save Changes
</button> </Button>
</section> </Form>
)} </Tab>
{activeTab === "orders" && ( {/* Orders */}
<section className={styles.Section}> <Tab eventKey="orders" title="Pembelian">
<h2>My Orders</h2> <h4>My Orders</h4>
<p>Orders list will be displayed here.</p> <p>Orders list will be displayed here.</p>
</section> </Tab>
)} </Tabs>
</div> </Container>
); );
}; };