# To AI Agent: Implement Clickable Google Maps Hyperlinks in PDF Export Table ## 1. Context & Feature Objective We are upgrading the PDF export functionality within the Yotrip Travel Planner application. Currently, the PDF generates a static table containing location names, addresses, and coordinates. **Objective:** Automatically turn every location row inside the PDF table into an interactive, clickable hyperlink. When a user clicks on the location cell in the generated PDF document, it must immediately open a browser tab navigating directly to that exact location on Google Maps. --- ## 2. Technical Architecture & Data Strategy Because `jspdf-autotable` draws text onto a canvas layout, raw HTML tags like `` will fail. We must implement a two-step rendering lifecycle: 1. **Extraction & Styling State:** During the data loop, verify coordinates or address data. Generate a standard universal Google Maps search URL, save it into a coordinate-tracking index object, and transform the raw text cell into a styled cell object (Yale Blue text `#28536b` to mimic an online link). 2. **Link Injection Layer:** Utilize the `didDrawCell` hook callback inside the `doc.autoTable` configuration to position a native invisible link window (`doc.link()`) precisely over the drawn dimensions of that specific location cell. --- ## 3. Code Refactoring Specification ### Step 1: Update Data Preparation Loop Locate the loop processing `currentTour.legs` inside your PDF generation code block. Initialize a temporary lookup map array named `pdfMapLinks` and refactor the item distribution logic as follows: ```typescript // Initialize an index mapping registry for hyperlinks before the loop execution const pdfMapLinks: { [key: number]: string } = {}; if (currentTour?.legs && currentTour.legs.length > 0) { currentTour.legs.forEach((leg: any, legIdx: number) => { const legName = leg.note || `Chặng ${legIdx + 1}`; const legLocations = (leg.locations || []).filter((loc: any) => loc && (loc.plannedStart || loc.plannedEnd || loc.name) ); const startRow = globalRowIndex; if (legLocations.length > 0) { legRowRanges[leg.id] = { start: startRow, count: legLocations.length }; legLocations.forEach((loc: any, locIdx: number) => { const isStartPoint = loc.plannedStart && new Date(loc.plannedStart).getTime() === 0; const timeSource = isStartPoint ? loc.plannedEnd : loc.plannedStart; const timeStr = timeSource ? formatDateTime(timeSource) : ''; const locName = loc.name || ''; const addressStr = loc.address || ''; const coordStr = loc.latitude && loc.longitude ? `\n${loc.latitude}, ${loc.longitude}` : ''; const locationText = [locName, addressStr, coordStr].filter(Boolean).join('\n'); const noteText = loc.note || ''; // 1. Generate standard universal Google Maps URL query pattern let mapUrl = ''; if (loc.latitude && loc.longitude) { // Absolute Precision using GPS coordinates mapUrl = `https://www.google.com/maps/search/?api=1&query=${loc.latitude},${loc.longitude}`; } else if (addressStr || locName) { // Text-search query fallback if GPS coordinates are missing mapUrl = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addressStr || locName)}`; } // 2. Register current row array position to map lookup index const currentRowPosition = tableRows.length; if (mapUrl) { pdfMapLinks[currentRowPosition] = mapUrl; } // 3. Convert raw string into custom styled autoTable Cell configuration object const locationCellObj = { content: locationText, // Apply custom link colors matching theme style #28536b (Yale Blue) styles: mapUrl ? { textColor: [40, 83, 107], fontStyle: 'bold' as const } : {} }; tableRows.push([ stt++, timeStr, locIdx === 0 ? legName : '', locationCellObj, // Injected as cell structure object noteText ]); globalRowIndex++; }); } else { legRowRanges[leg.id] = { start: startRow, count: 1 }; tableRows.push([ stt++, '', legName, 'Chưa có địa điểm trong chặng này', '' ]); globalRowIndex++; } }); } ### Step 2: Inject Coordinate-Based Overlay inside doc.autoTable Locate the core configuration module block where doc.autoTable({}) is invoked. Inject the didDrawCell event engine to deploy the link bounds: doc.autoTable({ head: [['STT', 'Thời gian', 'Chặng', 'Địa điểm', 'Ghi chú']], body: tableRows, theme: 'grid', // HOOK HANDLER: Overlays active coordinate zones on top of native cells after writing didDrawCell: (data: any) => { // Target explicitly: body section only + column index 3 (Location Column) if (data.section === 'body' && data.column.index === 3) { const activeRowIndex = data.row.index; const targetMapUrl = pdfMapLinks[activeRowIndex]; if (targetMapUrl) { // Build active link container utilizing jsPDF coordinates framework data.doc.link( data.cell.x, data.cell.y, data.cell.width, data.cell.height, { url: targetMapUrl } ); } } }, styles: { font: 'Roboto' } // Retain existing styling structure }); ## 4. Quality Control & Acceptance Criteria [ ] Visual Differentiation: Location text cells containing map URLs must render cleanly in bold dark-blue ([40, 83, 107]) while empty state texts stay muted. [ ] Coordinate Precision: Clicking a location card possessing explicit coordinates (latitude, longitude) must directly map to those absolute markers instead of doing an inaccurate keyword address query. [ ] Boundary Accuracy: The click targets must fit perfectly inside the grid cell borders. Clicking near the edges of column 3 must register properly, while clicking column 2 (Leg name) or column 4 (Notes) must remain non-reactive.