diff --git a/src/DiscussedTopics.js b/src/DiscussedTopics.js index f53943d..8e5610f 100644 --- a/src/DiscussedTopics.js +++ b/src/DiscussedTopics.js @@ -1,9 +1,76 @@ // DiscussedTopics.js -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import styles from './DiscussedTopics.module.css'; -const DiscussedTopics = ({ topics, faceAnalystList }) => { - const [activeTab, setActiveTab] = useState('topics'); // 'topics' or 'face' +const IMG_BASE = 'https://bot.kediritechnopark.com/webhook'; + +const DiscussedTopics = ({ topics = [], faceAnalystList = [] }) => { + const [activeTab, setActiveTab] = useState('topics'); // 'topics' | 'face' + const [imageSrcs, setImageSrcs] = useState({}); // { [id]: dataUrl } + const [imgErrors, setImgErrors] = useState({}); // { [id]: true } + + // super simple: always expect [{ data: "" }] + const fetchImageSrc = async (id) => { + const token = + localStorage.getItem('token') || + localStorage.getItem('access_token') || + ''; + + const resp = await fetch( + `${IMG_BASE}/16f3a63d-0271-4f03-b1fb-14a051c57285/face-analysis-image/${id}`, + { + headers: { + ...(token ? { Authorization: `Bearer ${token}` } : {}), + Accept: 'application/json', + }, + } + ); + + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + + const json = await resp.json(); + const base64 = json?.[0]?.data; + if (!base64) throw new Error('No base64 data'); + + // Your backend returns JPEG base64, so just prefix it: + return `data:image/jpeg;base64,${base64}`; + }; + + // Fetch images when Face tab becomes active + useEffect(() => { + if (activeTab !== 'face' || faceAnalystList.length === 0) return; + + let cancelled = false; + + (async () => { + const nextImages = {}; + const nextErrors = {}; + + await Promise.all( + faceAnalystList.map(async (item) => { + const id = item?.id; + if (!id) return; + + try { + const src = await fetchImageSrc(id); + nextImages[id] = src; + } catch (e) { + console.error(`Failed to load image for ${id}`, e); + nextErrors[id] = true; + } + }) + ); + + if (!cancelled) { + setImageSrcs(nextImages); + setImgErrors(nextErrors); + } + })(); + + return () => { + cancelled = true; + }; + }, [activeTab, faceAnalystList]); return (
@@ -55,15 +122,37 @@ const DiscussedTopics = ({ topics, faceAnalystList }) => {

No Face Analyst Report

) : ( - faceAnalystList.map((item, idx) => ( -
-
-

{item.name}

-

{item.description}

-

📞 {item.phone_number}

+ faceAnalystList.map((item, idx) => { + const id = item?.id; + const src = id ? imageSrcs[id] : null; + const hadError = id ? imgErrors[id] : false; + + return ( +
+
+

{item.name}

+ + {src && ( + {`Face + )} + + {!src && !hadError && ( +
Loading image…
+ )} + {hadError && ( +
Couldn’t load image
+ )} + +

{item.description}

+

📞 {item.phone_number}

+
-
- )) + ); + }) )}
diff --git a/src/DiscussedTopics.module.css b/src/DiscussedTopics.module.css index b0087c8..3f0b415 100644 --- a/src/DiscussedTopics.module.css +++ b/src/DiscussedTopics.module.css @@ -216,3 +216,8 @@ font-size: 0.85rem; color: #666; } + +.imagePreview{ + border-radius: 15px; + width: 100%; +} \ No newline at end of file diff --git a/src/FollowUps.js b/src/FollowUps.js index 1f6fd1c..32c7ac7 100644 --- a/src/FollowUps.js +++ b/src/FollowUps.js @@ -1,6 +1,67 @@ import React, { useState } from 'react'; import styles from './FollowUps.module.css'; +// Prefix → ikon (bebas ganti jadi SVG/logo sendiri) +const PREFIX_ICON = { + WGG: 'WHATSAPP', // WhatsApp + TGG: 'TELEGRAM', // Telegram + WEB: 'WEBSITE', // Web + IGG: 'INSTAGRAM', // Instagram +}; + +// Helper: parse, normalisasi, dan validasi nomor Indonesia +function parseContactInfo(raw) { + if (!raw) return { prefix: null, digits: null, display: raw || '', isValid: false }; + + let text = String(raw).trim(); + let prefix = null; + + // Deteksi prefix di awal (dengan/tanpa '-') + const m = text.match(/^(WGG|TGG|WEB|IGG)-?/i); + if (m) { + prefix = m[1].toUpperCase(); + text = text.slice(m[0].length); // buang prefix dari display + } + + // Bersihkan: sisakan angka dan tanda '+' + let cleaned = text.replace(/[^\d+]/g, ''); + + // Normalisasi: buang '+' untuk proses + if (cleaned.startsWith('+')) cleaned = cleaned.slice(1); + + // Jika 08… → ganti ke 62… + if (cleaned.startsWith('0')) cleaned = `62${cleaned.slice(1)}`; + + // Hanya digit + let digits = cleaned.replace(/\D/g, ''); +// Setelah menghasilkan `digits` yang hanya angka +if (digits.startsWith('6262')) { + // buang '62' ekstra di depan + digits = digits.slice(2); +} + +// Juga bereskan kasus '6208...' (orang nulis '62' + nomor lokal '08...') +if (digits.startsWith('620')) { + digits = '62' + digits.slice(3); // hasilnya '628...' +} + +// (opsional) Kalau user pakai format internasional '0062...' → jadikan ke '62...' +if (digits.startsWith('00')) { + digits = digits.slice(2); +} + + // Pastikan diawali 62 + if (!digits.startsWith('62') && /^\d+$/.test(digits)) { + // Jika bukan 62, biarkan apa adanya (mungkin sudah nomor lokal lain), + // tapi validator di bawah akan menandai tidak valid. + } + + const isValid = /^62\d{7,13}$/.test(digits); // 62 + 7–13 digit + const display = isValid ? `+${digits}` : (text.trim() || raw); + + return { prefix, digits, display, isValid }; +} + const FollowUps = ({ data: initialData }) => { const [data, setData] = useState(initialData); const [statusFilter, setStatusFilter] = useState('all'); @@ -13,9 +74,7 @@ const FollowUps = ({ data: initialData }) => { try { await fetch('https://bot.kediritechnopark.com/webhook/set-follow-up', { method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: user.id, isfollowup: user.isfollowup ? 'success' : 'followup' @@ -36,8 +95,11 @@ const FollowUps = ({ data: initialData }) => { ); } + // Buka WA chat pakai nomor yang sudah dinormalisasi (jika valid) if (!user.isfollowup) { - window.open(`https://api.whatsapp.com/send?phone=${user.contact_info}`, '_blank'); + const parsed = parseContactInfo(user.contact_info); + const waNumber = parsed.isValid ? parsed.digits : user.contact_info; + window.open(`https://api.whatsapp.com/send?phone=${waNumber}`, '_blank'); } } catch (error) { console.error('Failed to set follow-up:', error); @@ -45,31 +107,21 @@ const FollowUps = ({ data: initialData }) => { } }; - // Gabungkan data berdasarkan contact_info dan hilangkan note yang sama secara berurutan + // Gabungkan data berdasarkan contact_info dan hilangkan note yang sama berurutan const mergedDataMap = new Map(); - data.forEach(user => { const key = user.contact_info; - if (!mergedDataMap.has(key)) { mergedDataMap.set(key, { ...user, - notesList: [{ - note: user.notes, - created_at: user.created_at - }] + notesList: [{ note: user.notes, created_at: user.created_at }] }); } 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 - }); + existing.notesList.push({ note: user.notes, created_at: user.created_at }); } - - // Prioritaskan status tertinggi existing.issuccess = existing.issuccess || user.issuccess; existing.isfollowup = existing.issuccess ? false : (existing.isfollowup || user.isfollowup); } @@ -82,14 +134,10 @@ const FollowUps = ({ data: initialData }) => { const filteredData = mergedData .filter(user => { switch (statusFilter) { - case 'pending': - return !user.isfollowup && !user.issuccess; - case 'inProgress': - return user.isfollowup && !user.issuccess; - case 'success': - return user.issuccess; - default: - return true; + case 'pending': return !user.isfollowup && !user.issuccess; + case 'inProgress': return user.isfollowup && !user.issuccess; + case 'success': return user.issuccess; + default: return true; } }) .filter(user => { @@ -98,10 +146,11 @@ const FollowUps = ({ data: initialData }) => { switch (dateFilter) { case 'today': return created.toDateString() === now.toDateString(); - case 'week': + case 'week': { const aWeekAgo = new Date(); aWeekAgo.setDate(now.getDate() - 7); return created >= aWeekAgo; + } default: return true; } @@ -119,9 +168,9 @@ const FollowUps = ({ data: initialData }) => {
- setDateFilter(e.target.value)} > @@ -146,9 +195,9 @@ const FollowUps = ({ data: initialData }) => {
-