ok
This commit is contained in:
19
src/App.js
19
src/App.js
@@ -20,11 +20,7 @@ import ProductsPage from './components/pages/ProductsPage';
|
|||||||
import processProducts from './helper/processProducts';
|
import processProducts from './helper/processProducts';
|
||||||
|
|
||||||
function HomePage({
|
function HomePage({
|
||||||
hoveredCard,
|
|
||||||
setHoveredCard,
|
|
||||||
selectedProduct,
|
|
||||||
setSelectedProduct,
|
setSelectedProduct,
|
||||||
showedModal,
|
|
||||||
setShowedModal,
|
setShowedModal,
|
||||||
productSectionRef,
|
productSectionRef,
|
||||||
courseSectionRef,
|
courseSectionRef,
|
||||||
@@ -38,16 +34,12 @@ function HomePage({
|
|||||||
<ServicesSection />
|
<ServicesSection />
|
||||||
<ProductSection
|
<ProductSection
|
||||||
productSectionRef={productSectionRef}
|
productSectionRef={productSectionRef}
|
||||||
hoveredCard={hoveredCard}
|
|
||||||
setHoveredCard={setHoveredCard}
|
|
||||||
setSelectedProduct={setSelectedProduct}
|
setSelectedProduct={setSelectedProduct}
|
||||||
setShowedModal={setShowedModal}
|
setShowedModal={setShowedModal}
|
||||||
setWillDo={setWillDo}
|
setWillDo={setWillDo}
|
||||||
/>
|
/>
|
||||||
<AcademySection
|
<AcademySection
|
||||||
courseSectionRef={courseSectionRef}
|
courseSectionRef={courseSectionRef}
|
||||||
hoveredCard={hoveredCard}
|
|
||||||
setHoveredCard={setHoveredCard}
|
|
||||||
setSelectedProduct={setSelectedProduct}
|
setSelectedProduct={setSelectedProduct}
|
||||||
setShowedModal={setShowedModal}
|
setShowedModal={setShowedModal}
|
||||||
setWillDo={setWillDo}
|
setWillDo={setWillDo}
|
||||||
@@ -76,7 +68,6 @@ function parseJwt(token) {
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [hoveredCard, setHoveredCard] = useState(null);
|
|
||||||
const [subscriptions, setSubscriptions] = useState(null);
|
const [subscriptions, setSubscriptions] = useState(null);
|
||||||
const [selectedProduct, setSelectedProduct] = useState({});
|
const [selectedProduct, setSelectedProduct] = useState({});
|
||||||
const [showedModal, setShowedModal] = useState(null);
|
const [showedModal, setShowedModal] = useState(null);
|
||||||
@@ -303,11 +294,7 @@ function App() {
|
|||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
<HomePage
|
<HomePage
|
||||||
hoveredCard={hoveredCard}
|
|
||||||
setHoveredCard={setHoveredCard}
|
|
||||||
selectedProduct={selectedProduct}
|
|
||||||
setSelectedProduct={setSelectedProduct}
|
setSelectedProduct={setSelectedProduct}
|
||||||
showedModal={showedModal}
|
|
||||||
setShowedModal={setShowedModal}
|
setShowedModal={setShowedModal}
|
||||||
productSectionRef={productSectionRef}
|
productSectionRef={productSectionRef}
|
||||||
courseSectionRef={courseSectionRef}
|
courseSectionRef={courseSectionRef}
|
||||||
@@ -315,9 +302,11 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/products" element={<ProductsPage subscriptions={subscriptions} />} />
|
<Route path="/dashboard" element={<ProductsPage
|
||||||
|
setShowedModal={setShowedModal}
|
||||||
|
setSelectedProduct={setSelectedProduct} subscriptions={subscriptions} />} />
|
||||||
<Route
|
<Route
|
||||||
path="/dashboard"
|
path="/admin"
|
||||||
element={
|
element={
|
||||||
<Dashboard
|
<Dashboard
|
||||||
setShowedModal={(e, productId) => {
|
setShowedModal={(e, productId) => {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { Container } from 'react-bootstrap';
|
import { Container } from 'react-bootstrap';
|
||||||
import styles from './Styles.module.css';
|
import styles from './Styles.module.css';
|
||||||
|
|
||||||
const AcademySection = ({hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, courseSectionRef, setWillDo}) => {
|
const AcademySection = ({setSelectedProduct, setShowedModal, courseSectionRef, setWillDo}) => {
|
||||||
const [products, setProducts] = useState([]);
|
const [products, setProducts] = useState([]);
|
||||||
|
const [hoveredCard, setHoveredCard] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
fetch('https://bot.kediritechnopark.com/webhook/store-production/products', {
|
||||||
|
|||||||
191
src/components/Dashboard copy.js
Normal file
191
src/components/Dashboard copy.js
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users, Plus, GitBranchPlus } from 'lucide-react';
|
||||||
|
import styles from './Dashboard.module.css';
|
||||||
|
import processProducts from '../helper/processProducts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props:
|
||||||
|
* - setShowedModal: (modalName: string, productId?: string|number) => void
|
||||||
|
*/
|
||||||
|
const Dashboard = ({ setShowedModal }) => {
|
||||||
|
const [unitType, setUnitType] = useState('duration'); // kept for potential reuse
|
||||||
|
const [durationUnit, setDurationUnit] = useState('days'); // kept for potential reuse
|
||||||
|
const [availableTypes, setAvailableTypes] = useState([]);
|
||||||
|
const [availableGroups, setAvailableGroups] = useState([]);
|
||||||
|
const [selectedType, setSelectedType] = useState(null);
|
||||||
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||||
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
|
||||||
|
const [dashboardData, setDashboardData] = useState({
|
||||||
|
totalRevenue: { amount: 10215845, currency: 'IDR', change: 33.87, period: '22 - 29 May 2025' },
|
||||||
|
totalItemsSold: { amount: 128980, change: -33.87, period: '22 - 29 May 2025' },
|
||||||
|
totalVisitors: { amount: 2905897, change: 33.87, period: '22 - 29 May 2025' },
|
||||||
|
chartData: [
|
||||||
|
{ date: '22/06', items: 200, revenue: 800 },
|
||||||
|
{ date: '23/06', items: 750, revenue: 450 },
|
||||||
|
{ date: '24/06', items: 550, revenue: 200 },
|
||||||
|
{ date: '25/06', items: 300, revenue: 350 },
|
||||||
|
{ date: '26/06', items: 900, revenue: 450 },
|
||||||
|
{ date: '27/06', items: 550, revenue: 200 },
|
||||||
|
],
|
||||||
|
latestTransactions: []
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDistinctOptions = async () => {
|
||||||
|
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
|
||||||
|
if (!match) return;
|
||||||
|
const token = match[2];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://bot.kediritechnopark.com/webhook/store-production/get-products', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
const productsArr = result || [];
|
||||||
|
|
||||||
|
const types = [...new Set(productsArr.map(p => p.type).filter(Boolean))];
|
||||||
|
const groups = [...new Set(productsArr.map(p => p.group).filter(Boolean))];
|
||||||
|
|
||||||
|
setAvailableTypes(types);
|
||||||
|
setAvailableGroups(groups);
|
||||||
|
setProducts(processProducts(productsArr));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Gagal ambil produk:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDistinctOptions();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const formatCurrency = (amount) => new Intl.NumberFormat('id-ID').format(amount);
|
||||||
|
|
||||||
|
const getStatusClass = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'confirmed': return styles.statusConfirmed;
|
||||||
|
case 'waiting payment': return styles.statusWaiting;
|
||||||
|
case 'payment expired': return styles.statusExpired;
|
||||||
|
default: return styles.statusConfirmed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatCard = ({ title, value, currency, change, period, icon: Icon, isNegative }) => (
|
||||||
|
<div className={styles.statCard}>
|
||||||
|
<div className={styles.statCardHeader}>
|
||||||
|
<h3 className={styles.statCardTitle}>{title}</h3>
|
||||||
|
<Icon className={styles.statCardIcon} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.statCardValue}>
|
||||||
|
{currency && `${currency} `}{formatCurrency(value)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.statCardFooter}>
|
||||||
|
<div className={styles.statCardChange}>
|
||||||
|
{isNegative ? (
|
||||||
|
<TrendingDown className={`${styles.trendIcon} ${styles.trendDown}`} />
|
||||||
|
) : (
|
||||||
|
<TrendingUp className={`${styles.trendIcon} ${styles.trendUp}`} />
|
||||||
|
)}
|
||||||
|
<span className={`${styles.changeText} ${isNegative ? styles.changeTextNegative : styles.changeTextPositive}`}>
|
||||||
|
{Math.abs(change)}%
|
||||||
|
</span>
|
||||||
|
<span className={styles.fromLastWeek}>from last week</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.statCardPeriod}>{period}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const BarChart = ({ data }) => {
|
||||||
|
const maxValue = Math.max(...data.map(item => Math.max(item.items, item.revenue)));
|
||||||
|
return (
|
||||||
|
<div className={styles.barChart}>
|
||||||
|
{data.map((item, index) => (
|
||||||
|
<div key={index} className={styles.barGroup}>
|
||||||
|
<div className={styles.barContainer}>
|
||||||
|
<div className={`${styles.bar} ${styles.barItems}`} style={{ height: `${(item.items / maxValue) * 200}px` }} />
|
||||||
|
<div className={`${styles.bar} ${styles.barRevenue}`} style={{ height: `${(item.revenue / maxValue) * 200}px` }} />
|
||||||
|
</div>
|
||||||
|
<span className={styles.barLabel}>{item.date}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.statsGrid}>
|
||||||
|
<StatCard title="Total Revenue" value={dashboardData.totalRevenue.amount} currency="IDR" change={dashboardData.totalRevenue.change} period={dashboardData.totalRevenue.period} icon={DollarSign} isNegative={false} />
|
||||||
|
<StatCard title="Total Items Sold" value={dashboardData.totalItemsSold.amount} change={dashboardData.totalItemsSold.change} period={dashboardData.totalItemsSold.period} icon={ShoppingCart} isNegative={true} />
|
||||||
|
<StatCard title="Total Visitor" value={dashboardData.totalVisitors.amount} change={dashboardData.totalVisitors.change} period={dashboardData.totalVisitors.period} icon={Users} isNegative={false} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.chartsGrid}>
|
||||||
|
{/* Tempatkan <BarChart data={dashboardData.chartData} /> jika mau ditampilkan */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Products List */}
|
||||||
|
<div className={styles.chartCard}>
|
||||||
|
<div className={styles.transactionsHeader}>
|
||||||
|
<h3 className={styles.transactionsTitle}>Products</h3>
|
||||||
|
|
||||||
|
{/* Tombol "Buat Item" → buka modal create */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.primaryButton}
|
||||||
|
onClick={() => setShowedModal && setShowedModal('create-item')}
|
||||||
|
title="Buat produk baru"
|
||||||
|
>
|
||||||
|
<Plus size={16} style={{ marginRight: 6 }} />
|
||||||
|
Buat Item
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.transactionsList}>
|
||||||
|
{products.map((product) => (
|
||||||
|
<div key={product.id} className={styles.transactionItem}>
|
||||||
|
<div className={styles.transactionLeft}>
|
||||||
|
<div className={styles.transactionInfo}>
|
||||||
|
<h4>{product.name}</h4>
|
||||||
|
{product.children && product.children.map((child) => (
|
||||||
|
<p key={child.id}>- {child.name}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.transactionRight}>
|
||||||
|
<span className={styles.transactionAmount}>
|
||||||
|
IDR {formatCurrency(product.price)}
|
||||||
|
</span>
|
||||||
|
<div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div>
|
||||||
|
<span className={styles.transactionStatus}>{product.status}</span>
|
||||||
|
|
||||||
|
{/* Tombol "Add Child" → buka modal create dengan parent product_id */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.secondaryButton}
|
||||||
|
onClick={() => setShowedModal('create-item', product.id)}
|
||||||
|
title="Tambah sub-produk"
|
||||||
|
style={{ marginLeft: '0.75rem' }}
|
||||||
|
>
|
||||||
|
<GitBranchPlus size={16} style={{ marginRight: 6 }} />
|
||||||
|
Add Child
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bagian form create yang lama sudah DIPINDAH ke halaman/komponen baru */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users, Plus, GitBranchPlus } from 'lucide-react';
|
|
||||||
import styles from './Dashboard.module.css';
|
|
||||||
import processProducts from '../helper/processProducts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props:
|
|
||||||
* - setShowedModal: (modalName: string, productId?: string|number) => void
|
|
||||||
*/
|
|
||||||
const Dashboard = ({ setShowedModal }) => {
|
|
||||||
const [unitType, setUnitType] = useState('duration'); // kept for potential reuse
|
|
||||||
const [durationUnit, setDurationUnit] = useState('days'); // kept for potential reuse
|
|
||||||
const [availableTypes, setAvailableTypes] = useState([]);
|
|
||||||
const [availableGroups, setAvailableGroups] = useState([]);
|
|
||||||
const [selectedType, setSelectedType] = useState(null);
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
||||||
const [isVisible, setIsVisible] = useState(true);
|
|
||||||
const [products, setProducts] = useState([]);
|
|
||||||
|
|
||||||
const [dashboardData, setDashboardData] = useState({
|
|
||||||
totalRevenue: { amount: 10215845, currency: 'IDR', change: 33.87, period: '22 - 29 May 2025' },
|
|
||||||
totalItemsSold: { amount: 128980, change: -33.87, period: '22 - 29 May 2025' },
|
|
||||||
totalVisitors: { amount: 2905897, change: 33.87, period: '22 - 29 May 2025' },
|
|
||||||
chartData: [
|
|
||||||
{ date: '22/06', items: 200, revenue: 800 },
|
|
||||||
{ date: '23/06', items: 750, revenue: 450 },
|
|
||||||
{ date: '24/06', items: 550, revenue: 200 },
|
|
||||||
{ date: '25/06', items: 300, revenue: 350 },
|
|
||||||
{ date: '26/06', items: 900, revenue: 450 },
|
|
||||||
{ date: '27/06', items: 550, revenue: 200 },
|
|
||||||
],
|
|
||||||
latestTransactions: []
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchDistinctOptions = async () => {
|
|
||||||
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
|
|
||||||
if (!match) return;
|
|
||||||
const token = match[2];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch('https://bot.kediritechnopark.com/webhook/store-production/get-products', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await res.json();
|
|
||||||
const productsArr = result || [];
|
|
||||||
|
|
||||||
const types = [...new Set(productsArr.map(p => p.type).filter(Boolean))];
|
|
||||||
const groups = [...new Set(productsArr.map(p => p.group).filter(Boolean))];
|
|
||||||
|
|
||||||
setAvailableTypes(types);
|
|
||||||
setAvailableGroups(groups);
|
|
||||||
setProducts(processProducts(productsArr));
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Gagal ambil produk:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchDistinctOptions();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const formatCurrency = (amount) => new Intl.NumberFormat('id-ID').format(amount);
|
|
||||||
|
|
||||||
const getStatusClass = (status) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'confirmed': return styles.statusConfirmed;
|
|
||||||
case 'waiting payment': return styles.statusWaiting;
|
|
||||||
case 'payment expired': return styles.statusExpired;
|
|
||||||
default: return styles.statusConfirmed;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatCard = ({ title, value, currency, change, period, icon: Icon, isNegative }) => (
|
|
||||||
<div className={styles.statCard}>
|
|
||||||
<div className={styles.statCardHeader}>
|
|
||||||
<h3 className={styles.statCardTitle}>{title}</h3>
|
|
||||||
<Icon className={styles.statCardIcon} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.statCardValue}>
|
|
||||||
{currency && `${currency} `}{formatCurrency(value)}
|
|
||||||
</div>
|
|
||||||
<div className={styles.statCardFooter}>
|
|
||||||
<div className={styles.statCardChange}>
|
|
||||||
{isNegative ? (
|
|
||||||
<TrendingDown className={`${styles.trendIcon} ${styles.trendDown}`} />
|
|
||||||
) : (
|
|
||||||
<TrendingUp className={`${styles.trendIcon} ${styles.trendUp}`} />
|
|
||||||
)}
|
|
||||||
<span className={`${styles.changeText} ${isNegative ? styles.changeTextNegative : styles.changeTextPositive}`}>
|
|
||||||
{Math.abs(change)}%
|
|
||||||
</span>
|
|
||||||
<span className={styles.fromLastWeek}>from last week</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.statCardPeriod}>{period}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const BarChart = ({ data }) => {
|
|
||||||
const maxValue = Math.max(...data.map(item => Math.max(item.items, item.revenue)));
|
|
||||||
return (
|
|
||||||
<div className={styles.barChart}>
|
|
||||||
{data.map((item, index) => (
|
|
||||||
<div key={index} className={styles.barGroup}>
|
|
||||||
<div className={styles.barContainer}>
|
|
||||||
<div className={`${styles.bar} ${styles.barItems}`} style={{ height: `${(item.items / maxValue) * 200}px` }} />
|
|
||||||
<div className={`${styles.bar} ${styles.barRevenue}`} style={{ height: `${(item.revenue / maxValue) * 200}px` }} />
|
|
||||||
</div>
|
|
||||||
<span className={styles.barLabel}>{item.date}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.statsGrid}>
|
|
||||||
<StatCard title="Total Revenue" value={dashboardData.totalRevenue.amount} currency="IDR" change={dashboardData.totalRevenue.change} period={dashboardData.totalRevenue.period} icon={DollarSign} isNegative={false} />
|
|
||||||
<StatCard title="Total Items Sold" value={dashboardData.totalItemsSold.amount} change={dashboardData.totalItemsSold.change} period={dashboardData.totalItemsSold.period} icon={ShoppingCart} isNegative={true} />
|
|
||||||
<StatCard title="Total Visitor" value={dashboardData.totalVisitors.amount} change={dashboardData.totalVisitors.change} period={dashboardData.totalVisitors.period} icon={Users} isNegative={false} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.chartsGrid}>
|
|
||||||
{/* Tempatkan <BarChart data={dashboardData.chartData} /> jika mau ditampilkan */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Products List */}
|
|
||||||
<div className={styles.chartCard}>
|
|
||||||
<div className={styles.transactionsHeader}>
|
|
||||||
<h3 className={styles.transactionsTitle}>Products</h3>
|
|
||||||
|
|
||||||
{/* Tombol "Buat Item" → buka modal create */}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={styles.primaryButton}
|
|
||||||
onClick={() => setShowedModal && setShowedModal('create-item')}
|
|
||||||
title="Buat produk baru"
|
|
||||||
>
|
|
||||||
<Plus size={16} style={{ marginRight: 6 }} />
|
|
||||||
Buat Item
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.transactionsList}>
|
|
||||||
{products.map((product) => (
|
|
||||||
<div key={product.id} className={styles.transactionItem}>
|
|
||||||
<div className={styles.transactionLeft}>
|
|
||||||
<div className={styles.transactionInfo}>
|
|
||||||
<h4>{product.name}</h4>
|
|
||||||
{product.children && product.children.map((child) => (
|
|
||||||
<p key={child.id}>- {child.name}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.transactionRight}>
|
|
||||||
<span className={styles.transactionAmount}>
|
|
||||||
IDR {formatCurrency(product.price)}
|
|
||||||
</span>
|
|
||||||
<div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div>
|
|
||||||
<span className={styles.transactionStatus}>{product.status}</span>
|
|
||||||
|
|
||||||
{/* Tombol "Add Child" → buka modal create dengan parent product_id */}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={styles.secondaryButton}
|
|
||||||
onClick={() => setShowedModal('create-item', product.id)}
|
|
||||||
title="Tambah sub-produk"
|
|
||||||
style={{ marginLeft: '0.75rem' }}
|
|
||||||
>
|
|
||||||
<GitBranchPlus size={16} style={{ marginRight: 6 }} />
|
|
||||||
Add Child
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bagian form create yang lama sudah DIPINDAH ke halaman/komponen baru */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Dashboard;
|
|
||||||
|
|||||||
@@ -21,28 +21,41 @@ const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal, han
|
|||||||
>
|
>
|
||||||
HOME
|
HOME
|
||||||
</a>
|
</a>
|
||||||
|
{username &&
|
||||||
<a
|
<a
|
||||||
className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
|
className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
|
||||||
onMouseEnter={() => setHoveredNav(3)}
|
onMouseEnter={() => setHoveredNav(3)}
|
||||||
onMouseLeave={() => setHoveredNav(null)}
|
onMouseLeave={() => setHoveredNav(null)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!username) scrollToProduct();
|
navigate('/dashboard');
|
||||||
else navigate('/products');
|
}}>
|
||||||
|
DASHBOARD
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
{!username &&
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
className={`${styles.navLink} ${hoveredNav === 3 ? styles.navLinkHover : ''}`}
|
||||||
|
onMouseEnter={() => setHoveredNav(3)}
|
||||||
|
onMouseLeave={() => setHoveredNav(null)}
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/products');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{username ? 'MY PRODUCTS' : 'PRODUCTS'}
|
PRODUCTS
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className={`${styles.navLink} ${hoveredNav === 4 ? styles.navLinkHover : ''}`}
|
className={`${styles.navLink} ${hoveredNav === 4 ? styles.navLinkHover : ''}`}
|
||||||
onMouseEnter={() => setHoveredNav(4)}
|
onMouseEnter={() => setHoveredNav(4)}
|
||||||
onMouseLeave={() => setHoveredNav(null)}
|
onMouseLeave={() => setHoveredNav(null)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!username) scrollToCourse();
|
scrollToCourse();
|
||||||
else window.location.href = 'https://academy.kediritechnopark.com'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{username ? 'MY ACADEMY' : 'ACADEMY'}
|
ACADEMY
|
||||||
</a>
|
</a>
|
||||||
|
</>
|
||||||
|
}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Burger Menu Button */}
|
{/* Burger Menu Button */}
|
||||||
@@ -58,9 +71,9 @@ const Header = ({ username, scrollToProduct, scrollToCourse, setShowedModal, han
|
|||||||
<div className={styles.username}>{username}</div>
|
<div className={styles.username}>{username}</div>
|
||||||
|
|
||||||
<button className={styles.logoutButton} onClick={() => {
|
<button className={styles.logoutButton} onClick={() => {
|
||||||
navigate('/products');
|
navigate('/dashboard');
|
||||||
}}>
|
}}>
|
||||||
MY PRODUCTS
|
DASHBOARD
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className={styles.logoutButton} onClick={() => {
|
<button className={styles.logoutButton} onClick={() => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const LoginRegister = ({setShowedModal}) => {
|
const LoginRegister = ({setShowedModal}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [tab, setTab] = useState('login'); // 'login' or 'register'
|
const [tab, setTab] = useState('login'); // 'login' or 'register'
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
@@ -103,6 +105,7 @@ const LoginRegister = ({setShowedModal}) => {
|
|||||||
window.history.replaceState({}, '', newUrl);
|
window.history.replaceState({}, '', newUrl);
|
||||||
setShowedModal('product');
|
setShowedModal('product');
|
||||||
} else {
|
} else {
|
||||||
|
navigate('/dashboard');
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import styles from './Styles.module.css';
|
|||||||
import processProducts from '../helper/processProducts';
|
import processProducts from '../helper/processProducts';
|
||||||
|
|
||||||
|
|
||||||
const ProductSection = ({ hoveredCard, setHoveredCard, setSelectedProduct, setShowedModal, productSectionRef, setWillDo }) => {
|
const ProductSection = ({ setSelectedProduct, setShowedModal, productSectionRef, setWillDo }) => {
|
||||||
const [products, setProducts] = useState([]);
|
const [products, setProducts] = useState([]);
|
||||||
|
const [hoveredCard, setHoveredCard] = useState(null);
|
||||||
// Define this function outside useEffect so it can be called anywhere
|
// Define this function outside useEffect so it can be called anywhere
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ useEffect(() => {
|
|||||||
: `Rp ${product.price.toLocaleString('id-ID')}`}
|
: `Rp ${product.price.toLocaleString('id-ID')}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button className="px-4 py-2 rounded-pill text-white" style={{ background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }}
|
<button className="px-4 py-2 rounded-pill text-white" style={{ fontSize: '0.8rem', background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedProduct(product);
|
setSelectedProduct(product);
|
||||||
setShowedModal('product');
|
setShowedModal('product');
|
||||||
|
|||||||
@@ -304,7 +304,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.currentPrice {
|
.currentPrice {
|
||||||
font-size: 1.2rem;
|
font-size: 0.9rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2563eb;
|
color: #2563eb;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ import ProductDetailPage from '../ProductDetailPage';
|
|||||||
import Login from '../Login';
|
import Login from '../Login';
|
||||||
import styles from '../Styles.module.css';
|
import styles from '../Styles.module.css';
|
||||||
|
|
||||||
const CoursePage = ({ subscriptions }) => {
|
const CoursePage = ({ subscriptions, setSelectedProduct, setShowedModal}) => {
|
||||||
const [postLoginAction, setPostLoginAction] = useState(null);
|
|
||||||
const [selectedProduct, setSelectedProduct] = useState({});
|
|
||||||
const [hoveredCard, setHoveredCard] = useState(null);
|
const [hoveredCard, setHoveredCard] = useState(null);
|
||||||
const [showedModal, setShowedModal] = useState(null);
|
|
||||||
const [products, setProducts] = useState([]);
|
const [products, setProducts] = useState([]);
|
||||||
|
|
||||||
// Buka modal otomatis berdasarkan query
|
// Buka modal otomatis berdasarkan query
|
||||||
@@ -156,50 +153,18 @@ const CoursePage = ({ subscriptions }) => {
|
|||||||
: `SISA TOKEN ${product.quantity || 0}`}
|
: `SISA TOKEN ${product.quantity || 0}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button className="px-4 py-2 rounded-pill text-white" style={{ background: 'linear-gradient(to right, #6a59ff, #8261ee)', border: 'none' }}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedProduct(product);
|
||||||
|
setShowedModal('product');
|
||||||
|
}}>Perpanjang</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Features Section */}
|
|
||||||
{/* ... tidak berubah ... */}
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
{/* ... tidak berubah ... */}
|
|
||||||
|
|
||||||
{/* Unified Modal */}
|
|
||||||
{showedModal && (
|
|
||||||
<div
|
|
||||||
className={styles.modal}
|
|
||||||
onClick={() => {
|
|
||||||
setShowedModal(null);
|
|
||||||
setSelectedProduct({});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={styles.modalBody} onClick={(e) => e.stopPropagation()}>
|
|
||||||
{showedModal === 'product' && (
|
|
||||||
<div>
|
|
||||||
<ProductDetailPage
|
|
||||||
setPostLoginAction={setPostLoginAction}
|
|
||||||
setShowedModal={setShowedModal}
|
|
||||||
product={selectedProduct}
|
|
||||||
subscriptions={subscriptions}
|
|
||||||
onClose={() => {
|
|
||||||
setShowedModal(null);
|
|
||||||
setSelectedProduct({});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{showedModal === 'login' && (
|
|
||||||
<Login postLoginAction={postLoginAction} setPostLoginAction={setPostLoginAction} onClose={() => setShowedModal(null)} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user