ok
This commit is contained in:
@@ -34,7 +34,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
}, [existingConversation]);
|
}, [existingConversation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!localStorage.getItem('session')) {
|
if (!sessionStorage.getItem('session')) {
|
||||||
function generateUUID() {
|
function generateUUID() {
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
const r = Math.random() * 16 | 0;
|
const r = Math.random() * 16 | 0;
|
||||||
@@ -46,7 +46,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
const sessionId = generateUUID();
|
const sessionId = generateUUID();
|
||||||
const dateNow = new Date().toISOString();
|
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 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;
|
if (!session || !session.sessionId) return;
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
@@ -143,7 +143,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
const message = textOverride || input.trim();
|
const message = textOverride || input.trim();
|
||||||
if (message === '') return;
|
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) {
|
if ((!session || !session.name || !session.phoneNumber) && messages.length > 2) {
|
||||||
setIsPoppedUp(message);
|
setIsPoppedUp(message);
|
||||||
setInput('');
|
setInput('');
|
||||||
@@ -331,10 +331,10 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
className={styles.nextButton}
|
className={styles.nextButton}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (name.length > 2 && phoneNumber.length >= 10) {
|
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.name = name;
|
||||||
sessionData.phoneNumber = phoneNumber;
|
sessionData.phoneNumber = phoneNumber;
|
||||||
localStorage.setItem('session', JSON.stringify(sessionData));
|
sessionStorage.setItem('session', JSON.stringify(sessionData));
|
||||||
setIsPoppedUp('');
|
setIsPoppedUp('');
|
||||||
sendMessage(isPoppedUp);
|
sendMessage(isPoppedUp);
|
||||||
}
|
}
|
||||||
|
|||||||
237
src/Dashboard.js
237
src/Dashboard.js
@@ -15,13 +15,18 @@ import NotificationPrompt from './NotificationPrompt';
|
|||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
const chartRef = useRef(null);
|
const weeklyChartRef = useRef(null);
|
||||||
const chartInstanceRef = 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 [conversations, setConversations] = useState([]);
|
||||||
const [followUps, setFollowUps] = useState([]);
|
const [followUps, setFollowUps] = useState([]);
|
||||||
const [discussedTopics, setDiscussedTopics] = useState([]);
|
const [discussedTopics, setDiscussedTopics] = useState([]);
|
||||||
const [modalContent, setModalContent] = useState(null);
|
const [modalContent, setModalContent] = useState(null);
|
||||||
const [rawData, setRawData] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true); // ⬅️ Tambahkan state loading
|
const [loading, setLoading] = useState(true); // ⬅️ Tambahkan state loading
|
||||||
const [fileList, setFileList] = useState([]);
|
const [fileList, setFileList] = useState([]);
|
||||||
const [selectedKeys, setSelectedKeys] = 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(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
@@ -207,13 +321,19 @@ const Dashboard = () => {
|
|||||||
setFollowUps(data[0]?.graph[0]?.json?.result?.interested_users)
|
setFollowUps(data[0]?.graph[0]?.json?.result?.interested_users)
|
||||||
setFileList(data[0]?.files)
|
setFileList(data[0]?.files)
|
||||||
setUpdateDetected(data[1]?.updateDetected)
|
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;
|
const graphObj = data[0]?.graph[0]?.json?.result?.graph;
|
||||||
console.log(graphObj)
|
console.log(graphObj)
|
||||||
const rawDataArray = Object.entries(graphObj).map(([hour, sesi]) => ({
|
const rawDataArray = Object.entries(graphObj).map(([hour, sesi]) => ({
|
||||||
hour,
|
hour,
|
||||||
sesi,
|
sesi,
|
||||||
}));
|
}));
|
||||||
setRawData(rawDataArray);
|
|
||||||
|
|
||||||
let totalSessions = new Set();
|
let totalSessions = new Set();
|
||||||
let botMessages = 0;
|
let botMessages = 0;
|
||||||
@@ -320,98 +440,6 @@ const Dashboard = () => {
|
|||||||
setModalContent(<DiscussedTopics topics={discussedTopics} />);
|
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) => {
|
const handleDeleteFile = async (key) => {
|
||||||
if (!window.confirm(`Yakin ingin menghapus "${key}"?`)) return;
|
if (!window.confirm(`Yakin ingin menghapus "${key}"?`)) return;
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
@@ -437,7 +465,7 @@ const Dashboard = () => {
|
|||||||
for (const key of selectedKeys) {
|
for (const key of selectedKeys) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
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',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -479,7 +507,7 @@ const Dashboard = () => {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file, file.name);
|
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
@@ -527,7 +555,7 @@ const Dashboard = () => {
|
|||||||
for (const key of selectedKeys) {
|
for (const key of selectedKeys) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
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',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -641,7 +669,16 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
<div className={styles.chartSection}>
|
<div className={styles.chartSection}>
|
||||||
<h2 className={styles.chartTitle}>Interactions</h2>
|
<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>
|
||||||
<div className={styles.chartSection}>
|
<div className={styles.chartSection}>
|
||||||
<h2 className={styles.chartTitle}>Update AI data</h2>
|
<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();
|
const mergedDataMap = new Map();
|
||||||
|
|
||||||
data.forEach(user => {
|
data.forEach(user => {
|
||||||
@@ -61,10 +61,13 @@ const FollowUps = ({ data: initialData }) => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const existing = mergedDataMap.get(key);
|
const existing = mergedDataMap.get(key);
|
||||||
|
const lastNote = existing.notesList[existing.notesList.length - 1];
|
||||||
|
if (!lastNote || lastNote.note !== user.notes) {
|
||||||
existing.notesList.push({
|
existing.notesList.push({
|
||||||
note: user.notes,
|
note: user.notes,
|
||||||
created_at: user.created_at
|
created_at: user.created_at
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Prioritaskan status tertinggi
|
// Prioritaskan status tertinggi
|
||||||
existing.issuccess = existing.issuccess || user.issuccess;
|
existing.issuccess = existing.issuccess || user.issuccess;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const ProfileTab = () => {
|
|||||||
const [profileTemp, setProfileTemp] = useState({});
|
const [profileTemp, setProfileTemp] = useState({});
|
||||||
|
|
||||||
const licenses = [
|
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(() => {
|
useEffect(() => {
|
||||||
@@ -218,9 +218,9 @@ const ProfileTab = () => {
|
|||||||
<div className={styles.licenseCards}>
|
<div className={styles.licenseCards}>
|
||||||
{licenses.map((item) => (
|
{licenses.map((item) => (
|
||||||
<div className={styles.licenseCard} key={item.id}>
|
<div className={styles.licenseCard} key={item.id}>
|
||||||
<p><strong>{item.type}</strong></p>
|
<p><strong>CURRENT PLAN</strong></p>
|
||||||
<p>{item.number}</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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user