body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center}body.spinner-active{overflow:hidden}button{white-space:nowrap}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}*{box-sizing:border-box;margin:0;padding:0}.tiles{display:flex;justify-content:flex-start;align-items:center;flex-wrap:wrap}.tile{padding:5px;width:25%}.tile .sheet{background-color:#1165d4;cursor:pointer;display:flex;justify-content:center;align-items:center;height:150px;width:100%;transition:all .3s ease-in-out;padding:20px}.tile .sheet span{font-size:24px;font-weight:500;transition:all .3s ease-in-out}.tile .sheet:hover{transform:scale(1.02)}.tile .sheet:hover span{color:#fff}table{width:100%;border-left:1px solid black;border-top:1px solid black}td,th{border-right:1px solid black;border-bottom:1px solid black}.btn{padding:7px 20px;cursor:pointer;border:none;outline:none;color:#fff;font-weight:500;background-color:#000;border-radius:4px;margin:10px 0}.btn_edit,.btn_cancel,.btn_save,.btn_google,.btn_scrape{padding:7px 10px;cursor:pointer;border:none;outline:none;color:#fff;font-weight:500;border-radius:4px;margin:5px 0}.btn_edit{background-color:tomato}.btn_cancel{background-color:#fd4e30}.btn_save{background-color:#000}.btn_google{background-color:#1f1ffc}.btn_scrape{background-color:#f85215}.d_flex{display:flex;flex-direction:column;padding:5px 10px;gap:10px}.input{padding:10px;font-size:16px;min-width:60px;width:90%;border-radius:5px;border:1px solid #ccc;margin-left:5px;margin-right:5px;margin-bottom:15px}.upload-options,.pagination{display:flex;justify-content:center;align-items:center;gap:12px;margin-bottom:20px}.modal-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:#0006;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);display:flex;align-items:center;justify-content:center;z-index:9999}.modal-backdrop.hidden{display:none}.modal-backdrop.visible{display:flex}.modal,.modal-large{background:#fff;padding:30px;border-radius:10px;box-shadow:0 0 15px #0000004d;text-align:center}.modal{min-width:300px;max-width:90%}.modal-large{width:90%;max-height:90vh;overflow-y:auto}.modal h2{margin-bottom:20px}.modal input{min-width:400px;width:80%;padding:10px;font-size:16px;border:1px solid #ccc;border-radius:5px;margin-bottom:20px}.modal button,.modal-large button{padding:10px 20px;border:none;border-radius:5px;font-size:16px;cursor:pointer;margin:5px}.modal .btn_cancel,.modal-large .btn_cancel{background-color:#b71c1c;color:#fff}.file-modal{background:#fefefe;box-shadow:0 0 20px #0003;padding:1.5rem;border-radius:12px;max-height:80vh;overflow-y:auto;width:90%;max-width:500px}.file-list button:hover{background-color:#f0f0f0}.loader{border:6px solid #f3f3f3;border-top:6px solid #3498db;border-radius:50%;width:50px;height:50px;animation:spin 1s linear infinite}.loading-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#1119;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:99999;pointer-events:all}.edit-modal-content{background:#fff;width:95vw;max-width:1200px;max-height:80vh;overflow-y:auto;position:relative;padding:2rem;border-radius:10px;box-shadow:0 8px 20px #0003;text-align:left}.edit-modal-content h3{margin-bottom:1rem;font-size:24px;text-align:center}.edit-image-preview{text-align:center;margin-bottom:1.5rem}.edit-image-preview img{max-width:200px;height:auto;border-radius:8px;box-shadow:0 0 8px #0000001a}.edit-form{display:flex;gap:2rem;flex-wrap:wrap}.edit-form-column{flex:1;min-width:300px}.edit-form-group{margin-bottom:1rem}.edit-form-group label{display:block;font-weight:700;margin-bottom:.5rem;color:#333}.edit-form-group input{width:100%;min-width:250px;padding:.5rem;font-size:16px;border:1px solid #ccc;border-radius:4px}.edit-form-buttons{margin-top:1.5rem;display:flex;gap:1.5rem;flex-wrap:wrap;justify-content:center;align-items:center}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (max-width: 500px){.tile{width:50%}}@media (max-width: 768px){.edit-modal-content{width:100vw;height:100vh;padding:1rem;border-radius:0}.edit-form{flex-direction:column}}import{useEffect,useLayoutEffect,useState}from "react"; import{ToastContainer,toast}from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import "./App.css"; import{read,utils}from "xlsx"; import Scraped from "./Scraped"; import axios from "axios"; function App(){const [page,setPage] = useState(1);const [total,setTotal] = useState(1);const [totaldata,setTotalData] = useState([]);const [sheetdata,setSheetdata] = useState();const [sheetnames,setSheetnames] = useState();const [tableData,setTableData] = useState([]);const [tableheader,setTableheader] = useState([]);const [showtable,setShowtable] = useState(false);const [excelName,setExcelName] = useState("");const [percentage,setPercentage] = useState(0);const [sbNumber,setSbNumber] = useState("");const [category,setCategory] = useState("");const [warehouse,setWarehouse] = useState("");const [palletQty,setPalletQty] = useState(0);const [bol,setBol] = useState("");const [itemIds,setItemIds] = useState({});const [itemTitles,setItemTitles] = useState({});const [itemModelNumbers,setItemModelNumbers] = useState({});const [serverFiles,setServerFiles] = useState([]);const [showFileModal,setShowFileModal] = useState(false);const [showModal,setShowModal] = useState(false);const [modalPrompt,setModalPrompt] = useState("");const [modalInput,setModalInput] = useState("");const [onModalConfirm,setOnModalConfirm] = useState(() => () => {});const openPromptModal = (promptText,onConfirm) =>{setModalPrompt(promptText);setModalInput("");setOnModalConfirm(() => onConfirm);setShowModal(true)}// Prevent accidental refresh function handleBeforeUnload(e){e.preventDefault();e.returnValue = ""}window.addEventListener("beforeunload",handleBeforeUnload);const sumQtyAndWholesale = (data) =>{// console.log("Values sum",Object.values(data));let totalQty = 0;let totalWholesale = 0;for (let i = 1; i < data.length; i++){const loopData = Object.values(data[i]);// console.log("Data for loop:",loopData);const qty = parseInt(loopData[5]);const whole = parseInt(loopData[6]);if (!isNaN(qty)) totalQty += qty;if (!isNaN(whole)) totalWholesale += whole}return{totalQty,totalWholesale}}const uploadPackages = (e) =>{// console.log("Packages Bol:",bol);// console.log("Table data:",totaldata);const{totalQty,totalWholesale}= sumQtyAndWholesale(totaldata);const date = new Date();const packages ={order_number:"",sb_number: sbNumber,category,warehouse,pallets_quantity: palletQty,bol,items_count: totalQty,wholesale_price: totalWholesale,date,file_name: excelName,file_location: "uploads",}axios .post("https://www.manifestz.com/php_test_files/packages.php",packages) .then((res) => {toast.success(res.data.message); console.log(res.data);}) .catch((err) => console.error(err))}const fetchServerFiles = async () =>{const res = await fetch("https://www.manifestz.com/php_test_files/list_files.php");const files = await res.json();setServerFiles(files);// store file list setShowFileModal(true);// open modal}const handleSelectFile = async (selected) =>{setShowFileModal(false);setExcelName(selected);const resFile = await fetch(`https://www.manifestz.com/php_test_files/download_file.php?file=${selected}`);const blob = await resFile.blob();const reader = new FileReader();reader.onload = (e) =>{const sheets = e.target.result;const workbook = read(sheets,{type: "binary"});const sheetNames = workbook.SheetNames;const data ={}sheetNames.forEach((sheetName) => {const sheet = workbook.Sheets[sheetName]; data[sheetName] = utils.sheet_to_json(sheet);});setSheetnames(sheetNames);setSheetdata(data);setTableData()}reader.readAsBinaryString(blob)}const handleChange = (e) =>{const file = e.target.files[0];if (!file) return;// set excel name setExcelName(file.name);// Upload to PHP server const formData = new FormData();formData.append("file",file);fetch("https://www.manifestz.com/php_test_files/upload.php",{method: "POST",body: formData,}) .then((res) => res.json()) .then((data) => {toast.success(data.message) console.log("Upload response:",data.message);}) .catch((err) => console.error("Upload failed:",err));// Process in-browser const reader = new FileReader();reader.onload = (e) =>{const sheets = e.target.result;const workbook = read(sheets,{type: "binary"});const sheetNames = workbook.SheetNames;const data ={}sheetNames.forEach((sheetName) => {const sheet = workbook.Sheets[sheetName]; data[sheetName] = utils.sheet_to_json(sheet);});setSheetnames(sheetNames);setSheetdata(data);setTableData()}reader.readAsBinaryString(file)}const parseSheet = (data) =>{const row = Object.keys(data[0])[0] || "";let sbNum = row.match(/SBs*#?:?s*(\d+)/i)?.[1] || "Not found";let categ = row.match(/Category:?:?s*([^s,]+)/i)?.[1] || "Not found";let ware_house = row.match(/Warehouse:?:?s*([^s,]+)/i)?.[1] || "Not found";let palet_qty = row.match(/Pallets*Qty:?:?s*(\d+)/i)?.[1] || "Not found";let bl = row.match(/Bls*#?:?s*(\d+)/i)?.[1] || "Not found";// Step-by-step prompts using callbacks const askSbNumber = (next) =>{if (sbNum !== "Not found") return next();openPromptModal("Enter SB Number Value",(value) => {if (!value) return askSbNumber(next); sbNum = value; next();})}const askCategory = (next) =>{if (categ !== "Not found") return next();openPromptModal("Enter Category",(value) => {if (!value) return askCategory(next); categ = value; next();})}const askPercentage = () =>{openPromptModal("Enter Percentage Value",(value) => {if (!value || isNaN(value)) return askPercentage(); setPercentage(parseFloat(value)); finishParse();})}const finishParse = () =>{setSbNumber(sbNum);setCategory(categ);setBol(bl);setPalletQty(parseInt(palet_qty));setWarehouse(ware_house);console.log("SB Number:",sbNum);console.log("Category:",categ);console.log("warehouse:",ware_house);console.log("bol:",bl);console.log("pallet qty:",palet_qty);setTableheader(Object.values(data[0]));const seenSkus = new Set();const uniqueData = data.filter((row) => {const rawSku = Object.values(row)[0]; const sku = typeof rawSku === "string" ? rawSku.trim().toLowerCase() : String(rawSku).trim().toLowerCase(); if (seenSkus.has(sku)) return false; seenSkus.add(sku); return true;});console.log(`Removed ${data.length - uniqueData.length} duplicate rows`);setTotalData(data);// keep all rows setTableData(data.slice(0,10));// load page 1 setTotal(Math.ceil(data.length / 10));setPage(1);setShowtable(true)}// Start the chain askSbNumber(() => {askCategory(() => {askPercentage();});})}console.log("Percentage:",percentage);console.log("SB Number:",sbNumber);console.log("Category:",category);useEffect(() => {const start = (page - 1) * 10; const nextSlice = totaldata.slice(start,start + 10); setTableData((prev) => [...prev,...nextSlice]);},[page]);const handleScroll = () =>{const topScroll = Math.ceil(window.innerHeight + window.scrollY);const bottomScroll = Math.floor(document.documentElement.offsetHeight);const scrolledToBottom = topScroll >= bottomScroll;console.log(scrolledToBottom);console.log(topScroll,bottomScroll);if (scrolledToBottom){setPage((prevPage) => prevPage + 1)}}useEffect(() => {window.addEventListener("scroll",handleScroll); return () => window.removeEventListener("scroll",handleScroll);},[]);useLayoutEffect(() => {const shouldLock = showModal || showFileModal; document.body.classList.toggle("spinner-active",shouldLock);},[showModal,showFileModal]);return (<div className="App"> <ToastContainer position="top-right" autoClose={3000} /> {showModal && (<div className="loading-overlay"> <div className="file-modal"> <h2>{modalPrompt}</h2> <input className="input" style={{textAlign: "center"}} type="text" value={modalInput} onChange={(e) => setModalInput(e.target.value)} /> <div style={{display: "flex",gap: "1.5rem",textAlign: "center",justifyContent: "center",}} > <button className="btn_save" onClick={() => {setShowModal(false); onModalConfirm(modalInput);}} > Confirm </button> <button className="btn_scrape" onClick={() => setShowModal(false)} > Cancel </button> </div> </div> </div>)} {showFileModal && (<div className="loading-overlay"> <div className="file-modal"> <h2>Select a file to load</h2> <ul style={{listStyle: "none",padding: 0}}> {serverFiles.map((file) => (<li key={file}> <button style={{margin: "0.5rem 0",padding: "0.75rem 1rem",width: "100%",textAlign: "left",border: "1px solid #ccc",borderRadius: "5px",cursor: "pointer",}} onClick={() => handleSelectFile(file)} > {file} </button> </li>))} </ul> <button className="btn_scrape" onClick={() => setShowFileModal(false)} > Cancel </button> </div> </div>)} {!showtable && (<div className="upload-options"> <button onClick={() => document.getElementById("fileInput").click()} className="btn" > Upload from Computer </button> <button onClick={fetchServerFiles} className="btn"> Load from Server </button> <input id="fileInput" type="file" style={{display: "none"}} onChange={handleChange} /> </div>)} {showtable ? (<> <button onClick={(e) => {setShowtable(false);}} className="btn" > Back to home </button> <table cellSpacing="0"> <thead> <tr> {tableheader.map((item,i) => (<th key={i} style={{width: `${i == 2 ? "260px" : "fit-content"}`}} > {item.includes("Pallet") ? "PID #" : item.includes("Scan") ? "Item ID #" : item} {} </th>))} <th>Retail</th> <th>Image</th> <th>Difference</th> <th>Price</th> <th>Action</th> </tr> </thead> <tbody> <tr> <td colSpan={12} style={{padding: "10px"}}> {Object.keys(tableData[0])[0]} </td> </tr> {tableData.map((row,i) => {// console.log("Row: ",row[0]); // console.log("Keys: ",Object.keys(row)[0]); let keys = Object.keys(tableData[0]); let sku = Object.values(row)[0]; if (sku?.toString()?.trim() === "SKU") return <></>; return (<Scraped key={i} sbNumber={sbNumber} category={category} sku={sku} product={row} keys={keys} percentage={percentage} scrapedItemID={(sku,newId) => {setItemIds((prev) => ({...prev,[sku]: newId}));}} scrapedItemTitle={(sku,newTitle) => {setItemTitles((prev) => ({...prev,[sku]: newTitle}));}} scrapedItemModelNumber={(sku,newModelNumber) => {setItemModelNumbers((prev) => ({...prev,[sku]: newModelNumber,}));}} onDeleteRow={(deletedSku) => {setTableData((prev) => prev.filter((r) => String(Object.values(r)[0]) !== String(deletedSku))); setTotalData((prev) => prev.filter((r) => String(Object.values(r)[0]) !== String(deletedSku)));}} itemIds={itemIds} itemTitles={itemTitles} itemModelNumbers={itemModelNumbers} />);})} </tbody> </table> <div className="pagination"> <button onClick={(e) => {setShowtable(false);}} className="btn" > Back to home </button> <span> page {page > total ? total : page} of {total} </span> {page < total && (<button onClick={() => setPage((p) => p + 1)} className="btn"> Load Next </button>)} <button onClick={(e) => uploadPackages(e)} className="btn"> Save to packages </button> </div> </>) : (<div className="tiles"> {sheetdata && sheetnames && sheetnames.map((name,i) => (<div className="tile" key={i} onClick={() => parseSheet(sheetdata[name])} > <div className="sheet"> <span>{name}</span> </div> </div>))} </div>)} </div>)}export default App;{}
