Global Construction Rate Estimator & BOQ Analysis — Pro

Global Construction Rate Estimator

Multi-Country Rate Analysis, Schedule Database & BOQ Estimator

Active Project
Items Shown
0
of database
BOQ Items
0
with quantities
Estimate
0
grand total
Projects
1
0 files saved

Rate Schedule Items

0 items Pakistan • MRS 2026
Live Total BOQ Estimate
Enter quantities above to calculate instantly
0.00 PKR
`; return t; } // --- File System Access API: real folder structure inside Downloads --- let idbHandlePromise = null; function idbOpen() { return new Promise((res, rej) => { const rq = indexedDB.open("gce_fs", 1); rq.onupgradeneeded = () => rq.result.createObjectStore("handles"); rq.onsuccess = () => res(rq.result); rq.onerror = () => rej(rq.error); }); } async function idbGet(key) { const d = await idbOpen(); return new Promise((res) => { const rq = d.transaction("handles").objectStore("handles").get(key); rq.onsuccess = () => res(rq.result); rq.onerror = () => res(null); }); } async function idbSet(key, val) { const d = await idbOpen(); return new Promise((res) => { const tx = d.transaction("handles", "readwrite"); tx.objectStore("handles").put(val, key); tx.oncomplete = () => res(); }); } async function getDownloadsDirHandle() { let handle = await idbGet("downloadsDir").catch(() => null); if (handle) { const perm = await handle.queryPermission({ mode: "readwrite" }); if (perm === "granted") return handle; if ((await handle.requestPermission({ mode: "readwrite" })) === "granted") return handle; } handle = await window.showDirectoryPicker({ id: "gce-downloads", mode: "readwrite", startIn: "downloads" }); await idbSet("downloadsDir", handle).catch(() => {}); return handle; } async function downloadEstimate() { if (!licenseAllows()) { refreshLicenseUI(); openLicenseModal("Your free trial has expired. Activate a lifetime license to continue downloading."); return; } const format = document.getElementById("exportFormat").value; const content = format === "csv" ? buildCSV() : buildXLS(); if (!content) { alert("No items to export with the current filters."); return; } const p = folderParts(); const stamp = new Date().toISOString().slice(0, 10); const fileName = `BOQ_${p.project}_${p.sub}_${stamp}.${format}`; const mime = format === "csv" ? "text/csv;charset=utf-8" : "application/vnd.ms-excel"; const exportMeta = buildExportRows(); const logBase = { fileName, sub: p.sub, total: exportMeta.grand, currency: exportMeta.currency }; // 1) Desktop app (Electron): direct save into Downloads\Estimation\Project\Sub\ if (window.desktopAPI && window.desktopAPI.saveEstimate) { try { const savedPath = await window.desktopAPI.saveEstimate({ folders: [p.root, p.project, p.sub], fileName, content }); showToast(`Saved ✓ ${savedPath}`); recordDownload(Object.assign({ path: savedPath }, logBase)); return; } catch (err) { console.warn("Desktop save failed:", err); } } // 2) Chrome Extension: downloads API supports subfolders inside Downloads if (typeof chrome !== "undefined" && chrome.downloads && chrome.downloads.download) { try { const blobUrl = URL.createObjectURL(new Blob([content], { type: mime })); chrome.downloads.download({ url: blobUrl, filename: `${p.root}/${p.project}/${p.sub}/${fileName}`, conflictAction: "uniquify" }, (downloadId) => { setTimeout(() => URL.revokeObjectURL(blobUrl), 5000); showToast(`Saved ✓ Downloads/${p.root}/${p.project}/${p.sub}/${fileName}`); recordDownload(Object.assign({ downloadId }, logBase)); }); return; } catch (err) { console.warn("Extension download failed:", err); } } // 3) Browser: real folder structure via File System Access API (Chrome/Edge) if (window.showDirectoryPicker && window.isSecureContext) { try { const root = await getDownloadsDirHandle(); const estDir = await root.getDirectoryHandle(p.root, { create: true }); const projDir = await estDir.getDirectoryHandle(p.project, { create: true }); const subDir = await projDir.getDirectoryHandle(p.sub, { create: true }); const fileHandle = await subDir.getFileHandle(fileName, { create: true }); const w = await fileHandle.createWritable(); await w.write(new Blob([content], { type: mime })); await w.close(); showToast(`Saved ✓ ${p.root}/${p.project}/${p.sub}/${fileName}`); recordDownload(Object.assign({ path: `Downloads/${p.root}/${p.project}/${p.sub}/${fileName}` }, logBase)); return; } catch (err) { if (err && err.name === "AbortError") return; // user cancelled picker console.warn("FS Access failed, falling back to normal download:", err); } } // 4) Fallback: normal browser download (folder structure encoded in file name) const blob = new Blob([content], { type: mime }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `Estimation_${fileName}`; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(a.href), 4000); showToast("Downloaded ✓ (browser default Downloads folder)"); recordDownload(Object.assign({ path: "Downloads (browser default)" }, logBase)); } // ==================== LICENSE / SUBSCRIPTION (GH Mughal Consultancy) ==================== const LICENSE_CFG = { product: "GCRE", salt: "GHM-GCRE-2026-K7431-SECRET", trialDays: 7, buyUrl: "https://ghmughalconsultancy.com/extensions/" }; const LIC_KEY = "gce_license", TRIAL_KEY = "gce_trial_start"; function ghmHash(str) { let h = 5381; for (let i = 0; i < str.length; i++) h = (((h << 5) + h) + str.charCodeAt(i)) >>> 0; return h.toString(16).toUpperCase().padStart(8, "0"); } function validateKey(raw) { const k = String(raw).toUpperCase().replace(/[^A-Z0-9]/g, ""); // Format: GHM + part1(5) + part2(5) + check(5) if (!k.startsWith("GHM") || k.length !== 18) return false; const p1 = k.slice(3, 8), p2 = k.slice(8, 13), chk = k.slice(13, 18); const expect = (ghmHash(LICENSE_CFG.salt + p1 + p2) + ghmHash(p2 + LICENSE_CFG.salt + p1)).slice(0, 5); return chk === expect; } function getLicense() { try { return JSON.parse(localStorage.getItem(LIC_KEY)); } catch (e) { return null; } } function isPro() { const l = getLicense(); return !!(l && l.key && validateKey(l.key)); } function licenseTier() { const l = getLicense(); if (!l || !l.key) return ""; return l.key.replace(/[^A-Z0-9]/gi, "").charAt(3).toUpperCase() === "B" ? "BUSINESS" : "PRO"; } function openPlansModal() { document.getElementById("buyBtnPro").href = LICENSE_CFG.buyUrl; document.getElementById("buyBtnBiz").href = LICENSE_CFG.buyUrl; document.getElementById("plansModal").classList.add("open"); } function closePlansModal() { document.getElementById("plansModal").classList.remove("open"); } function trialDaysLeft() { let start = localStorage.getItem(TRIAL_KEY); if (!start) { start = Date.now().toString(); localStorage.setItem(TRIAL_KEY, start); } const used = (Date.now() - parseInt(start)) / 86400000; return Math.max(0, Math.ceil(LICENSE_CFG.trialDays - used)); } function licenseAllows() { return isPro() || trialDaysLeft() > 0; } function refreshLicenseUI() { const bar = document.getElementById("licenseBar"); const status = document.getElementById("licenseStatus"); document.getElementById("buyBtn").href = LICENSE_CFG.buyUrl; document.getElementById("buyBtn2").href = LICENSE_CFG.buyUrl; bar.style.display = "flex"; bar.classList.remove("pro", "expired"); if (isPro()) { const l = getLicense(); bar.classList.add("pro"); status.textContent = `✅ ${licenseTier()} Activated${l.name ? " — " + l.name : ""} (Lifetime)`; document.getElementById("buyBtn").style.display = "none"; } else { const days = trialDaysLeft(); if (days > 0) { status.textContent = `⏳ Free Trial — ${days} day(s) left. Activate PRO for lifetime access.`; } else { bar.classList.add("expired"); status.textContent = "❌ Trial expired — Downloads locked. Buy a license to continue."; } document.getElementById("buyBtn").style.display = ""; } } function openLicenseModal(msg) { if (msg) document.getElementById("licenseModalMsg").textContent = msg; document.getElementById("licenseModal").classList.add("open"); setTimeout(() => document.getElementById("licKey").focus(), 50); } function closeLicenseModal() { document.getElementById("licenseModal").classList.remove("open"); } function activateLicense() { const key = document.getElementById("licKey").value.trim(); const name = document.getElementById("licName").value.trim(); if (!validateKey(key)) { alert("Invalid license key. Please check the key or contact GH Mughal Consultancy."); return; } localStorage.setItem(LIC_KEY, JSON.stringify({ key: key.toUpperCase(), name, activatedOn: new Date().toISOString().slice(0, 10) })); closeLicenseModal(); refreshLicenseUI(); showToast("🎉 License activated — lifetime access. Thank you!"); } // ==================== PROJECT FILES HISTORY & EMAIL ==================== function recordDownload(info) { const p = activeProj(); if (!p.files) p.files = []; p.files.push(Object.assign({ date: new Date().toISOString().replace("T", " ").slice(0, 16) }, info)); if (p.files.length > 200) p.files = p.files.slice(-200); saveDB(); maybeAutoEmail(info); } function openFilesModal() { const p = activeProj(); document.getElementById("filesModalProject").textContent = db.activeProject; const files = (p.files || []).slice().reverse(); const list = document.getElementById("filesList"); if (!files.length) { list.innerHTML = `
No files have been downloaded for this project yet.
`; } else { list.innerHTML = files.map((f, i) => `
${escHtml(f.fileName)}
📁 ${escHtml(f.sub || "")} • 🕒 ${escHtml(f.date)}${f.total !== undefined ? " • 💰 " + fmt(f.total) + " " + escHtml(f.currency || "") : ""}
${canShowFile(f) ? `` : ""}
`).join(""); } document.getElementById("filesHint").textContent = p.email ? `Assigned email: ${p.email}${p.autoEmail ? " (auto-email ON)" : ""}` : "Tip: assign an email in the Project card — the Email button will open a draft to it."; document.getElementById("openFolderBtn").style.display = (window.desktopAPI && window.desktopAPI.openFolder) ? "" : "none"; document.getElementById("filesModal").classList.add("open"); } function closeFilesModal() { document.getElementById("filesModal").classList.remove("open"); } function canShowFile(f) { if (window.desktopAPI && window.desktopAPI.showItem && f.path) return true; if (typeof chrome !== "undefined" && chrome.downloads && chrome.downloads.show && f.downloadId !== undefined) return true; return false; } function showFileInFolder(revIdx) { const p = activeProj(); const files = (p.files || []).slice().reverse(); const f = files[revIdx]; if (!f) return; if (window.desktopAPI && window.desktopAPI.showItem && f.path) { window.desktopAPI.showItem(f.path); return; } if (typeof chrome !== "undefined" && chrome.downloads && chrome.downloads.show && f.downloadId !== undefined) chrome.downloads.show(f.downloadId); } function openProjectFolder() { if (window.desktopAPI && window.desktopAPI.openFolder) { const p = folderParts(); window.desktopAPI.openFolder([p.root, p.project]); } } function buildEmailDraft(to, info) { const subject = `BOQ Estimate — ${db.activeProject}${info && info.fileName ? " — " + info.fileName : ""}`; const lines = [ `Dear Sir/Madam,`, ``, `Project: ${db.activeProject}`, activeProj().client ? `Client: ${activeProj().client}` : ``, info && info.fileName ? `File: ${info.fileName}` : ``, info && info.sub ? `Folder: Downloads/Estimation/${folderParts().project}/${info.sub}` : ``, info && info.total !== undefined ? `Total Estimate: ${fmt(info.total)} ${info.currency || ""}` : ``, ``, `Note: Please attach the BOQ file from your Downloads/Estimation folder.`, ``, `Regards,`, `GH Mughal Consultancy — Global Construction Rate Estimator` ].filter(l => l !== undefined); return `mailto:${encodeURIComponent(to)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(lines.join("\n"))}`; } function openEmail(url) { if (window.desktopAPI && window.desktopAPI.openExternal) { window.desktopAPI.openExternal(url); return; } const a = document.createElement("a"); a.href = url; document.body.appendChild(a); a.click(); a.remove(); } function emailFileDraft(revIdx) { const p = activeProj(); const files = (p.files || []).slice().reverse(); const f = files[revIdx]; let to = p.email; if (!to) { to = prompt("Enter an email address (or assign one in the Project card):", ""); if (!to) return; } openEmail(buildEmailDraft(to, f)); } function maybeAutoEmail(info) { const p = activeProj(); if (p.email && p.autoEmail) { setTimeout(() => openEmail(buildEmailDraft(p.email, info)), 600); showToast(`✉ Opening email draft → ${p.email}`); } } // ==================== UTILS ==================== function fmt(n) { const d = (db.settings && db.settings.decimals !== undefined) ? db.settings.decimals : 2; return (Math.round(n * 100) / 100).toLocaleString(undefined, { maximumFractionDigits: d }); } function round2(n) { return Math.round(n * 100) / 100; } function escHtml(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function escAttr(s) { return escHtml(s).replace(/'/g, "'"); } function showToast(msg) { const t = document.getElementById("toast"); t.textContent = msg; t.style.display = "block"; clearTimeout(t._h); t._h = setTimeout(() => t.style.display = "none", 3500); } // ==================== INIT ==================== window.onload = function () { loadDB(); applyTheme(); applyCachedRates(); if (masterData[db.settings.defaultCountry]) document.getElementById("countrySelect").value = db.settings.defaultCountry; refreshProjectUI(); onCountryChange(); refreshLicenseUI(); autoCheckRates(); };