# To AI Agent: Fix Missing Leg Dates and Implement Full Temporal Fallbacks in PDF Export Table ## 1. Context & Layout Bug Analysis We are fixing a data-extraction omission bug in the PDF generation script based on `image_c13c27.png` (Timeline UI) and `image_c14d36.png` (Generated PDF): - **The Issue:** As shown in the timeline UI, every Stage/Leg has definitive date attributes (e.g., Chặng 3: `01/07/2026`, Chặng 4: `02/07/2026`, Chặng 5: `03/07/2026`). However, in the generated PDF table, the "Ngày giờ" column renders completely blank for rows 13, 14, and 15. - **Root Causes:** 1. For rows 13 & 14 (Empty stages), the previous fallback property keys did not match the actual database object keys inside the `leg` state framework. 2. For row 15 ("595 Trần Cao Vân"), the stage *has* a location, but because that specific location node lacks an explicit `plannedStart` timestamp, the cell defaulted to an empty string—ignoring the parent Leg's valid date context (`03/07/2026`). --- ## 2. Refactoring Strategy & Fallback Hierarchy We need to establish a strict multi-tiered date resolver helper function that queries both target schema properties and fallback data containers: 1. **Leg Object Scanning Matrix:** The resolver must sequentially look up: `leg.plannedStart` ➔ `leg.startDate` ➔ `leg.start_date` ➔ `leg.date` ➔ `leg.createdAt`. 2. **Location Cell Level Injection:** When mapping individual location rows, if `loc.plannedStart` is missing or invalid, the generator must instantly fall back to its parent `leg`'s temporal parameters instead of leaving a blank row cell. --- ## 3. Code Refactoring Blueprint ### Step 1: Overhaul the Itinerary Data Processing Loop Replace your data-mapping iteration phase with this secure, fallback-fortified configuration block: ```typescript if (currentTour?.legs && currentTour.legs.length > 0) { currentTour.legs.forEach((leg: any, legIdx: number) => { const legName = leg.note || `Chặng ${legIdx + 1}`; const legNameCellObj = { content: legName, styles: { fontStyle: 'bold' as const } }; // 1. Comprehensive Robust Date Extractor/Format Parser Helper const extractAndFormatTimeInline = (primaryTime: any, fallbackLegObj: any) => { // Establish priority resolution chain const absoluteTimeSource = primaryTime || fallbackLegObj?.plannedStart || fallbackLegObj?.startDate || fallbackLegObj?.start_date || fallbackLegObj?.date || fallbackLegObj?.createdAt; if (!absoluteTimeSource) return ''; const d = new Date(absoluteTimeSource); if (isNaN(d.getTime())) return ''; const hhmm = `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; const ddmmyyyy = `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`; return `${hhmm}|${ddmmyyyy}`; // Bounded pipe string for the centered custom rendering hook }; 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 initialTimeSource = isStartPoint ? loc.plannedEnd : loc.plannedStart; // FIX BUG AT ROW 15: Pass the location timestamp, but bound the entire parent leg object as secondary fallback const timeStr = extractAndFormatTimeInline(initialTimeSource, leg); 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'); tableRows.push([ stt++, timeStr, // Safely guaranteed to possess at least the parent Leg Date info locIdx === 0 ? legNameCellObj : '', locationText, loc.note || '' ]); globalRowIndex++; }); } else { // FIX BUGS AT ROW 13 & 14: Force resolution of empty leg properties using full lookup schema chain const legTimeStr = extractAndFormatTimeInline(null, leg); legRowRanges[leg.id] = { start: startRow, count: 1 }; tableRows.push([ stt++, legTimeStr, // Injected fallback inline timestamp string legNameCellObj, 'Chưa có địa điểm trong chặng này', '' ]); globalRowIndex++; } }); } ### Step 2: Retain the Centered Single-Row Rendering Hook Configuration Ensure that the didDrawCell configurations continue to split and paint the text color matrix seamlessly within a single horizontal baseline row: // Keep this implementation inside your active doc.autoTable configurations block didDrawCell: (data: any) => { if (data.section === 'body' && data.column.index === 1) { const rawTextStr = data.cell.customInlineBuffer || data.cell.raw; if (rawTextStr && typeof rawTextStr === 'string' && rawTextStr.includes('|')) { const [timePart, datePart] = rawTextStr.split('|'); const separatorSpace = " "; data.doc.setFont(data.cell.styles.font, 'bold'); const timeWidth = data.doc.getTextWidth(timePart); data.doc.setFont(data.cell.styles.font, 'normal'); const spaceWidth = data.doc.getTextWidth(separatorSpace); const dateWidth = data.doc.getTextWidth(datePart); const totalBlockWidth = timeWidth + spaceWidth + dateWidth; const targetX = data.cell.x + (data.cell.width - totalBlockWidth) / 2; const targetY = data.cell.y + (data.cell.height / 2) + (data.cell.styles.fontSize / 2) - 1; // Draw Time -> Vivid Bold Red (#ef4444) data.doc.setFont(data.cell.styles.font, 'bold'); data.doc.setTextColor(239, 68, 68); data.doc.text(timePart, targetX, targetY); // Draw Date -> Clean Regular Blue (#2563eb) data.doc.setFont(data.cell.styles.font, 'normal'); data.doc.setTextColor(37, 99, 235); data.doc.text(targetX + timeWidth + spaceWidth, targetY); } } } ## 4. Verification Checklist for AI Verification [ ] Empty Stage Time Capture: Rows 13 ("Quay về xứ nẫu") and 14 ("Thưởng thức don sông Trà") must automatically capture their matching 01/07/2026 and 02/07/2026 parameters from the timeline view hierarchy. [ ] Location Level Recovery: Row 15 ("595 Trần Cao Vân") must successfully parse and render its parent leg's date (03/07/2026) instead of displaying a blank cell block. [ ] Default Time Token: If the parent Leg data context only specifies a raw date string without precise hours/minutes, confirm it sets the time token smoothly to 00:00 for baseline rendering stability.