ok
This commit is contained in:
@@ -34,7 +34,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
}, [existingConversation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage.getItem('session')) {
|
||||
if (!sessionStorage.getItem('session')) {
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
@@ -46,7 +46,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
const sessionId = generateUUID();
|
||||
const dateNow = new Date().toISOString();
|
||||
|
||||
localStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow }))
|
||||
sessionStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow }))
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -65,7 +65,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
}
|
||||
|
||||
const askToBot = async ({ type = 'text', content, tryCount = 0 }) => {
|
||||
const session = JSON.parse(localStorage.getItem('session'));
|
||||
const session = JSON.parse(sessionStorage.getItem('session'));
|
||||
if (!session || !session.sessionId) return;
|
||||
|
||||
let body;
|
||||
@@ -143,7 +143,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
const message = textOverride || input.trim();
|
||||
if (message === '') return;
|
||||
|
||||
const session = JSON.parse(localStorage.getItem('session'));
|
||||
const session = JSON.parse(sessionStorage.getItem('session'));
|
||||
if ((!session || !session.name || !session.phoneNumber) && messages.length > 2) {
|
||||
setIsPoppedUp(message);
|
||||
setInput('');
|
||||
@@ -331,10 +331,10 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
||||
className={styles.nextButton}
|
||||
onClick={() => {
|
||||
if (name.length > 2 && phoneNumber.length >= 10) {
|
||||
const sessionData = JSON.parse(localStorage.getItem('session')) || {};
|
||||
const sessionData = JSON.parse(sessionStorage.getItem('session')) || {};
|
||||
sessionData.name = name;
|
||||
sessionData.phoneNumber = phoneNumber;
|
||||
localStorage.setItem('session', JSON.stringify(sessionData));
|
||||
sessionStorage.setItem('session', JSON.stringify(sessionData));
|
||||
setIsPoppedUp('');
|
||||
sendMessage(isPoppedUp);
|
||||
}
|
||||
|
||||
237
src/Dashboard.js
237
src/Dashboard.js
@@ -15,13 +15,18 @@ import NotificationPrompt from './NotificationPrompt';
|
||||
const Dashboard = () => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
const chartRef = useRef(null);
|
||||
const chartInstanceRef = useRef(null);
|
||||
const weeklyChartRef = useRef(null);
|
||||
const allTimeChartRef = useRef(null);
|
||||
const weeklyChartInstanceRef = useRef(null);
|
||||
const allTimeChartInstanceRef = useRef(null);
|
||||
|
||||
const [weeklyData, setWeeklyData] = useState([]);
|
||||
const [allTimeData, setAllTimeData] = useState([]);
|
||||
|
||||
const [conversations, setConversations] = useState([]);
|
||||
const [followUps, setFollowUps] = useState([]);
|
||||
const [discussedTopics, setDiscussedTopics] = useState([]);
|
||||
const [modalContent, setModalContent] = useState(null);
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const [loading, setLoading] = useState(true); // ⬅️ Tambahkan state loading
|
||||
const [fileList, setFileList] = useState([]);
|
||||
const [selectedKeys, setSelectedKeys] = useState([]);
|
||||
@@ -177,6 +182,115 @@ const Dashboard = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Helper parse data function
|
||||
function parseGraphData(graph) {
|
||||
const prefixLabelMap = {
|
||||
WEB: 'Web App',
|
||||
TGG: 'Telegram',
|
||||
WGG: 'Whatsapp',
|
||||
IGG: 'Instagram',
|
||||
};
|
||||
const prefixColors = {
|
||||
WEB: { border: '#e2b834', background: 'rgba(226,184,52,0.6)' },
|
||||
TGG: { border: '#24A1DE', background: 'rgba(36,161,222,0.6)' },
|
||||
WGG: { border: '#25d366', background: 'rgba(37,211,102,0.6)' },
|
||||
IGG: { border: '#d62976', background: 'rgba(214,41,118,0.6)' },
|
||||
};
|
||||
|
||||
const rawDataArray = Object.entries(graph).map(([date, sesi]) => ({
|
||||
hour: date,
|
||||
sesi,
|
||||
}));
|
||||
|
||||
rawDataArray.sort((a, b) => new Date(a.hour) - new Date(b.hour));
|
||||
|
||||
const prefixes = Object.keys(prefixLabelMap);
|
||||
|
||||
// Format label: tanggal + nama bulan singkat Indonesia
|
||||
const bulanIndoSingkat = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||
const labels = rawDataArray.map(d => {
|
||||
const date = new Date(d.hour);
|
||||
const tgl = date.getDate().toString().padStart(2, '0');
|
||||
const bln = bulanIndoSingkat[date.getMonth()];
|
||||
return `${tgl} ${bln}`;
|
||||
});
|
||||
|
||||
const counts = {};
|
||||
prefixes.forEach(prefix => {
|
||||
counts[prefix] = labels.map(() => 0);
|
||||
});
|
||||
|
||||
rawDataArray.forEach(({ sesi }, index) => {
|
||||
prefixes.forEach(prefix => {
|
||||
if (typeof sesi[prefix] === 'number') {
|
||||
counts[prefix][index] = sesi[prefix];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const datasets = prefixes.map(prefix => ({
|
||||
label: prefixLabelMap[prefix],
|
||||
data: counts[prefix],
|
||||
borderColor: prefixColors[prefix].border,
|
||||
backgroundColor: prefixColors[prefix].background,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
}));
|
||||
|
||||
return { labels, datasets };
|
||||
}
|
||||
|
||||
|
||||
// Effect buat render weekly chart
|
||||
useEffect(() => {
|
||||
if (!weeklyData.labels || !weeklyChartRef.current) return;
|
||||
|
||||
if (weeklyChartInstanceRef.current) {
|
||||
weeklyChartInstanceRef.current.destroy();
|
||||
}
|
||||
|
||||
const ctx = weeklyChartRef.current.getContext('2d');
|
||||
weeklyChartInstanceRef.current = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: weeklyData.labels,
|
||||
datasets: weeklyData.datasets,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'bottom' },
|
||||
},
|
||||
scales: { y: { beginAtZero: true } },
|
||||
},
|
||||
});
|
||||
}, [weeklyData]);
|
||||
|
||||
// Effect buat render all-time chart
|
||||
useEffect(() => {
|
||||
if (!allTimeData.labels || !allTimeChartRef.current) return;
|
||||
|
||||
if (allTimeChartInstanceRef.current) {
|
||||
allTimeChartInstanceRef.current.destroy();
|
||||
}
|
||||
|
||||
const ctx = allTimeChartRef.current.getContext('2d');
|
||||
allTimeChartInstanceRef.current = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: allTimeData.labels,
|
||||
datasets: allTimeData.datasets,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'bottom' },
|
||||
},
|
||||
scales: { y: { beginAtZero: true } },
|
||||
},
|
||||
});
|
||||
}, [allTimeData]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem('token');
|
||||
@@ -207,13 +321,19 @@ const Dashboard = () => {
|
||||
setFollowUps(data[0]?.graph[0]?.json?.result?.interested_users)
|
||||
setFileList(data[0]?.files)
|
||||
setUpdateDetected(data[1]?.updateDetected)
|
||||
|
||||
|
||||
const result = data[0].graph[0].json.result;
|
||||
|
||||
setWeeklyData(parseGraphData(result.weekly_graph || {}));
|
||||
setAllTimeData(parseGraphData(result.all_time_graph || {}));
|
||||
|
||||
const graphObj = data[0]?.graph[0]?.json?.result?.graph;
|
||||
console.log(graphObj)
|
||||
const rawDataArray = Object.entries(graphObj).map(([hour, sesi]) => ({
|
||||
hour,
|
||||
sesi,
|
||||
}));
|
||||
setRawData(rawDataArray);
|
||||
|
||||
let totalSessions = new Set();
|
||||
let botMessages = 0;
|
||||
@@ -320,98 +440,6 @@ const Dashboard = () => {
|
||||
setModalContent(<DiscussedTopics topics={discussedTopics} />);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!rawData.length) return;
|
||||
|
||||
const ctx = chartRef.current?.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
if (chartInstanceRef.current) {
|
||||
chartInstanceRef.current.destroy();
|
||||
}
|
||||
|
||||
const prefixLabelMap = {
|
||||
WEB: 'Web App',
|
||||
TGG: 'Telegram',
|
||||
WGG: 'Whatsapp',
|
||||
IGG: 'Instagram',
|
||||
};
|
||||
|
||||
const prefixColors = {
|
||||
WEB: { border: '#e2b834', background: 'rgba(226,184,52,0.6)' },
|
||||
TGG: { border: '#24A1DE', background: 'rgba(36,161,222,0.6)' },
|
||||
WGG: { border: '#25d366', background: 'rgba(37,211,102,0.6)' },
|
||||
IGG: { border: '#d62976', background: 'rgba(214,41,118,0.6)' },
|
||||
};
|
||||
|
||||
const prefixes = Object.keys(prefixLabelMap);
|
||||
const parsedHours = rawData.map(d => new Date(d.hour));
|
||||
parsedHours.sort((a, b) => a - b);
|
||||
|
||||
// Extract only the date (no timezone shifting)
|
||||
const getDateStr = date => date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0');
|
||||
|
||||
|
||||
const hours = parsedHours.map((date, index) => {
|
||||
const timeStr = date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
|
||||
return index === parsedHours.length - 1 ? 'Now' : timeStr;
|
||||
});
|
||||
|
||||
const counts = {};
|
||||
prefixes.forEach(prefix => {
|
||||
counts[prefix] = hours.map(() => 0);
|
||||
});
|
||||
|
||||
rawData.forEach(({ sesi }, index) => {
|
||||
prefixes.forEach(prefix => {
|
||||
if (Array.isArray(sesi[prefix])) {
|
||||
counts[prefix][index] = sesi[prefix].length;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const datasets = prefixes.map(prefix => ({
|
||||
label: prefixLabelMap[prefix],
|
||||
data: counts[prefix],
|
||||
borderColor: prefixColors[prefix].border,
|
||||
backgroundColor: prefixColors[prefix].background,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
}));
|
||||
|
||||
chartInstanceRef.current = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: hours,
|
||||
datasets,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
boxWidth: 15
|
||||
}
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
font: {
|
||||
size: 10, // 👈 set your desired font size here
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [rawData]);
|
||||
|
||||
const handleDeleteFile = async (key) => {
|
||||
if (!window.confirm(`Yakin ingin menghapus "${key}"?`)) return;
|
||||
const token = localStorage.getItem('token');
|
||||
@@ -437,7 +465,7 @@ const Dashboard = () => {
|
||||
for (const key of selectedKeys) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://bot.kediritechnopark.com/webhook/dermalounge/files/download?key=${encodeURIComponent(key)}`,
|
||||
`https://bot.kediritechnopark.com/webhook/files/download?key=${encodeURIComponent(key)}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -479,7 +507,7 @@ const Dashboard = () => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, file.name);
|
||||
|
||||
const response = await fetch('https://bot.kediritechnopark.com/webhook/dermalounge/files/upload', {
|
||||
const response = await fetch('https://bot.kediritechnopark.com/webhook/files/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
@@ -527,7 +555,7 @@ const Dashboard = () => {
|
||||
for (const key of selectedKeys) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://bot.kediritechnopark.com/webhook/dermalounge/files/delete?key=${encodeURIComponent(key)}`,
|
||||
`https://bot.kediritechnopark.com/webhook/files/delete?key=${encodeURIComponent(key)}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
@@ -641,7 +669,16 @@ const Dashboard = () => {
|
||||
|
||||
<div className={styles.chartSection}>
|
||||
<h2 className={styles.chartTitle}>Interactions</h2>
|
||||
<canvas ref={chartRef}></canvas>
|
||||
|
||||
<section style={{ marginBottom: '40px' }}>
|
||||
<h2 className={styles.chartTitle}>Weekly Graph</h2>
|
||||
<canvas ref={weeklyChartRef}></canvas>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className={styles.chartTitle}>All Time Graph</h2>
|
||||
<canvas ref={allTimeChartRef}></canvas>
|
||||
</section>
|
||||
</div>
|
||||
<div className={styles.chartSection}>
|
||||
<h2 className={styles.chartTitle}>Update AI data</h2>
|
||||
|
||||
@@ -45,7 +45,7 @@ const FollowUps = ({ data: initialData }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Gabungkan data berdasarkan contact_info
|
||||
// Gabungkan data berdasarkan contact_info dan hilangkan note yang sama secara berurutan
|
||||
const mergedDataMap = new Map();
|
||||
|
||||
data.forEach(user => {
|
||||
@@ -61,10 +61,13 @@ const FollowUps = ({ data: initialData }) => {
|
||||
});
|
||||
} else {
|
||||
const existing = mergedDataMap.get(key);
|
||||
const lastNote = existing.notesList[existing.notesList.length - 1];
|
||||
if (!lastNote || lastNote.note !== user.notes) {
|
||||
existing.notesList.push({
|
||||
note: user.notes,
|
||||
created_at: user.created_at
|
||||
});
|
||||
}
|
||||
|
||||
// Prioritaskan status tertinggi
|
||||
existing.issuccess = existing.issuccess || user.issuccess;
|
||||
|
||||
@@ -11,7 +11,7 @@ const ProfileTab = () => {
|
||||
const [profileTemp, setProfileTemp] = useState({});
|
||||
|
||||
const licenses = [
|
||||
{ id: 1, type: "Monthly Subscription", number: "DRML-2025-AI001", validUntil: "July 31 2025" },
|
||||
{ id: 1, type: "MONTHLY SUBSCRIPTION", number: "DRML-2025-AI001", validUntil: "Aug 31 2025" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -218,9 +218,9 @@ const ProfileTab = () => {
|
||||
<div className={styles.licenseCards}>
|
||||
{licenses.map((item) => (
|
||||
<div className={styles.licenseCard} key={item.id}>
|
||||
<p><strong>{item.type}</strong></p>
|
||||
<p><strong>CURRENT PLAN</strong></p>
|
||||
<p>{item.number}</p>
|
||||
<p><strong>Free License </strong>Valid until: {item.validUntil}</p>
|
||||
<p><strong style={{fontSize: '12px'}}>{item.type} </strong>Valid until: {item.validUntil}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user