Untitled

 avatar
unknown
plain_text
23 days ago
43 kB
2
Indexable
{% extends "base.html" %}
{% load static %}

{% block title %}Takeoff Screen{% endblock %}

{% block content %}
<div class="takeoff-container">
    <div class="takeoff-content">
        <!-- Sidebar -->
        <div class="takeoff-sidebar">
            <!-- Project Selector -->
            <div class="sidebar-section">
                <label for="project-selector" class="form-label small">Project:</label>
                <select id="project-selector" class="form-select form-select-sm" onchange="changeProject(this.value)">
                    <option value="{{ project.id }}" selected>{{ project.name }}</option>
                    {% for proj in all_projects %}
                        {% if proj.id != project.id %}
                            <option value="{{ proj.id }}">{{ proj.name }}</option>
                        {% endif %}
                    {% endfor %}
                </select>
            </div>
            
            <!-- PDF Selector -->
            <div class="sidebar-section">
                <label for="pdf-selector" class="form-label small">PDF Document:</label>
                <select id="pdf-selector" class="form-select form-select-sm" onchange="changePDF(this.value)">
                    {% for pdf in pdfs %}
                        <option value="{{ pdf.id }}" data-url="{{ pdf.file.url }}">{{ pdf.file.name|slice:"6:" }}</option>
                    {% empty %}
                        <option disabled>No PDFs available</option>
                    {% endfor %}
                </select>
                <div class="d-grid gap-2 mt-1">
                    <button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#uploadPdfModal">Upload PDF</button>
                </div>
            </div>
            
            <!-- Scale Calibration Button -->
            <div class="sidebar-section">
                <button class="btn btn-outline-secondary btn-sm w-100" onclick="toggleScaleBox()">Set/Calibrate Scale</button>
            </div>
            
            <!-- Measurement Tools -->
            <div class="sidebar-section">
                <h6 class="small fw-bold">Measurement Tools</h6>
                <div class="d-grid gap-1">
                    <button class="btn btn-sm btn-outline-dark" onclick="selectTool('lineal')">Lineal (m)</button>
                    <button class="btn btn-sm btn-outline-dark" onclick="selectTool('area')">Area (m²)</button>
                    <button class="btn btn-sm btn-outline-dark" onclick="selectTool('volume')">Volume (m³)</button>
                    <button class="btn btn-sm btn-outline-dark" onclick="selectTool('count')">Count (ea)</button>
                </div>
                <div class="form-check mt-2">
                    <input class="form-check-input" type="checkbox" value="" id="deduction-check">
                    <label class="form-check-label small" for="deduction-check">
                        Deduction
                    </label>
                </div>
            </div>
            
            <!-- Measurements List -->
            <div class="sidebar-section measurements-container">
                <h6 class="small fw-bold">Measurements</h6>
                <table class="table table-sm small">
                    <thead>
                        <tr>
                            <th>Type</th>
                            <th>Value</th>
                            <th></th>
                        </tr>
                    </thead>
                    <tbody id="measurements-table">
                        <!-- Measurements will be added here dynamically -->
                    </tbody>
                </table>
                <div class="d-flex justify-content-between align-items-center mt-3">
                    <button type="button" class="btn btn-primary btn-sm" onclick="saveMeasurementsAndReturn()">Save and Return to Estimating</button>
                </div>
            </div>
        </div>
        
        <!-- Main Canvas Area -->
        <div class="takeoff-canvas-container">
            <!-- PDF Controls -->
            <div class="pdf-controls">
                <div class="btn-group btn-group-sm me-2">
                    <button class="btn btn-outline-secondary" onclick="zoomOut()"><i class="fas fa-search-minus"></i></button>
                    <button class="btn btn-outline-secondary" onclick="resetZoom()">100%</button>
                    <button class="btn btn-outline-secondary" onclick="zoomIn()"><i class="fas fa-search-plus"></i></button>
                </div>
                <div class="btn-group btn-group-sm">
                    <button class="btn btn-outline-secondary" onclick="fitToWidth()">Fit Width</button>
                    <button class="btn btn-outline-secondary" onclick="fitToPage()">Fit Page</button>
                </div>
                <span class="ms-2 zoom-level">Zoom: 100%</span>
                <div class="ms-3">
                    <span class="small">Page: </span>
                    <select id="page-selector" class="form-select form-select-sm d-inline-block" style="width: auto;" onchange="changePage(this.value)">
                        <!-- Page options will be added dynamically -->
                    </select>
                </div>
            </div>
            
            <!-- Image Viewer -->
            <div id="pdf-container">
                <div id="image-render" class="position-relative">
                    <!-- Loading spinner -->
                    <div id="loading-spinner" class="position-absolute top-50 start-50 translate-middle">
                        <div class="spinner-border text-primary" role="status">
                            <span class="visually-hidden">Loading...</span>
                        </div>
                    </div>
                </div>            
            </div>
            
            <!-- Floating Scale Calibration Box -->
            <div id="scale-calibration" class="position-absolute top-0 start-0 bg-light p-2 m-2 rounded shadow-sm collapse">
                <h6>Set/Calibrate Scale</h6>
                <form method="post" id="scale-form" class="d-flex flex-column">
                    {% csrf_token %}
                    <input type="hidden" name="pdf_id" id="scale-pdf-id">
                    <div class="mb-2">
                        <label for="scale-ratio" class="form-label small">Scale Ratio:</label>
                        <select id="scale-ratio" class="form-select form-select-sm" onchange="handleScaleSelection(this.value)">
                            <option value="1:5">1:5</option>
                            <option value="1:10">1:10</option>
                            <option value="1:20">1:20</option>
                            <option value="1:50">1:50</option>
                            <option value="1:100" selected>1:100</option>
                            <option value="1:200">1:200</option>
                            <option value="custom">Custom</option>
                        </select>
                    </div>
                    <div class="mb-2">
                        <label for="page-size" class="form-label small">Page Size:</label>
                        <select id="page-size" class="form-select form-select-sm" onchange="handlePageSizeChange(this.value)">
                            <option value="A1">A1</option>
                            <option value="A3" selected>A3</option>
                            <option value="A4">A4</option>
                        </select>
                    </div>
                    <div id="custom-scale-input" class="mb-2 collapse">
                        <label for="custom-scale" class="form-label small">Custom Scale:</label>
                        <input type="number" id="custom-scale-value" class="form-control form-control-sm" min="1" step="0.01">
                        <button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="promptBenchmarkMeasurement()">Calculate Scale</button>
                    </div>
                    <div class="d-flex justify-content-between align-items-center">
                        <button type="button" class="btn btn-primary btn-sm" onclick="saveScale()">Save</button>
                        <button type="button" class="btn btn-outline-secondary btn-sm" onclick="toggleScaleBox()">Close</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

<!-- PDF Upload Modal -->
<div class="modal fade" id="uploadPdfModal" tabindex="-1" aria-labelledby="uploadPdfModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="uploadPdfModalLabel">Upload PDF</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                <form method="post" enctype="multipart/form-data" id="pdf-upload-form">
                    {% csrf_token %}
                    {{ pdf_upload_form.as_p }}
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                <button type="button" class="btn btn-primary" onclick="document.getElementById('pdf-upload-form').submit()">Upload</button>
            </div>
        </div>
    </div>
</div>

<style>
  /* Main container styles */
  .takeoff-container {
    position: absolute;
    top: 56px; /* Adjust based on your navbar height */
    bottom: 0;
    left: 0;
    right: 0;
    overflow: hidden;
    margin-top: 1px; /* Added to fix navbar overlap */
    padding-top: 20px; /* Ensure content is pushed below the navbar */
  }
  
  .takeoff-content {
    display: flex;
    height: 100%;
    width: 100%;
  }
  
  /* Sidebar styles */
  .takeoff-sidebar {
    width: 260px;
    min-width: 260px; /* Prevent sidebar from shrinking */
    height: 100%;
    background-color: #f8f9fa;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    border-right: 1px solid #dee2e6;
    z-index: 10;
  }
  
  .sidebar-section {
    padding: 10px;
    border-bottom: 1px solid #dee2e6;
  }
  
  .measurements-container {
    flex-grow: 1;
    overflow-y: auto;
  }
  
  /* Canvas container */
  .takeoff-canvas-container {
    flex-grow: 1;
    height: 100%;
    display: flex;
    flex-direction: column;
    position: relative;
    min-width: 0; /* Allow canvas to shrink if necessary */
  }
  
  /* PDF controls */
  .pdf-controls {
    padding: 8px 10px;
    background-color: #f8f9fa;
    border-bottom: 1px solid #dee2e6;
    display: flex;
    align-items: center;
  }
  
  .zoom-level {
    font-size: 0.9rem;
    color: #6c757d;
  }
  
  /* PDF container */
  #pdf-container {
    flex-grow: 1;
    overflow: auto;
    position: relative;
    background-color: #e0e0e0;
    display: flex;
    align-items: flex-start;
    justify-content: flex-start; /* Changed from center to flex-start */
    padding: 20px;
  }

  #image-render {
    background-color: white;
    box-shadow: 0 0 10px rgba(0,0,0,0.2);
    margin: 0; /* Removed auto margin */
    position: relative;
    transform-origin: top left;
  }
  
  #image-render img {
    display: block; /* Remove any extra space below image */
    max-width: none; /* Prevent Bootstrap from restricting the image size */
  }
  
  #image-render canvas {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none; /* Let events pass through to the image */
  }
  
  /* Style for the floating scale box */
  #scale-calibration {
    z-index: 1000;
    max-width: 250px;
  }
</style>

<script>
  let currentPDFId = null;
  let currentPageIndex = 0;
  let currentTool = 'lineal';
  let isDeduction = false;
  let measurements = [];
  let currentScale = 1.0;
  let pdfImages = []; // Array to store image URLs for each page
  let aspectRatio = 1.414; // Default A4 aspect ratio (width:height = 1:1.414)
  let originalImageSize = { width: 0, height: 0 }; // Store original image dimensions
  let drawingLayer = null; // Canvas for drawing measurements
  let isCalibrationMode = false;
  let calibrationLineStart = null;
  let calibrationLineEnd = null;
  let currentScaleRatio = 100; // Default scale 1:100 (1mm = 100mm)
  let currentPageSize = 'A3'; // Default page size
  let pageSizeMmDimensions = {
    'A1': { width: 841, height: 594 },
    'A3': { width: 420, height: 297 },
    'A4': { width: 297, height: 210 }
  };

  // Initialize on page load
  document.addEventListener('DOMContentLoaded', function() {
    // Make "lineal" measurement active by default
    document.querySelector('.btn-outline-dark').classList.add('active');
    
    // Get the first PDF if available
    const pdfSelector = document.getElementById('pdf-selector');
    if (pdfSelector && pdfSelector.options.length > 0) {
      const selectedOption = pdfSelector.options[0];
      currentPDFId = selectedOption.value;
      loadPDFImages(currentPDFId);
    }
    
    // Set up event listener for deduction checkbox
    document.getElementById('deduction-check').addEventListener('change', function() {
      isDeduction = this.checked;
    });

    // Set up scroll wheel zoom with Ctrl key
    const pdfContainer = document.getElementById('pdf-container');
    pdfContainer.addEventListener('wheel', function(e) {
      // Only zoom if Ctrl key is pressed
      if (e.ctrlKey) {
        e.preventDefault(); // Prevent default scroll behavior
        
        // Calculate new scale based on scroll direction
        const delta = e.deltaY || e.detail || e.wheelDelta;
        
        if (delta > 0) {
          // Zoom out - smaller step for smoother zoom
          const newScale = Math.max(0.1, currentScale - 0.1);
          setZoom(newScale);
        } else {
          // Zoom in - smaller step for smoother zoom
          const newScale = Math.min(5.0, currentScale + 0.1);
          setZoom(newScale);
        }
      }
    });
  });

  // Function to load PDF as images
  function loadPDFImages(pdfId) {
    showLoading(true);
    
    // Call the backend to convert PDF to images
    fetch(`/convert-pdf-to-images/${pdfId}/`)
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                pdfImages = data.images;
                
                // Update page selector
                updatePageSelector(data.page_count);
                
                // Load the first page
                currentPageIndex = 0;
                loadPage(currentPageIndex);
                
                // Fetch measurements for this PDF
                fetchMeasurements(pdfId);
                
                // Update scale form with the current PDF ID
                document.getElementById('scale-pdf-id').value = pdfId;
                
                // Load saved scale if available
                if (data.scale_ratio) {
                    currentScaleRatio = data.scale_ratio;
                    console.log(`Loaded saved scale: 1:${currentScaleRatio}`);
                    
                    // Update the scale ratio dropdown if it matches a standard option
                    const scaleSelect = document.getElementById('scale-ratio');
                    const standardScaleOptions = ['5', '10', '20', '50', '100', '200'];
                    if (standardScaleOptions.includes(currentScaleRatio.toString())) {
                        scaleSelect.value = `1:${currentScaleRatio}`;
                    } else {
                        scaleSelect.value = 'custom';
                        handleScaleSelection('custom');
                        const customScaleInput = document.getElementById('custom-scale');
                        if (customScaleInput) {
                            customScaleInput.value = currentScaleRatio;
                        }
                    }
                } else {
                    // Default to 1:100 for newly uploaded PDFs
                    currentScaleRatio = 100;
                }
            } else {
                // Display the specific error from the server
                if (data.error && data.error.includes('poppler-utils')) {
                    showError('Failed to convert PDF: Missing required dependency "poppler-utils". Please install it and try again.');
                } else {
                    showError(`Failed to convert PDF: ${data.error || 'Unknown error'}`);
                }
                console.error('Error details:', data.error);
            }
        })
        .catch(error => {
            console.error('Network or parsing error:', error);
            showError('Network error converting PDF. Please check your connection and try again.');
        });
  }
  
  // Update page selector dropdown
  function updatePageSelector(pageCount) {
    const pageSelector = document.getElementById('page-selector');
    pageSelector.innerHTML = '';
    
    for (let i = 0; i < pageCount; i++) {
      const option = document.createElement('option');
      option.value = i;
      option.textContent = `Page ${i + 1}`;
      pageSelector.appendChild(option);
    }
  }
  
  // Function to load a specific page
  function loadPage(pageIndex) {
    if (pageIndex < 0 || pageIndex >= pdfImages.length) {
      return;
    }
    
    currentPageIndex = pageIndex;
    
    // Update page selector
    const pageSelector = document.getElementById('page-selector');
    pageSelector.value = pageIndex;
    
    showLoading(true);
    
    // Clear the image container
    const imageContainer = document.getElementById('image-render');
    imageContainer.innerHTML = '';
    
    // Create image element
    const img = new Image();
    img.onload = function() {
      // Store original image dimensions
      originalImageSize = {
        width: img.naturalWidth,
        aspectRatio: img.naturalWidth / img.naturalHeight
      };
      
      // Add drawing canvas on top of the image
      setupDrawingLayer(img);
      
      // Apply current zoom
      setZoom(currentScale);
      
      showLoading(false);
    };
    
    img.onerror = function() {
      showError('Failed to load image');
    };
    
    // Set image source
    img.src = pdfImages[pageIndex];
    img.id = 'pdf-image';
    imageContainer.appendChild(img);
  }
  
  // Setup the drawing canvas layer
  function setupDrawingLayer(imageElement) {
    const imageContainer = document.getElementById('image-render');
    
    // Create canvas element for drawing
    const canvas = document.createElement('canvas');
    canvas.width = imageElement.naturalWidth;
    canvas.height = imageElement.naturalHeight;
    canvas.id = 'drawing-layer';
    canvas.style.pointerEvents = 'auto'; // Enable mouse events
    
    // Add canvas to container
    imageContainer.appendChild(canvas);
    
    // Store the canvas context
    drawingLayer = canvas.getContext('2d');
    
    // Initialize drawing on the canvas
    initializeDrawing(canvas);
    
    // Redraw measurements
    redrawMeasurements();
  }
  
  // Function to change PDF
  function changePDF(pdfId) {
    if (!pdfId || pdfId === currentPDFId) return;
    
    currentPDFId = pdfId;
    loadPDFImages(pdfId);
  }
  
  // Function to change page
  function changePage(pageIndex) {
    loadPage(parseInt(pageIndex, 10));
  }
  
  // Toggle scale calibration box
  function toggleScaleBox() {
    const scaleBox = document.getElementById('scale-calibration');
    scaleBox.classList.toggle('collapse');
    
    // Ensure the PDF ID is set
    if (currentPDFId) {
      document.getElementById('scale-pdf-id').value = currentPDFId;
    }
  }
  
  // Select measurement tool
  function selectTool(tool) {
    currentTool = tool;
    
    // Highlight the selected tool button
    const buttons = document.querySelectorAll('.btn-outline-dark');
    buttons.forEach(btn => {
      btn.classList.remove('active');
      if (btn.textContent.toLowerCase().includes(tool)) {
        btn.classList.add('active');
      }
    });
  }
  
  // Set zoom level
  function setZoom(scale) {
    currentScale = scale;
    
    // Apply zoom to image and canvas
    const imageRender = document.getElementById('image-render');
    const image = document.getElementById('pdf-image');
    const canvas = document.getElementById('drawing-layer');
    
    if (image && canvas) {
      // Calculate new dimensions
      const newWidth = image.naturalWidth * currentScale;
      
      // Apply zoom
      image.style.width = `${newWidth}px`;
      canvas.style.width = `${newWidth}px`;
      canvas.style.height = 'auto';
      
      // Update zoom display
      updateZoomDisplay();
      
      // Redraw measurements at the new scale
      redrawMeasurements();
    }
  }
  
  // Update zoom level display
  function updateZoomDisplay() {
    document.querySelector('.zoom-level').textContent = `Zoom: ${Math.round(currentScale * 100)}%`;
  }
  
  // Zoom control functions
  function zoomIn() {
    setZoom(Math.min(5.0, currentScale + 0.1));
  }
  
  function zoomOut() {
    setZoom(Math.max(0.1, currentScale - 0.1));
  }
  
  function resetZoom() {
    setZoom(1.0);
  }
  
  function fitToWidth() {
    const pdfContainer = document.getElementById('pdf-container');
    const containerWidth = pdfContainer.clientWidth - 40; // Account for padding
    
    if (originalImageSize.width) {
      const newScale = containerWidth / originalImageSize.width;
      setZoom(newScale);
    }
  }
  
  function fitToPage() {
    const pdfContainer = document.getElementById('pdf-container');
    const containerWidth = pdfContainer.clientWidth - 40;
    const containerHeight = pdfContainer.clientHeight - 40;
    
    if (originalImageSize.width) {
      const imageHeight = originalImageSize.width / originalImageSize.aspectRatio;
      
      // Get scale based on width and height
      const scaleWidth = containerWidth / originalImageSize.width;
      const scaleHeight = containerHeight / imageHeight;
      
      // Choose the smaller scale to ensure the entire page fits
      setZoom(Math.min(scaleWidth, scaleHeight));
    }
  }
  
  // Show/hide loading spinner
  function showLoading(show) {
    const spinner = document.getElementById('loading-spinner');
    if (spinner) {
      spinner.style.display = show ? 'block' : 'none';
    }
  }
  
  // Show error message
  function showError(message) {
    showLoading(false);
    const imageRender = document.getElementById('image-render');
    imageRender.innerHTML = `<div class="alert alert-danger m-3">${message}</div>`;
  }
  
  // Initialize drawing on canvas
  function initializeDrawing(canvas) {
    let isDrawing = false;
    let startX, startY, endX, endY;
    
    // Mouse down event
    canvas.addEventListener('mousedown', function(e) {
      const rect = canvas.getBoundingClientRect();
      startX = (e.clientX - rect.left) * (canvas.width / rect.width);
      startY = (e.clientY - rect.top) * (canvas.height / rect.height);
      isDrawing = true;

      if (isCalibrationMode) {
        calibrationLineStart = { x: startX, y: startY };
      }
    });
    
    // Mouse move event
    canvas.addEventListener('mousemove', function(e) {
      if (!isDrawing) return;
      
      const rect = canvas.getBoundingClientRect();
      endX = (e.clientX - rect.left) * (canvas.width / rect.width);
      endY = (e.clientY - rect.top) * (canvas.height / rect.height);
      
      // Clear and redraw
      drawingLayer.clearRect(0, 0, canvas.width, canvas.height);
      
      // Draw temporary measurement
      drawMeasurement(drawingLayer, startX, startY, endX, endY, currentTool, true);
      
      // Redraw existing measurements
      measurements.forEach(m => {
        if (m.coordinates) {
          const coords = m.coordinates;
          
          // Convert from normalized to absolute coordinates
          const startX = coords.startX * canvas.width;
          const startY = coords.startY * canvas.height;
          const endX = coords.endX * canvas.width;
          const endY = coords.endY * canvas.height;
          
          drawMeasurement(
            drawingLayer, 
            startX, 
            startY, 
            endX, 
            endY, 
            m.type, 
            false, 
            m.is_deduction
          );
        }
      });
    });
    
    // Mouse up event
    canvas.addEventListener('mouseup', async function(e) {
      if (!isDrawing) return;
      isDrawing = false;
      
      const rect = canvas.getBoundingClientRect();
      endX = (e.clientX - rect.left) * (canvas.width / rect.width);
      endY = (e.clientY - rect.top) * (canvas.height / rect.height);

      if (isCalibrationMode) {
        calibrationLineEnd = { x: endX, y: endY };
        const pixelLength = calculateMeasurement(
          calibrationLineStart.x,
          calibrationLineStart.y,
          calibrationLineEnd.x,
          calibrationLineEnd.y,
          'lineal'
        );
        const actualLength = prompt('Enter the actual length of the line you drew (in mm):');
        if (actualLength) {
          calculateScaleFromBenchmark(pixelLength, actualLength);
        }
        isCalibrationMode = false;
        enableMeasurementTools();
        return;
      }
      
      // Calculate the measurement based on tool type and scale
      const value = calculateMeasurement(startX, startY, endX, endY, currentTool);
      
      // Add to measurements array
      const measurementData = {
        type: currentTool,
        value: parseFloat(value.toFixed(2)),
        is_deduction: isDeduction,
        coordinates: { 
          startX: startX / canvas.width, 
          startY: startY / canvas.height, 
          endX: endX / canvas.width, 
          endY: endY / canvas.height 
        }
      };
      
      // Save to server
      const savedMeasurement = await saveMeasurement(measurementData);
      if (savedMeasurement) {
        // Add the saved measurement to our local array
        measurements.push({
          id: savedMeasurement.id,
          type: savedMeasurement.type,
          value: savedMeasurement.value,
          is_deduction: savedMeasurement.is_deduction,
          coordinates: savedMeasurement.coordinates
        });
        
        // Update measurements table
        updateMeasurementsTable();
      }
      
      // Redraw everything
      drawingLayer.clearRect(0, 0, canvas.width, canvas.height);
      redrawMeasurements();
    });
  }
  
  // Draw measurement based on tool type
  function drawMeasurement(context, startX, startY, endX, endY, tool, isTemporary, isDeduct) {
    const deductionToUse = isDeduct !== undefined ? isDeduct : isDeduction;
    
    context.beginPath();
    context.strokeStyle = deductionToUse ? 'red' : 'blue';
    context.lineWidth = 2;
    
    switch(tool) {
      case 'lineal':
        // Draw a line
        context.moveTo(startX, startY);
        context.lineTo(endX, endY);
        break;
      case 'area':
        // Draw a rectangle
        context.rect(startX, startY, endX - startX, endY - startY);
        if (!isTemporary) {
          context.fillStyle = deductionToUse ? 'rgba(255,0,0,0.2)' : 'rgba(0,0,255,0.2)';
          context.fill();
        }
        break;
      case 'volume':
        // Draw a 3D-like box (simplified)
        const offset = 20;
        context.moveTo(startX, startY);
        context.lineTo(endX, endY);
        context.moveTo(startX, startY);
        context.lineTo(startX + offset, startY - offset);
        context.lineTo(endX + offset, endY - offset);
        context.lineTo(endX, endY);
        context.closePath();
        if (!isTemporary) {
          context.fillStyle = deductionToUse ? 'rgba(255,0,0,0.2)' : 'rgba(0,0,255,0.2)';
          context.fill();
        }
        break;
      case 'count':
        // Draw a circle
        const radius = 10;
        context.arc(startX, startY, radius, 0, 2 * Math.PI);
        if (!isTemporary) {
          context.fillStyle = deductionToUse ? 'rgba(255,0,0,0.7)' : 'rgba(0,0,255,0.7)';
          context.fill();
        }
        break;
    }
    
    context.stroke();
    
    // Add label if not temporary
    if (!isTemporary) {
      const measurement = calculateMeasurement(startX, startY, endX, endY, tool);
      context.font = '28px Arial';
      context.fillStyle = 'black';
      context.fillText(`${measurement.toFixed(2)} ${getMeasurementUnit(tool)}`, (startX + endX) / 2, (startY + endY) / 2);
    }
  }
  
  // Calculate measurement based on tool type, coordinates, and scale
  function calculateMeasurement(startX, startY, endX, endY, tool) {
    // Get pixel measurements
    let pixelMeasurement;
    switch(tool) {
      case 'lineal':
        // Distance formula
        pixelMeasurement = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
        break;
      case 'area':
        // Area of rectangle
        pixelMeasurement = Math.abs((endX - startX) * (endY - startY));
        break;
      case 'volume':
        // Volume of box (with assumed depth)
        const depth = 20; // Example assumed depth in pixels
        pixelMeasurement = Math.abs((endX - startX) * (endY - startY) * depth);
        break;
      case 'count':
        // Just return 1 for count (no scaling needed)
        return 1;
      default:
        return 0;
    }
    
    // Get the pixel-to-mm conversion factor based on the current page size
    const pixelToMm = getPixelToMmConversionFactor();
    
    // Convert pixel measurement to mm then apply scale
    let actualMeasurement;
    switch(tool) {
      case 'lineal':
        // Convert to meters with scale
        actualMeasurement = (pixelMeasurement * pixelToMm * currentScaleRatio) / 1000;
        break;
      case 'area':
        // Convert to square meters with scale
        actualMeasurement = (pixelMeasurement * Math.pow(pixelToMm * currentScaleRatio, 2)) / 1000000;
        break;
      case 'volume':
        // Convert to cubic meters with scale
        actualMeasurement = (pixelMeasurement * Math.pow(pixelToMm * currentScaleRatio, 3)) / 1000000000;
        break;
      default:
        actualMeasurement = 0;
    }
    
    return actualMeasurement;
  }
  
  // Get unit based on measurement type
  function getMeasurementUnit(tool) {
    switch(tool) {
      case 'lineal': return 'm';
      case 'area': return 'm²';
      case 'volume': return 'm³';
      case 'count': return 'ea';
      default: return '';
    }
  }
  
  // Update the measurements table
  function updateMeasurementsTable() {
    const tbody = document.getElementById('measurements-table');
    tbody.innerHTML = '';
    
    if (!measurements || measurements.length === 0) {
      const row = document.createElement('tr');
      row.innerHTML = '<td colspan="3" class="text-center">No measurements yet</td>';
      tbody.appendChild(row);
      return;
    }
    
    measurements.forEach(m => {
      const row = document.createElement('tr');
      row.innerHTML = `
        <td>${m.type} ${m.is_deduction ? '(-)' : ''}</td>
        <td>${typeof m.value === 'number' ? m.value.toFixed(2) : m.value} ${getMeasurementUnit(m.type)}</td>
        <td><button class="btn btn-sm btn-danger" onclick="deleteMeasurement(${m.id})">×</button></td>
      `;
      tbody.appendChild(row);
    });
  }
  
  // Delete a measurement
  function deleteMeasurement(id) {
    // Find the measurement to delete
    const measurementToDelete = measurements.find(m => m.id === id);
    if (!measurementToDelete) return;
    
    // Confirm deletion
    if (!confirm("Are you sure you want to delete this measurement?")) {
      return;
    }
    
    // Remove from array
    measurements = measurements.filter(m => m.id !== id);
    
    // Update the table
    updateMeasurementsTable();
    
    // Redraw remaining measurements
    if (drawingLayer) {
      drawingLayer.clearRect(0, 0, drawingLayer.canvas.width, drawingLayer.canvas.height);
      redrawMeasurements();
    }
    
    // Delete from server
    fetch(`/takeoff-screen/${getProjectId()}/delete-measurement/${id}/`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRFToken': getCsrfToken(),
      }
    }).catch(error => console.error('Error deleting measurement:', error));
  }
  
  // Fetch existing measurements
  function fetchMeasurements(pdfId) {
    if (!pdfId) return;
    
    fetch(`/takeoff-screen/${getProjectId()}/measurements/${pdfId}/`)
      .then(response => {
        if (!response.ok) {
          throw new Error('Failed to fetch measurements');
        }
        return response.json();
      })
      .then(data => {
        measurements = data.measurements || [];
        updateMeasurementsTable();
        redrawMeasurements();
      })
      .catch(error => {
        console.error('Error fetching measurements:', error);
      });
  }
  
  // Save a measurement to the database
  async function saveMeasurement(measurementData) {
    try {
      const response = await fetch(`/takeoff-screen/${getProjectId()}/save-measurement/`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRFToken': getCsrfToken(),
        },
        body: JSON.stringify({
          pdf_id: currentPDFId,
          type: measurementData.type,
          value: measurementData.value,
          is_deduction: measurementData.is_deduction,
          coordinates: measurementData.coordinates
        })
      });
      
      if (!response.ok) {
        throw new Error('Failed to save measurement');
      }
      
      const data = await response.json();
      return data;
    } catch (error) {
      console.error('Error saving measurement:', error);
      return null;
    }
  }
  
  // Function to change project
  function changeProject(projectId) {
    if (projectId) {
      window.location.href = `/takeoff-screen/${projectId}/`;
    }
  }
  
  // Get CSRF token from cookie
  function getCsrfToken() {
    const name = 'csrftoken';
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
      const cookies = document.cookie.split(';');
      for (let i = 0; i < cookies.length; i++) {
        const cookie = cookies[i].trim();
        if (cookie.substring(0, name.length + 1) === (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }
  
  // Get the project ID from the URL
  function getProjectId() {
    const pathParts = window.location.pathname.split('/');
    return pathParts[2]; // Assumes URL pattern is /takeoff-screen/:projectId/
  }
  
  // Redraw all measurements on the canvas
  function redrawMeasurements() {
    if (!measurements || measurements.length === 0 || !drawingLayer) return;
    
    const canvas = drawingLayer.canvas;
    drawingLayer.clearRect(0, 0, canvas.width, canvas.height);
    
    // Draw each measurement with its stored coordinates
    measurements.forEach(m => {
      // If we have stored coordinates, use them
      if (m.coordinates) {
        const coords = m.coordinates;
        
        // Convert from normalized to absolute coordinates
        const startX = coords.startX * canvas.width;
        const startY = coords.startY * canvas.height;
        const endX = coords.endX * canvas.width;
        const endY = coords.endY * canvas.height;
        
        drawMeasurement(
          drawingLayer, 
          startX, 
          startY, 
          endX, 
          endY, 
          m.type, 
          false, // Not temporary
          m.is_deduction
        );
      }
    });
  }
  
  // Handle window resize to adjust fit
  window.addEventListener('resize', function() {
    if (document.getElementById('pdf-image')) {
      fitToPage();
    }
  });

  function handleScaleSelection(value) {
    const customInput = document.getElementById('custom-scale-input');
    if (value === 'custom') {
      customInput.classList.remove('collapse');
    } else {
      customInput.classList.add('collapse');
      // Apply the selected standard scale immediately
      const scaleValue = parseInt(value.split(':')[1]);
      if (!isNaN(scaleValue)) {
        currentScaleRatio = scaleValue;
        applyScaleToDrawing(scaleValue);
      }
    }
  }

  function promptBenchmarkMeasurement() {
    // Enter calibration mode and prompt user to draw a line
    isCalibrationMode = true;
    alert('Please draw a line on the drawing to represent a known length. After drawing, you will be prompted to enter the actual length.');
    
    // Disable normal measurement tools during calibration
    disableMeasurementTools();
    
    // Show instruction on screen
    const pdfContainer = document.getElementById('pdf-container');
    const instructionEl = document.createElement('div');
    instructionEl.id = 'calibration-instruction';
    instructionEl.className = 'position-absolute top-0 start-0 bg-warning p-2 m-2 rounded';
    instructionEl.textContent = 'CALIBRATION MODE: Draw a line representing a known distance';
    pdfContainer.appendChild(instructionEl);
  }

  function disableMeasurementTools() {
    // Temporarily disable regular measurement buttons
    const measurementButtons = document.querySelectorAll('.btn-outline-dark');
    measurementButtons.forEach(btn => {
      btn.disabled = true;
    });
  }

  function enableMeasurementTools() {
    // Re-enable measurement buttons
    const measurementButtons = document.querySelectorAll('.btn-outline-dark');
    measurementButtons.forEach(btn => {
      btn.disabled = false;
    });
    
    // Remove instruction
    const instructionEl = document.getElementById('calibration-instruction');
    if (instructionEl) instructionEl.remove();
  }

  function calculateScaleFromBenchmark(drawnLineLength, actualLengthMm) {
    // Convert drawn line length from pixels to mm using the corrected factor
    const pixelToMm = getPixelToMmConversionFactor();
    const drawnLineLengthMm = drawnLineLength * pixelToMm;
    
    // Calculate scale ratio (1:X)
    const scaleRatio = actualLengthMm / drawnLineLengthMm;
    
    // Update the UI to show the calculated scale
    alert(`Scale calculated: 1:${scaleRatio.toFixed(2)}`);
    
    // Apply the new scale
    applyScaleToDrawing(scaleRatio);
    
    return scaleRatio;
  }

  function applyScaleToDrawing(scaleRatio) {
    // Save the scale ratio to be applied to all measurements
    currentScaleRatio = scaleRatio;
    console.log(`Applied scale ratio: 1:${scaleRatio}`);
    
    // Update all existing measurements if needed
    redrawMeasurements();
    
    // Save to server for persistence
    if (currentPDFId) {
      fetch(`/save-scale/${currentPDFId}/`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRFToken': getCsrfToken(),
        },
        body: JSON.stringify({
          scale_ratio: scaleRatio
        })
      })
      .then(response => response.json())
      .then(data => {
        console.log('Scale saved:', data);
      })
      .catch(error => {
        console.error('Error saving scale:', error);
      });
    }
  }

  // Function to save the scale setting without reloading the page
  function saveScale() {
    let scaleRatio;
    
    // Get the scale value from either dropdown or custom input
    const scaleSelect = document.getElementById('scale-ratio');
    if (scaleSelect.value === 'custom') {
      // Get custom scale value
      const customInput = document.getElementById('custom-scale-value');
      if (customInput && customInput.value) {
        scaleRatio = parseFloat(customInput.value);
      } else {
        alert('Please enter a valid custom scale value');
        return;
      }
    } else {
      // Get selected scale from dropdown
      scaleRatio = parseInt(scaleSelect.value.split(':')[1]);
    }
    
    // Validate the scale ratio
    if (isNaN(scaleRatio) || scaleRatio <= 0) {
      alert('Please enter a valid scale ratio');
      return;
    }
    
    // Apply and save the scale
    applyScaleToDrawing(scaleRatio);
    
    // Update UI
    alert(`Scale set to 1:${scaleRatio}`);
    
    // Close the scale box
    toggleScaleBox();
  }

  // Update the pixel-to-mm conversion based on page size
  function getPixelToMmConversionFactor() {
    // Base conversion factor (72 DPI: 1 inch = 25.4 mm, so 1 pixel = 25.4/72 mm)
    const basePixelToMm = 0.3528;
    
    // For A3 page at 1:50 scale that's showing 2000mm when it should be 1000mm,
    // we need to apply a correction factor of 0.5
    const correctionFactor = 0.5;
    
    return basePixelToMm * correctionFactor;
  }

  // Update scale calculations if page size or scale changes
  function updateScaleCalculations() {
    // Redraw measurements with new calculations
    redrawMeasurements();
  }

  // Function to handle page size change
  function handlePageSizeChange(pageSize) {
    currentPageSize = pageSize;
    console.log(`Page size changed to ${pageSize}`);
    
    // Recalculate and apply scale if needed
    updateScaleCalculations();
  }

  // Save measurements and return to estimating
  function saveMeasurementsAndReturn() {
    // Save measurements logic here
    alert('Measurements saved successfully!');
    window.location.href = `/estimating-screen/${getProjectId()}/`;
  }
</script>

<script src="{% static 'myapp/js/takeoff.js' %}"></script>
<div id="takeoff-canvas" data-project-id="{{ project.id }}" data-pdf-id="{{ pdf.id }}"></div>
{% endblock %}
Editor is loading...
Leave a Comment