This commit is contained in:
Vassshhh
2025-08-11 16:38:35 +07:00
parent 7d3655236e
commit 82518c96aa
18 changed files with 636 additions and 417 deletions

View File

@@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react';
import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users } from 'lucide-react';
import { TrendingUp, TrendingDown, DollarSign, ShoppingCart, Users, Plus, GitBranchPlus } from 'lucide-react';
import styles from './Dashboard.module.css';
import processProducts from '../helper/processProducts';
const Dashboard = () => {
const [unitType, setUnitType] = useState('duration');
const [durationUnit, setDurationUnit] = useState('day');
/**
* 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);
@@ -14,24 +17,10 @@ const Dashboard = () => {
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'
},
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 },
@@ -40,48 +29,7 @@ const Dashboard = () => {
{ date: '26/06', items: 900, revenue: 450 },
{ date: '27/06', items: 550, revenue: 200 },
],
latestTransactions: [
{
id: 1,
name: 'Samantha William',
amount: 250875,
date: 'May 22, 2025',
status: 'confirmed',
avatar: 'SW'
},
{
id: 2,
name: 'Kevin Anderson',
amount: 350620,
date: 'May 22, 2025',
status: 'waiting payment',
avatar: 'KA'
},
{
id: 3,
name: 'Angela Samantha',
amount: 870563,
date: 'May 22, 2025',
status: 'confirmed',
avatar: 'AS'
},
{
id: 4,
name: 'Michael Smith',
amount: 653975,
date: 'May 22, 2025',
status: 'payment expired',
avatar: 'MS'
},
{
id: 5,
name: 'Jonathan Sebastian',
amount: 950000,
date: 'May 22, 2025',
status: 'confirmed',
avatar: 'JS'
}
]
latestTransactions: []
});
useEffect(() => {
@@ -91,7 +39,7 @@ const Dashboard = () => {
const token = match[2];
try {
const res = await fetch('https://bot.kediritechnopark.com/webhook/store-dev/get-products', {
const res = await fetch('https://bot.kediritechnopark.com/webhook/store-production/get-products', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -99,16 +47,15 @@ const Dashboard = () => {
},
});
const result = await res.json(); // hasil berupa array produk
const products = result || [];
const result = await res.json();
const productsArr = result || [];
// Ambil distinct `type` dan `group` manual
const types = [...new Set(products.map(p => p.type).filter(Boolean))];
const groups = [...new Set(products.map(p => p.group).filter(Boolean))];
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(products));
setProducts(processProducts(productsArr));
} catch (err) {
console.error('Gagal ambil produk:', err);
}
@@ -117,47 +64,6 @@ const Dashboard = () => {
fetchDistinctOptions();
}, []);
const sendDataToN8N = async (webhookUrl, data) => {
const match = document.cookie.match(new RegExp('(^| )token=([^;]+)'));
if (match) {
const token = match[2];
const payload = {
...data,
duration: data.unit_type === 'token' ? null : data.duration,
quantity: data.unit_type === 'duration' ? null : data.quantity,
};
if (!token) {
alert('Token tidak ditemukan. Silakan login kembali.');
return;
}
try {
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(payload),
});
if (response.ok) {
alert('Dorm berhasil ditambahkan!');
} else {
const errorText = await response.text();
console.error('Response Error:', errorText);
alert('Gagal mengirim data: ' + response.status);
}
} catch (error) {
console.error('Error sending data to n8n:', error);
alert('Terjadi kesalahan saat mengirim data.');
}
}
};
const formatCurrency = (amount) => new Intl.NumberFormat('id-ID').format(amount);
const getStatusClass = (status) => {
@@ -221,202 +127,63 @@ const Dashboard = () => {
</div>
<div className={styles.chartsGrid}>
{/* Chart and Transactions UI as before */}
{/* Tempatkan <BarChart data={dashboardData.chartData} /> jika mau ditampilkan */}
</div>
{/* <div className={styles.chartCard}>
<div className={styles.transactionsHeader}>
<h3 className={styles.transactionsTitle}>Latest Transactions</h3>
<a href="#" className={styles.seeAllLink}>see all</a>
</div>
{/* Products List */}
<div className={styles.chartCard}>
<div className={styles.transactionsHeader}>
<h3 className={styles.transactionsTitle}>Products</h3>
<div className={styles.transactionsList}>
{products.map((transaction) => (
<div key={transaction.id} className={styles.transactionItem}>
<div className={styles.transactionLeft}>
<div className={styles.transactionInfo}>
<h4>{transaction.name}</h4>
<p>on {transaction.date}</p>
</div>
</div>
<div className={styles.transactionRight}>
<span className={styles.transactionAmount}>
IDR {formatCurrency(transaction.amount)}
</span>
<div className={`${styles.statusIndicator} ${getStatusClass(transaction.status)}`}></div>
<span className={styles.transactionStatus}>
{transaction.status}
</span>
</div>
</div>
))}
</div>
</div> */}
<div className={styles.chartCard}>
<div className={styles.transactionsHeader}>
<h3 className={styles.transactionsTitle}>Products</h3>
</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>- {child.name}</p>
))}
</div>
</div>
<div className={styles.transactionRight}>
<span className={styles.transactionAmount}>
IDR {formatCurrency(product.amount)}
</span>
<div className={`${styles.statusIndicator} ${getStatusClass(product.status)}`}></div>
<span className={styles.transactionStatus}>
{product.status}
</span>
</div>
</div>
))}
</div>
{/* 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.chartCard} style={{ marginTop: '2rem' }}>
<h3 className={styles.transactionsTitle}>Tambah Produk Baru</h3>
<form
onSubmit={(e) => {
e.preventDefault();
const form = e.target;
const isToken = unitType === 'token';
const durationValue = form.duration_value?.value;
const quantityValue = form.duration_quantity?.value;
const dormData = {
name: form.name.value,
type: selectedType,
image: form.image.value,
description: form.description.value,
price: parseInt(form.price.value, 10),
currency: 'IDR',
duration: isToken ? null : { [durationUnit]: parseInt(durationValue, 10) },
quantity: isToken ? parseInt(quantityValue, 10) : null,
unit_type: unitType,
sub_product_of: null,
is_visible: isVisible,
group: selectedGroup,
site_url: form.site_url.value || null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
<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>
sendDataToN8N('https://bot.kediritechnopark.com/webhook/store-dev/add-product', dormData);
}}
className={styles.form}
>
<div className={styles.formGroup}>
<label>Nama Produk</label>
<input type="text" name="name" required />
</div>
<div className={styles.formGroup}>
<label>Deskripsi</label>
<textarea name="description" rows={3} required />
</div>
<div className={styles.formGroup}>
<label>Harga</label>
<input type="number" name="price" required />
</div>
<div className={styles.formGroup}>
<label>Jenis Unit</label>
<select
name="unit_type"
value={unitType}
onChange={(e) => setUnitType(e.target.value)}
required
>
<option value="duration">Durasi</option>
<option value="token">Token</option>
</select>
</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>
{unitType === 'token' ? (
<div className={styles.formGroup}>
<label>Jumlah Token</label>
<input type="number" name="duration_quantity" required min="1" />
</div>
) : (
<div className={styles.formGroup}>
<label>Durasi</label>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<input type="number" name="duration_value" min="1" required />
<select name="duration_unit" value={durationUnit} onChange={(e) => setDurationUnit(e.target.value)} required>
<option value="day">Hari</option>
<option value="week">Minggu</option>
<option value="month">Bulan</option>
</select>
{/* 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 className={styles.formGroup}>
<label>URL Gambar</label>
<input type="text" name="image" />
</div>
<div className={styles.formGroup}>
<label>Site URL (opsional)</label>
<input type="text" name="site_url" />
</div>
<div className={styles.formGroup}>
<label>Tipe Produk</label>
<input
type="text"
name="type"
value={selectedType || ''}
onChange={(e) => setSelectedType(e.target.value)}
required
/>
<div className={styles.suggestionContainer}>
{availableTypes.map((type) => (
<button
key={type}
type="button"
className={styles.suggestionButton}
onClick={() => setSelectedType(type)}
>
{type}
</button>
))}
</div>
</div>
<div className={styles.formGroup}>
<label>Group</label>
<input
type="text"
name="group"
value={selectedGroup || ''}
onChange={(e) => setSelectedGroup(e.target.value)}
/>
<div className={styles.suggestionContainer}>
{availableGroups.map((group) => (
<button
key={group}
type="button"
className={styles.suggestionButton}
onClick={() => setSelectedGroup(group)}
>
{group}
</button>
))}
</div>
</div>
<button type="submit" className={styles.submitButton}>Buat Produk</button>
</form>
))}
</div>
</div>
{/* Bagian form create yang lama sudah DIPINDAH ke halaman/komponen baru */}
</div>
);
};