Files
travelplanning/DATE_INSERT.md
T

7.0 KiB

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.plannedStartleg.startDateleg.start_dateleg.dateleg.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:

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.