Pipe Stock (Production Report)
Wed Mar 12 2025 10:06:38 GMT+0000 (Coordinated Universal Time)
Saved by @SrijanVerma
import React, { useState, useEffect } from 'react' import * as XLSX from 'xlsx'; import axios from 'axios'; import { HiArrowSmRight, HiArrowSmDown } from "react-icons/hi"; import { FaDownload, FaTimes } from "react-icons/fa"; import { FaArrowLeft } from "react-icons/fa"; import { useNavigate } from 'react-router-dom'; import Apis from '../../../APIs'; import StyledWrapperRed from '../Ticketing/StyledWrapperRed'; const PipeStockReport = () => { const [data, setData] = useState([]); const [thicknesses, setThicknesses] = useState([]); const [sizes, setSizes] = useState([]); const [stockAgingData, setStockAgingData] = useState([]); const [weightUnit, setWeightUnit] = useState("MT"); const [dateRange, setDateRange] = useState({ startDate: null, endDate: null, }); const [loading, setLoading] = useState(true); // Add loading state const navigate = useNavigate(); const [reportType, setReportType] = useState("weight"); const [selectedReport, setSelectedReport] = useState("stock"); //for showing User Name : const [userModalOpen, setUserModalOpen] = useState(false); const [selectedUserName, setSelectedUserName] = useState(""); const [showAvailablePipes, setShowAvailablePipes] = useState(false); // Fetch data from the backend useEffect(() => { const fetchData = async () => { try { setLoading(true); const endpoint = Apis.PIPE_STOCK; let response; if (selectedReport === "stock") { if (dateRange.startDate && dateRange.endDate) { // Fetch data based on date range response = await axios.get(endpoint, { params: { startDate: dateRange.startDate, endDate: dateRange.endDate, }, }); } else { // Fetch all data if no date range is selected response = await axios.get(endpoint); // console.log("Hr Stock : ", response.data); } const backendData = response.data; // Extract unique thicknesses and sizes const thicknessList = backendData.map((item) => item.pipeLotThickness); const sizeList = [ ...new Set( backendData.flatMap((item) => item.pipeLotSizes.map((sizeObj) => sizeObj.pipeLotSize)) ), ]; setData(backendData); setThicknesses(thicknessList); setSizes(sizeList); } else { // Fetch Stock Aging Report Data const agingApiUrl = Apis.PIPE_AGING; response = dateRange.startDate && dateRange.endDate ? await axios.get(agingApiUrl, { params: { startDate: dateRange.startDate, endDate: dateRange.endDate } }) : await axios.get(agingApiUrl); setStockAgingData(response.data); // console.log("Hr Stock Aging:", response.data); } } catch (error) { console.error('Error fetching data:', error); } finally { setLoading(false); } }; fetchData(); }, [dateRange, selectedReport]); const handleDateChange = (event) => { const { name, value } = event.target; setDateRange((prev) => ({ ...prev, [name]: value, })); }; const clearDateRange = () => { setDateRange({ startDate: null, endDate: null }); }; // Fetch User Details const fetchUserNameByID = async (_id) => { setLoading(true); try { const response = await axios.get( `${Apis.FIND_USER_NAME}/${_id}` ); // console.log(response.data); if (response.data) { setSelectedUserName(response.data); setUserModalOpen(true); } } catch (error) { console.error("Error fetching user name details:", error); } finally { setLoading(false); } }; // Generate table content orders: const generateTableContent = () => { return sizes.map((size) => ( <tr key={size}> {/* Size as the first column */} <td className="border border-gray-300 px-2 py-1 text-center bg-gray-100 font-medium"> {size} </td> {/* Map thicknesses to find matching quantities */} {thicknesses.map((thick) => { const matchingItem = data .find((item) => item.pipeLotThickness === thick) ?.pipeLotSizes.find((sizeObj) => sizeObj.pipeLotSize === size); let displayValue = "-"; // Default if no matching data if (matchingItem) { if (reportType === "weight") { displayValue = weightUnit === "MT" ? (matchingItem.lotWeight / 1000).toFixed(2) : matchingItem.lotWeight.toFixed(2); } else { displayValue = matchingItem.noOfPipes; // Display No. of Pipes } } return ( <td key={thick} className="border border-gray-300 px-2 py-1 text-center text-sm" > {displayValue || "-"} </td> ); })} </tr> )); }; // Generate Table Content for Stock Aging Report const generateStockAgingTableContent = () => { return stockAgingData.filter(item => showAvailablePipes ? item.noOfPipe > 0 : true).map((item) => ( <tr key={item._id}> {/* <td className="border border-gray-300 px-2 py-1 text-center">{item._id}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.uniqueId}</td> */} <td className="border border-gray-300 px-2 py-1 text-center">{item.pipeSize}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.msgi}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.length}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.pipeIs}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.grade}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.thickness}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.noOfPipe}</td> <td className="border border-gray-300 px-2 py-1 text-center">{(weightUnit === "MT" ? (item.weight / 1000).toFixed(2) : item.weight)}</td> <td className="border border-gray-300 px-2 py-1 text-center">{(weightUnit === "MT" ? (item.unitWeight / 1000).toFixed(5) : item.unitWeight)}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.pipeStatus}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.pipeClass}</td> {/* <td className="border border-gray-300 px-2 py-1 text-center">{item.division}</td> */} <td className="border border-gray-300 px-2 py-1 text-center">{item.endType}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.vwv}</td> <td className="border border-gray-300 px-2 py-1 text-center">{item.pipeType}</td> <td className="border border-gray-300 px-2 py-1 text-center">{(weightUnit === "MT" ? (item.weightPerPc / 1000).toFixed(5) : item.weightPerPc)}</td> {/* <td className="border border-gray-300 px-2 py-1 text-center"> {item.pipeLotModelList.length > 0 ? ( <ul className="list-none"> {item.pipeLotModelList.map((lot, index) => ( <li key={index} className="text-xs text-gray-600">{lot}</li> ))} </ul> ) : "-"} </td> */} <td className="border border-gray-300 px-2 py-1 text-center font-semibold text-emerald-500 cursor-pointer hover:underline" onClick={(e) => { e.stopPropagation(); fetchUserNameByID(item.createdBy); }}> {item.createdBy} </td> <td className="border border-gray-300 px-2 py-1 text-center">{new Date(new Date(item.createdAt).getTime() + 330 * 60000).toLocaleString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true, })}</td> <td className="border border-gray-300 px-2 py-1 text-center">{new Date(new Date(item.updatedAt).getTime() + 330 * 60000).toLocaleString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true, })}</td> {/* Lot Age Calculation */} <td className="border border-gray-300 px-2 py-1 text-center"> {(() => { const createdAt = new Date(item.createdAt); const updatedAt = new Date(item.updatedAt); const diffMs = updatedAt - createdAt; const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); return `${diffDays}d ${diffHours}h ${diffMinutes}m`; })()} </td> </tr> )); }; // Function to download the table as an Excel file const downloadExcel = () => { // Prepare the data for Excel const headerRow = ['Pipe Size / Thickness', ...thicknesses]; // Add Thicknesses as header const excelData = [ headerRow, // Add header row ...sizes.map((size) => [ size, // Add the size as the first column ...thicknesses.map((thick) => { const matchingItem = data .find((item) => item.pipeLotThickness === thick) ?.pipeLotSizes.find((sizeObj) => sizeObj.pipeLotSize === size); // return matchingItem ? matchingItem.quantityInMt : '-'; // Populate quantity or empty value if (matchingItem) { if (reportType === "weight") { // Convert to MT if selected return weightUnit === "MT" ? (matchingItem.lotWeight / 1000).toFixed(2) // Convert to MT : matchingItem.lotWeight.toFixed(2); // Keep in KG } else { return matchingItem.noOfPipes; // Return No. of Pipes } } return '-'; }), ]), ]; // Create a worksheet and workbook const worksheet = XLSX.utils.aoa_to_sheet(excelData); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet( workbook, worksheet, `Slit Stock Report` ); // Set the file name dynamically const fileName = reportType === "weight" ? `Slit Stock (Weight Report) (${weightUnit}).xlsx` : `Slit Stock (No. of Pipes Report).xlsx`; // Write the workbook to an Excel file XLSX.writeFile(workbook, fileName); }; const downloadStockAgingExcel = () => { if (stockAgingData.length === 0) { alert("No data available to download."); return; } // Define the headers const headers = [ "Pipe Size", "MsGi", "Length", "Pipe IS", "Grade", "Thickness", "No. of Pipe", "Weight", "Unit Weight", "Pipe Status", "Pipe Class", "End Type", "VWV", "Pipe Type", "Weight Per Pc", "Created By", "Created At", "Updated At", "Lot Age" ]; // Map the data into an array format for Excel const excelData = stockAgingData .filter(item => showAvailablePipes ? item.noOfPipe > 0 : true) // Apply filter based on toggle .map((item) => { const createdAt = new Date(new Date(item.createdAt).getTime() + 330 * 60000).toLocaleString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true }); const updatedAt = new Date(new Date(item.updatedAt).getTime() + 330 * 60000).toLocaleString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true }); // Calculate Lot Age const diffMs = new Date(item.updatedAt) - new Date(item.createdAt); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); const lotAge = `${diffDays}d ${diffHours}h ${diffMinutes}m`; return [ item.pipeSize, item.msgi, item.length, item.pipeIs, item.grade, item.thickness, item.noOfPipe, weightUnit === "MT" ? (item.weight / 1000).toFixed(2) : item.weight, weightUnit === "MT" ? (item.unitWeight / 1000).toFixed(5) : item.unitWeight, item.pipeStatus, item.pipeClass, item.endType, item.vwv, item.pipeType, weightUnit === "MT" ? (item.weightPerPc / 1000).toFixed(5) : item.weightPerPc, item.createdBy, createdAt, updatedAt, lotAge ]; }); // Create a worksheet and workbook const worksheet = XLSX.utils.aoa_to_sheet([headers, ...excelData]); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, "Stock Aging Report"); // Set the file name dynamically const fileName = `Stock_Aging_Report_${weightUnit}.xlsx`; // Write and download the Excel file XLSX.writeFile(workbook, fileName); }; return ( <> <div className="flex flex-col items-center bg-gray-50 min-h-screen"> <header className="w-full bg-red-500 text-white py-6"> <div className="container mx-auto flex items-center justify-between px-4 relative"> {/* Back Button - Visible Only on Mobile */} <button onClick={() => navigate(-1)} className="lg:hidden absolute left-1 flex items-center gap-2 px-4 py-2 rounded-lg shadow hover:bg-green-100 transition" > <FaArrowLeft className="text-lg" /> </button> {/* Centered Heading */} <div className="flex-grow text-center"> <h1 className="text-3xl font-bold"> Pipe Stock Report </h1> <p className="text-sm mt-2">A detailed Pipe stock report table</p> </div> </div> </header> {/* Radio Button for Report Selection */} <div className="mt-6 mb-4 flex justify-center"> <div className="bg-white shadow-lg rounded-xl p-2 flex space-x-2"> <label className={`relative flex items-center justify-center px-6 py-3 rounded-lg cursor-pointer transition-all duration-200 ${selectedReport === "stock" ? "bg-red-500 text-white font-bold shadow-md" : "bg-gray-100 text-gray-700 hover:bg-gray-200" }`} > <input type="radio" value="stock" checked={selectedReport === "stock"} onChange={() => setSelectedReport("stock")} className="absolute opacity-0" /> <div className="flex items-center"> {selectedReport === "stock" && ( <div className="absolute -left-1 -top-1 w-3 h-3 bg-red-500 rounded-full animate-ping"></div> )} <span className="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /> </svg> Pipe Stock Report </span> </div> </label> <label className={`relative flex items-center justify-center px-6 py-3 rounded-lg cursor-pointer transition-all duration-200 ${selectedReport === "aging" ? "bg-red-500 text-white font-bold shadow-md" : "bg-gray-100 text-gray-700 hover:bg-gray-200" }`} > <input type="radio" value="aging" checked={selectedReport === "aging"} onChange={() => setSelectedReport("aging")} className="absolute opacity-0" /> <div className="flex items-center"> {selectedReport === "aging" && ( <div className="absolute -left-1 -top-1 w-3 h-3 bg-red-500 rounded-full animate-ping"></div> )} <span className="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> Stock Aging Report </span> </div> </label> </div> </div> {loading ? ( <StyledWrapperRed className='mt-auto'> <div className="loader"> <div> <ul> <li> <svg fill="currentColor" viewBox="0 0 90 120"> <path d="M90,0 L90,120 L11,120 C4.92486775,120 0,115.075132 0,109 L0,11 C0,4.92486775 4.92486775,0 11,0 L90,0 Z M71.5,81 L18.5,81 C17.1192881,81 16,82.1192881 16,83.5 C16,84.8254834 17.0315359,85.9100387 18.3356243,85.9946823 L18.5,86 L71.5,86 C72.8807119,86 74,84.8807119 74,83.5 C74,82.1745166 72.9684641,81.0899613 71.6643757,81.0053177 L71.5,81 Z M71.5,57 L18.5,57 C17.1192881,57 16,58.1192881 16,59.5 C16,60.8254834 17.0315359,61.9100387 18.3356243,61.9946823 L18.5,62 L71.5,62 C72.8807119,62 74,60.8807119 74,59.5 C74,58.1192881 72.8807119,57 71.5,57 Z M71.5,33 L18.5,33 C17.1192881,33 16,34.1192881 16,35.5 C16,36.8254834 17.0315359,37.9100387 18.3356243,37.9946823 L18.5,38 L71.5,38 C72.8807119,38 74,36.8807119 74,35.5 C74,34.1192881 72.8807119,33 71.5,33 Z" /> </svg> </li> <li> <svg fill="currentColor" viewBox="0 0 90 120"> <path d="M90,0 L90,120 L11,120 C4.92486775,120 0,115.075132 0,109 L0,11 C0,4.92486775 4.92486775,0 11,0 L90,0 Z M71.5,81 L18.5,81 C17.1192881,81 16,82.1192881 16,83.5 C16,84.8254834 17.0315359,85.9100387 18.3356243,85.9946823 L18.5,86 L71.5,86 C72.8807119,86 74,84.8807119 74,83.5 C74,82.1745166 72.9684641,81.0899613 71.6643757,81.0053177 L71.5,81 Z M71.5,57 L18.5,57 C17.1192881,57 16,58.1192881 16,59.5 C16,60.8254834 17.0315359,61.9100387 18.3356243,61.9946823 L18.5,62 L71.5,62 C72.8807119,62 74,60.8807119 74,59.5 C74,58.1192881 72.8807119,57 71.5,57 Z M71.5,33 L18.5,33 C17.1192881,33 16,34.1192881 16,35.5 C16,36.8254834 17.0315359,37.9100387 18.3356243,37.9946823 L18.5,38 L71.5,38 C72.8807119,38 74,36.8807119 74,35.5 C74,34.1192881 72.8807119,33 71.5,33 Z" /> </svg> </li> <li> <svg fill="currentColor" viewBox="0 0 90 120"> <path d="M90,0 L90,120 L11,120 C4.92486775,120 0,115.075132 0,109 L0,11 C0,4.92486775 4.92486775,0 11,0 L90,0 Z M71.5,81 L18.5,81 C17.1192881,81 16,82.1192881 16,83.5 C16,84.8254834 17.0315359,85.9100387 18.3356243,85.9946823 L18.5,86 L71.5,86 C72.8807119,86 74,84.8807119 74,83.5 C74,82.1745166 72.9684641,81.0899613 71.6643757,81.0053177 L71.5,81 Z M71.5,57 L18.5,57 C17.1192881,57 16,58.1192881 16,59.5 C16,60.8254834 17.0315359,61.9100387 18.3356243,61.9946823 L18.5,62 L71.5,62 C72.8807119,62 74,60.8807119 74,59.5 C74,58.1192881 72.8807119,57 71.5,57 Z M71.5,33 L18.5,33 C17.1192881,33 16,34.1192881 16,35.5 C16,36.8254834 17.0315359,37.9100387 18.3356243,37.9946823 L18.5,38 L71.5,38 C72.8807119,38 74,36.8807119 74,35.5 C74,34.1192881 72.8807119,33 71.5,33 Z" /> </svg> </li> <li> <svg fill="currentColor" viewBox="0 0 90 120"> <path d="M90,0 L90,120 L11,120 C4.92486775,120 0,115.075132 0,109 L0,11 C0,4.92486775 4.92486775,0 11,0 L90,0 Z M71.5,81 L18.5,81 C17.1192881,81 16,82.1192881 16,83.5 C16,84.8254834 17.0315359,85.9100387 18.3356243,85.9946823 L18.5,86 L71.5,86 C72.8807119,86 74,84.8807119 74,83.5 C74,82.1745166 72.9684641,81.0899613 71.6643757,81.0053177 L71.5,81 Z M71.5,57 L18.5,57 C17.1192881,57 16,58.1192881 16,59.5 C16,60.8254834 17.0315359,61.9100387 18.3356243,61.9946823 L18.5,62 L71.5,62 C72.8807119,62 74,60.8807119 74,59.5 C74,58.1192881 72.8807119,57 71.5,57 Z M71.5,33 L18.5,33 C17.1192881,33 16,34.1192881 16,35.5 C16,36.8254834 17.0315359,37.9100387 18.3356243,37.9946823 L18.5,38 L71.5,38 C72.8807119,38 74,36.8807119 74,35.5 C74,34.1192881 72.8807119,33 71.5,33 Z" /> </svg> </li> <li> <svg fill="currentColor" viewBox="0 0 90 120"> <path d="M90,0 L90,120 L11,120 C4.92486775,120 0,115.075132 0,109 L0,11 C0,4.92486775 4.92486775,0 11,0 L90,0 Z M71.5,81 L18.5,81 C17.1192881,81 16,82.1192881 16,83.5 C16,84.8254834 17.0315359,85.9100387 18.3356243,85.9946823 L18.5,86 L71.5,86 C72.8807119,86 74,84.8807119 74,83.5 C74,82.1745166 72.9684641,81.0899613 71.6643757,81.0053177 L71.5,81 Z M71.5,57 L18.5,57 C17.1192881,57 16,58.1192881 16,59.5 C16,60.8254834 17.0315359,61.9100387 18.3356243,61.9946823 L18.5,62 L71.5,62 C72.8807119,62 74,60.8807119 74,59.5 C74,58.1192881 72.8807119,57 71.5,57 Z M71.5,33 L18.5,33 C17.1192881,33 16,34.1192881 16,35.5 C16,36.8254834 17.0315359,37.9100387 18.3356243,37.9946823 L18.5,38 L71.5,38 C72.8807119,38 74,36.8807119 74,35.5 C74,34.1192881 72.8807119,33 71.5,33 Z" /> </svg> </li> <li> <svg fill="currentColor" viewBox="0 0 90 120"> <path d="M90,0 L90,120 L11,120 C4.92486775,120 0,115.075132 0,109 L0,11 C0,4.92486775 4.92486775,0 11,0 L90,0 Z M71.5,81 L18.5,81 C17.1192881,81 16,82.1192881 16,83.5 C16,84.8254834 17.0315359,85.9100387 18.3356243,85.9946823 L18.5,86 L71.5,86 C72.8807119,86 74,84.8807119 74,83.5 C74,82.1745166 72.9684641,81.0899613 71.6643757,81.0053177 L71.5,81 Z M71.5,57 L18.5,57 C17.1192881,57 16,58.1192881 16,59.5 C16,60.8254834 17.0315359,61.9100387 18.3356243,61.9946823 L18.5,62 L71.5,62 C72.8807119,62 74,60.8807119 74,59.5 C74,58.1192881 72.8807119,57 71.5,57 Z M71.5,33 L18.5,33 C17.1192881,33 16,34.1192881 16,35.5 C16,36.8254834 17.0315359,37.9100387 18.3356243,37.9946823 L18.5,38 L71.5,38 C72.8807119,38 74,36.8807119 74,35.5 C74,34.1192881 72.8807119,33 71.5,33 Z" /> </svg> </li> </ul> </div><span>Loading</span></div> </StyledWrapperRed> ) : ( <> <div className="mt-8 mb-6 flex flex-col items-center"> <div className="w-full max-w-4xl bg-white rounded-xl shadow-lg p-6"> {/* Toggle Controls */} <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> {/* Report Type Toggle */} {selectedReport === "stock" && ( <div className="bg-gray-50 rounded-lg p-4 shadow-sm"> <h3 className="text-gray-700 font-medium mb-3 flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-red-500" viewBox="0 0 20 20" fill="currentColor" > <path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zm6-4a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zm6-3a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" /> </svg> Report Type </h3> <div className="flex items-center justify-between bg-white rounded-lg p-3 shadow-inner"> <span className={`text-sm font-medium ${reportType === "pipes" ? "text-red-500" : "text-gray-500"}`} > No. of Pipes </span> <div className="relative mx-3"> <label className="flex items-center cursor-pointer"> <input type="checkbox" className="sr-only peer" checked={reportType === "weight"} onChange={() => setReportType(reportType === "weight" ? "pipes" : "weight")} /> <div className="relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-red-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:bg-red-500 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-6 after:w-6 after:shadow-md after:transition-all duration-300 ease-in-out"></div> </label> <div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full animate-ping opacity-75" style={{ display: reportType === "weight" ? "block" : "none" }} ></div> </div> <span className={`text-sm font-medium ${reportType === "weight" ? "text-red-500" : "text-gray-500"}`} > Weight </span> </div> </div> )} {/* Weight Unit Toggle */} {reportType === "weight" && ( <div className="bg-gray-50 rounded-lg p-4 shadow-sm"> <h3 className="text-gray-700 font-medium mb-3 flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-red-500" viewBox="0 0 20 20" fill="currentColor" > <path fillRule="evenodd" d="M10 2a1 1 0 011 1v1.323l3.954 1.582 1.599-.8a1 1 0 01.894 1.79l-1.233.616 1.738 5.42a1 1 0 01-.285 1.05A3.989 3.989 0 0115 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.715-5.349L11 6.477V16h2a1 1 0 110 2H7a1 1 0 110-2h2V6.477L6.237 7.582l1.715 5.349a1 1 0 01-.285 1.05A3.989 3.989 0 015 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.738-5.42-1.233-.617a1 1 0 01.894-1.788l1.599.799L9 4.323V3a1 1 0 011-1z" clipRule="evenodd" /> </svg> Weight Unit </h3> <div className="flex items-center justify-between bg-white rounded-lg p-3 shadow-inner"> <span className={`text-sm font-medium ${weightUnit === "Kg" ? "text-red-500" : "text-gray-500"}`}> Kilograms (Kg) </span> <div className="relative mx-3"> <label className="flex items-center cursor-pointer"> <input type="checkbox" className="sr-only peer" checked={weightUnit === "MT"} onChange={() => setWeightUnit(weightUnit === "Kg" ? "MT" : "Kg")} /> <div className="relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-red-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:bg-red-500 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-6 after:w-6 after:shadow-md after:transition-all duration-300 ease-in-out"></div> </label> <div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full animate-ping opacity-75" style={{ display: weightUnit === "MT" ? "block" : "none" }} ></div> </div> <span className={`text-sm font-medium ${weightUnit === "MT" ? "text-red-500" : "text-gray-500"}`}> Metric Tons (MT) </span> </div> </div> )} {/* Available Pipes Toggle */} {selectedReport === "aging" && ( <div className="bg-gray-50 rounded-lg p-4 shadow-sm"> <h3 className="text-gray-700 font-medium mb-3 flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-red-500" viewBox="0 0 20 20" fill="currentColor" > <path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z" /> <path fillRule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z" clipRule="evenodd" /> </svg> Filter Pipes </h3> <div className="flex items-center justify-between bg-white rounded-lg p-3 shadow-inner"> <span className={`text-sm font-medium ${showAvailablePipes ? "text-red-500" : "text-gray-500"}`}> Available Only </span> <div className="relative mx-3"> <label className="flex items-center cursor-pointer"> <input type="checkbox" className="sr-only peer" checked={!showAvailablePipes} onChange={() => setShowAvailablePipes(!showAvailablePipes)} /> <div className="relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-red-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:bg-red-500 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-6 after:w-6 after:shadow-md after:transition-all duration-300 ease-in-out"></div> </label> <div className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full animate-ping opacity-75" style={{ display: !showAvailablePipes ? "block" : "none" }} ></div> </div> <span className={`text-sm font-medium ${!showAvailablePipes ? "text-red-500" : "text-gray-500"}`}> All Pipes </span> </div> </div> )} </div> {/* Date Range Picker */} {selectedReport === "aging" && ( <div className="bg-gray-50 rounded-lg p-4 shadow-sm mb-6"> <h3 className="text-gray-700 font-medium mb-3 flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-red-500" viewBox="0 0 20 20" fill="currentColor" > <path fillRule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clipRule="evenodd" /> </svg> Date Range </h3> <div className="grid grid-cols-1 md:grid-cols-3 gap-4 items-center"> <div className="relative"> <label htmlFor="datepicker-range-start" className="block text-xs font-medium text-gray-700 mb-1"> Start Date </label> <div className="relative"> <input id="datepicker-range-start" name="startDate" type="date" value={dateRange.startDate || ""} onChange={handleDateChange} className="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-red-500 focus:border-red-500 block w-full pl-10 pr-3 py-2.5 shadow-sm" placeholder="Select start date" /> <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <svg className="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > <path fillRule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clipRule="evenodd" ></path> </svg> </div> </div> </div> <div className="relative"> <label htmlFor="datepicker-range-end" className="block text-xs font-medium text-gray-700 mb-1"> End Date </label> <div className="relative"> <input id="datepicker-range-end" name="endDate" type="date" value={dateRange.endDate || ""} onChange={handleDateChange} className="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-red-500 focus:border-red-500 block w-full pl-10 pr-3 py-2.5 shadow-sm" placeholder="Select end date" /> <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <svg className="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > <path fillRule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clipRule="evenodd" ></path> </svg> </div> </div> </div> <div className="flex items-end"> <button className="w-full bg-white border border-red-500 text-red-500 hover:bg-red-50 px-4 py-2.5 rounded-lg shadow-sm transition-colors duration-200 flex items-center justify-center" onClick={clearDateRange} > <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-1.5" viewBox="0 0 20 20" fill="currentColor" > <path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" /> </svg> Clear Range </button> </div> </div> {/* Date Range Info */} <div className="mt-3 text-center"> <p className="text-sm text-gray-600 bg-white px-3 py-1.5 rounded-md inline-block shadow-sm"> {dateRange.startDate && dateRange.endDate ? ( <span className="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1 text-red-500" viewBox="0 0 20 20" fill="currentColor" > <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> </svg> Selected Range:{" "} <span className="font-medium ml-1"> {dateRange.startDate} - {dateRange.endDate} </span> </span> ) : ( <span className="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1 text-blue-500" viewBox="0 0 20 20" fill="currentColor" > <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" /> </svg> Showing All Data </span> )} </p> </div> </div> )} {/* Weight Unit Info & Download Button */} <div className="flex flex-col sm:flex-row items-center justify-between gap-4 bg-gray-50 rounded-lg p-4 shadow-sm"> <div className="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-red-500" viewBox="0 0 20 20" fill="currentColor" > <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" /> </svg> <span className="font-medium text-gray-700"> All weights are in <span className="text-red-500 font-bold">{weightUnit}</span> </span> </div> <button onClick={selectedReport === "stock" ? downloadExcel : downloadStockAgingExcel} className="group bg-red-500 hover:bg-red-600 text-white font-medium px-6 py-2.5 rounded-lg shadow-lg transition-all duration-200 ease-in-out transform hover:scale-105 active:scale-95 flex items-center justify-center min-w-[180px]" > <FaDownload className="text-lg mr-2 group-hover:animate-bounce" /> <span>Download Excel</span> <div className="absolute -top-1 -right-1 w-3 h-3 bg-white rounded-full animate-ping opacity-75 hidden group-hover:block"></div> </button> </div> </div> </div> {userModalOpen && ( <div className="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-50"> <div className="fixed inset-0 bg-opacity-50" onClick={() => setUserModalOpen(false)}></div> <div className="bg-white rounded-lg shadow-lg p-8 w-96 text-center transform transition-all scale-100"> <div className="flex justify-end"> <button className="text-gray-600 hover:text-gray-800" onClick={() => setUserModalOpen(false)}> <FaTimes size={20} /> </button> </div> <h3 className="text-2xl font-bold text-gray-900 mt-2">User Details</h3> <p className="text-lg text-gray-700 mt-4">👤 {selectedUserName}</p> <button className="mt-6 px-6 py-2 bg-blue-500 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 transition" onClick={() => setUserModalOpen(false)}> Close </button> </div> </div> )} {/* Main Table */} <main className="container mx-auto py-8"> <div className="overflow-auto max-w-full"> <table className="table-auto border-collapse border border-gray-300 mx-auto bg-white shadow-lg"> <thead> <tr> {selectedReport === "stock" ? ( <> {/* First column header for "Thickness / Pipe Lot Sizes" */} <th className="border border-gray-300 px-4 py-2 text-center bg-gray-200 text-sm font-medium"> <div className="flex items-center justify-center space-x-2"> <span>Pipe Lot Thickness</span> <HiArrowSmRight className="text-blue-600" /> <span>/</span> <span> Pipe Lot Sizes</span> <HiArrowSmDown className="text-blue-600" /> </div> </th> {/* Dynamically render column headers for thickness */} {thicknesses.map((thick) => ( <th key={thick} className="border border-gray-300 px-2 py-1 text-center bg-gray-200 text-sm" > {thick} </th> ))} </> ) : ( <> {/* Headers for Stock Aging Report */} {/* <th className="border px-4 py-2"> ID </th> <th className="border px-4 py-2"> Unique ID</th> */} <th className="border px-4 py-2">Pipe Size</th> <th className="border px-4 py-2"> MsGi </th> <th className="border px-4 py-2"> Length </th> <th className="border px-4 py-2">Pipe IS</th> <th className="border px-4 py-2"> Grade </th> <th className="border px-4 py-2"> Thickness </th> <th className="border px-4 py-2"> No. of Pipe </th> <th className="border px-4 py-2"> Weight </th> <th className="border px-4 py-2">Unit Weight</th> <th className="border px-4 py-2">Pipe Status</th> <th className="border px-4 py-2"> Pipe Class </th> {/* <th className="border px-4 py-2"> Division </th> */} <th className="border px-4 py-2"> End Type </th> <th className="border px-4 py-2"> VWV </th> <th className="border px-4 py-2"> Pipe Type </th> <th className="border px-4 py-2">Weight Per Pc</th> {/* <th className="border px-4 py-2">PipeLot Modal List</th> */} <th className="border px-4 py-2">Created By</th> <th className="border px-4 py-2">Created At</th> <th className="border px-4 py-2"> Updated At</th> <th className="border px-4 py-2"> Lot Age </th> </> )} </tr> </thead> <tbody> {selectedReport === "stock" ? generateTableContent() : generateStockAgingTableContent()} </tbody> </table> </div> </main> </> )} {/* Footer */} <footer className="w-full bg-gray-800 text-white py-4 mt-auto"> <div className="container mx-auto text-center"> <p className="text-sm"> © {new Date().getFullYear()} Dynamic Report System. All Rights Reserved. </p> </div> </footer> </div> </> ) } export default PipeStockReport
Comments