This commit is contained in:
Vassshhh
2025-08-06 23:04:58 +07:00
parent 262dce053f
commit 7abdf68372
4 changed files with 154 additions and 114 deletions

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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);
existing.notesList.push({
note: user.notes,
created_at: user.created_at
});
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;

View File

@@ -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>