import React, { useEffect, useMemo, useRef, useState } from "react"; import styles from "./IdentifyCafeModal.module.css"; import API_BASE_URL from "../config.js"; import { getTables, createTable } from "../helpers/tableHelper"; import { saveCafeDetails } from "../helpers/cafeHelpers"; import { getImageUrl } from "../helpers/itemHelper"; import { toPng } from "html-to-image"; export default function IdentifyCafeModal({ shop }) { const [cafeIdentifyName, setCafeIdentifyName] = useState(shop.cafeIdentifyName || ""); const [availability, setAvailability] = useState(null); // 200 ok, 409 taken const [checking, setChecking] = useState(false); const [tables, setTables] = useState([]); const [selectedTable, setSelectedTable] = useState(null); const [qrSize, setQrSize] = useState(Number(shop.scale) || 1); const [qrX, setQrX] = useState(Number(shop.xposition) || 50); const [qrY, setQrY] = useState(Number(shop.yposition) || 50); const [fontSize, setFontSize] = useState(Number(shop.fontsize) || 16); const [fontColor, setFontColor] = useState(shop.fontcolor || "#FFFFFF"); const [fontX, setFontX] = useState(Number(shop.fontxposition) || 50); const [fontY, setFontY] = useState(Number(shop.fontyposition) || 85); const [bgImageUrl, setBgImageUrl] = useState(getImageUrl(shop.qrBackground)); const bgFileRef = useRef(null); const [newTableNo, setNewTableNo] = useState(""); const previewRef = useRef(null); const [copied, setCopied] = useState(false); const [currentStep, setCurrentStep] = useState(1); // 1=Alamat, 2=Desain QR, 3=Meja const [saveStatus, setSaveStatus] = useState(null); // 'success' | 'error' const [saving, setSaving] = useState(false); const initialDesignRef = useRef({ qrSize: Number(shop.scale) || 1, qrX: Number(shop.xposition) || 50, qrY: Number(shop.yposition) || 50, fontSize: Number(shop.fontsize) || 16, fontColor: shop.fontcolor || "#FFFFFF", fontX: Number(shop.fontxposition) || 50, fontY: Number(shop.fontyposition) || 85, bgImageUrl: getImageUrl(shop.qrBackground), }); const shopHost = useMemo(() => window.location.hostname, []); const fullLink = useMemo(() => `${shopHost}/${cafeIdentifyName}`, [shopHost, cafeIdentifyName]); // Debounced availability check const debounceRef = useRef(null); const handleIdentifyChange = (e) => { const val = e.target.value .toLowerCase() .replace(/\s+/g, "_") .replace(/[^a-z0-9_]/g, ""); setCafeIdentifyName(val); setChecking(true); setAvailability(null); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(async () => { try { const res = await fetch(`${API_BASE_URL}/cafe/check-identifyName/${val}`); setAvailability(res.ok ? 200 : 409); } catch (_) { setAvailability(409); } finally { setChecking(false); } }, 600); }; // Load tables useEffect(() => { (async () => { try { const fetched = await getTables(shop.cafeId); setTables(fetched || []); } catch (e) { // ignore } })(); }, [shop.cafeId]); const handleUploadBg = (e) => { const file = e.target.files?.[0]; if (file) { const url = URL.createObjectURL(file); setBgImageUrl(url); } }; const handleCreateTable = async () => { if (!newTableNo) return; try { const created = await createTable(shop.cafeId, { tableNo: newTableNo }); setTables((t) => [...t, created]); setNewTableNo(""); } catch (e) { // noop } }; const handleSave = async () => { setSaving(true); setSaveStatus(null); const qrBackgroundFile = bgFileRef.current?.files?.[0]; const details = { qrSize, qrPosition: { left: qrX, top: qrY }, qrBackgroundFile, fontsize: fontSize, fontcolor: fontColor, fontPosition: { left: fontX, top: fontY }, cafeIdentifyName: shop.cafeIdentifyName !== cafeIdentifyName ? cafeIdentifyName : null, }; try { await saveCafeDetails(shop.cafeId, details); setSaveStatus('success'); } catch (e) { setSaveStatus('error'); } finally { setSaving(false); } }; const downloadPreview = async () => { if (!previewRef.current) return; const node = previewRef.current; const originalBg = node.style.backgroundColor; node.style.backgroundColor = "transparent"; try { const dataUrl = await toPng(node, { pixelRatio: 2 }); const link = document.createElement("a"); link.href = dataUrl; link.download = selectedTable ? `QR Meja (${selectedTable.tableNo}).png` : `QR ${shop.name}.png`; link.click(); } catch (e) { // noop } finally { node.style.backgroundColor = originalBg; } }; const copyLink = async () => { try { await navigator.clipboard.writeText(fullLink); setCopied(true); setTimeout(()=>setCopied(false), 1400); } catch (_) {} }; const applyPreset = (preset) => { if (preset === 'center') { setQrX(50); setQrY(50); setQrSize(1); setFontX(50); setFontY(85); } else if (preset === 'topLeft') { setQrX(25); setQrY(30); setQrSize(1); setFontX(50); setFontY(85); } else if (preset === 'bottomRight') { setQrX(75); setQrY(70); setQrSize(1); setFontX(50); setFontY(15); } }; const resetDesign = () => { const d = initialDesignRef.current; setQrSize(d.qrSize); setQrX(d.qrX); setQrY(d.qrY); setFontSize(d.fontSize); setFontColor(d.fontColor); setFontX(d.fontX); setFontY(d.fontY); setBgImageUrl(d.bgImageUrl); if (bgFileRef.current) bgFileRef.current.value = ''; }; // Positioning helpers const qrStyle = { left: `${qrX}%`, top: `${qrY}%`, transform: `translate(-50%, -50%) scale(${qrSize})`, }; const fontStyle = { left: `${fontX}%`, top: `${fontY}%`, transform: `translate(-50%, -50%)`, color: fontColor, fontSize: `${fontSize}px`, position: "absolute", fontWeight: 700, }; const qrData = selectedTable ? `${fullLink}/${selectedTable.tableNo}` : fullLink; const canProceedFromStep1 = () => { if (!cafeIdentifyName) return false; if (cafeIdentifyName === (shop.cafeIdentifyName || '')) return true; return availability === 200 && !checking; }; return (
Identifikasi kedai
1 Alamat
2 Desain QR
3 Meja
{currentStep === 1 && (
Alamat kedai
{shopHost}/
{checking ? 'Memeriksa…' : availability === 200 ? 'Tersedia' : availability === 409 ? 'Terpakai' : 'Menunggu input'}
Gunakan huruf kecil, angka, dan garis bawah (_). Contoh: kopikenangan_malam
)} {currentStep === 2 && (
Desain QR
{bgImageUrl && Background} QR
{selectedTable ? selectedTable.tableNo : 'Kedai'}
setQrSize(parseFloat(e.target.value))} />
setQrX(parseInt(e.target.value))} />
setQrY(parseInt(e.target.value))} />
setFontSize(parseInt(e.target.value))} />
setFontColor(e.target.value)} />
setFontX(parseInt(e.target.value))} />
setFontY(parseInt(e.target.value))} />
Tip: Gunakan latar yang kontras agar QR mudah dipindai.
)} {currentStep === 3 && (
Daftar meja
setNewTableNo(e.target.value)} />
{tables && tables .filter((t)=>t.tableNo !== 0) .map((t)=>{ const active = selectedTable && selectedTable.tableId === t.tableId; return (
setSelectedTable(active ? null : t)} > {t.tableNo}
); })}
Pilih meja untuk membuat QR khusus meja (opsional).
)}
{currentStep < 3 ? ( ) : ( )}
{saveStatus === 'success' &&
Simpan berhasil
} {saveStatus === 'error' &&
Gagal menyimpan
}
); }