Skip to main content
Glama
Raistlin82

SAP OData to MCP Server

by Raistlin82

ui-report-builder

Generate drill-down reports with analytical capabilities from SAP OData services. Create summary, detailed, analytical, or custom reports by specifying entity types, dimensions, and measures for data analysis.

Instructions

Creates comprehensive drill-down reports with analytical capabilities

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
entityTypeYesSAP entity type for the report
reportTypeYesType of report to generate
dimensionsYesReport dimension fields
measuresYesReport measure fields

Implementation Reference

  • The UIReportBuilderTool class is the main handler for the 'ui-report-builder' tool. It implements MCPToolHandler, defines the tool name and description, and contains the handle() method that performs authentication validation, builds report configuration, and generates a comprehensive HTML-based report interface with charts, tables, drill-down, export, and scheduling features.
    export class UIReportBuilderTool implements MCPToolHandler {
        private logger = new Logger('UiReportBuilderTool');
    
      name = 'ui-report-builder';
      description = 'Creates comprehensive drill-down reports with analytical capabilities and export options';
      inputSchema = InputSchema;
    
      constructor(
        private renderingEngine: UIRenderingEngine,
        private componentLibrary: UIComponentLibrary,
        private entityManager: SAPEntityManager,
        private authValidator: AuthenticationValidator
      ) {}
    
      async handle(args: z.infer<typeof InputSchema>, context?: any): Promise<any> {
        try {
          // Validate authentication and authorization
          const authResult = await this.authValidator.validateToken(context?.token);
          if (!authResult.isValid) {
            throw new Error('Authentication required for report builder');
          }
    
          if (!authResult.scopes?.includes('ui.reports')) {
            throw new Error('Insufficient permissions for report builder (ui.reports scope required)');
          }
    
          // Get entity metadata
          const entityMetadata = await this.entityManager.getEntityMetadata(args.entityType);
          if (!entityMetadata) {
            throw new Error(`Entity type ${args.entityType} not found`);
          }
    
          // Build report configuration
          const reportConfig: ReportConfig = {
            entityType: args.entityType,
            reportType: args.reportType,
            dimensions: args.dimensions,
            measures: args.measures,
            filters: args.filters || [],
            drillDownLevels: this.buildDrillDownLevels(args.drillDownLevels || []),
            exportFormats: args.exportFormats || ['pdf', 'excel', 'csv'],
            schedulingEnabled: args.schedulingOptions?.enabled || false,
            visualizations: this.buildReportCharts(args.visualizations || [])
          };
    
          // Generate the report UI
          const reportHTML = await this.generateReportInterface(reportConfig, entityMetadata);
    
          return {
            content: [
              {
                type: 'text',
                text: `Report builder created for ${args.entityType} with ${args.reportType} configuration. Features drill-down capabilities, multiple export formats, and analytical visualizations.`
              },
              {
                type: 'text',
                text: reportHTML
              }
            ]
          };
    
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: `Error creating report builder: ${error instanceof Error ? error.message : 'Unknown error'}`
              }
            ]
          };
        }
      }
    
      private buildDrillDownLevels(levels: any[]): DrillDownLevel[] {
        return levels.map(level => ({
          field: level.field,
          targetEntity: level.entity,
          navigationProperty: level.navigationProperty,
          enabled: true
        }));
      }
    
      private buildReportCharts(visualizations: any[]): ReportChart[] {
        return visualizations.map(viz => ({
          type: viz.type,
          title: viz.title,
          xAxis: viz.xAxis,
          yAxis: viz.yAxis,
          groupBy: viz.groupBy,
          config: {
            responsive: true,
            maintainAspectRatio: false
          }
        }));
      }
    
      private async generateReportInterface(config: ReportConfig, entityMetadata: any): Promise<string> {
        const reportId = `report_${Date.now()}`;
    
        return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>${config.entityType} Report Builder</title>
        <link rel="stylesheet" href="https://sdk.openui5.org/resources/sap/ui/core/themes/sap_horizon/library.css">
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
        <style>
            body {
                font-family: "72", "72full", Arial, Helvetica, sans-serif;
                margin: 0;
                padding: 20px;
                background-color: #f7f7f7;
            }
    
            .report-container {
                max-width: 1400px;
                margin: 0 auto;
                background: white;
                border-radius: 8px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                overflow: hidden;
            }
    
            .report-header {
                background: linear-gradient(135deg, #0070f3, #0051cc);
                color: white;
                padding: 24px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
    
            .report-title {
                font-size: 24px;
                font-weight: 600;
                margin: 0;
            }
    
            .report-actions {
                display: flex;
                gap: 12px;
            }
    
            .sap-button {
                background: rgba(255,255,255,0.2);
                color: white;
                border: 1px solid rgba(255,255,255,0.3);
                padding: 8px 16px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
                transition: all 0.2s;
            }
    
            .sap-button:hover {
                background: rgba(255,255,255,0.3);
                transform: translateY(-1px);
            }
    
            .sap-button.primary {
                background: #ff6b35;
                border-color: #ff6b35;
            }
    
            .sap-button.primary:hover {
                background: #e55a2b;
            }
    
            .report-toolbar {
                padding: 16px 24px;
                background: #f9f9f9;
                border-bottom: 1px solid #e6e6e6;
                display: flex;
                flex-wrap: wrap;
                gap: 16px;
                align-items: center;
            }
    
            .filter-group {
                display: flex;
                align-items: center;
                gap: 8px;
            }
    
            .filter-input {
                padding: 6px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .report-content {
                padding: 24px;
            }
    
            .report-tabs {
                display: flex;
                border-bottom: 2px solid #e6e6e6;
                margin-bottom: 24px;
            }
    
            .report-tab {
                padding: 12px 20px;
                cursor: pointer;
                border-bottom: 3px solid transparent;
                font-weight: 500;
                transition: all 0.2s;
            }
    
            .report-tab.active {
                border-bottom-color: #0070f3;
                color: #0070f3;
            }
    
            .report-tab:hover {
                background: #f5f5f5;
            }
    
            .tab-content {
                display: none;
            }
    
            .tab-content.active {
                display: block;
            }
    
            .data-table {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .data-table th,
            .data-table td {
                padding: 12px;
                text-align: left;
                border-bottom: 1px solid #e6e6e6;
            }
    
            .data-table th {
                background: #f8f9fa;
                font-weight: 600;
                position: sticky;
                top: 0;
                z-index: 10;
                cursor: pointer;
            }
    
            .data-table th:hover {
                background: #e9ecef;
            }
    
            .data-table tr:hover {
                background: #f8f9fa;
            }
    
            .drill-down-link {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .drill-down-link:hover {
                color: #0051cc;
            }
    
            .chart-container {
                background: white;
                border-radius: 8px;
                padding: 20px;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .chart-title {
                font-size: 18px;
                font-weight: 600;
                margin-bottom: 16px;
                color: #333;
            }
    
            .metrics-grid {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
                gap: 16px;
                margin-bottom: 24px;
            }
    
            .metric-card {
                background: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
                text-align: center;
            }
    
            .metric-value {
                font-size: 32px;
                font-weight: 700;
                color: #0070f3;
                margin-bottom: 8px;
            }
    
            .metric-label {
                font-size: 14px;
                color: #666;
                font-weight: 500;
            }
    
            .breadcrumb {
                background: #f0f0f0;
                padding: 12px 16px;
                border-radius: 4px;
                margin-bottom: 16px;
                font-size: 14px;
            }
    
            .breadcrumb-item {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .breadcrumb-separator {
                margin: 0 8px;
                color: #999;
            }
    
            .export-modal {
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.5);
                z-index: 1000;
            }
    
            .export-dialog {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: white;
                padding: 24px;
                border-radius: 8px;
                min-width: 400px;
            }
    
            .export-options {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
                gap: 12px;
                margin: 16px 0;
            }
    
            .export-option {
                padding: 12px;
                border: 2px solid #e6e6e6;
                border-radius: 4px;
                text-align: center;
                cursor: pointer;
                transition: all 0.2s;
            }
    
            .export-option:hover {
                border-color: #0070f3;
                background: #f0f8ff;
            }
    
            .export-option.selected {
                border-color: #0070f3;
                background: #0070f3;
                color: white;
            }
    
            .loading {
                display: inline-block;
                width: 20px;
                height: 20px;
                border: 3px solid #f3f3f3;
                border-top: 3px solid #0070f3;
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }
    
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
    
            .pagination {
                display: flex;
                justify-content: center;
                align-items: center;
                gap: 8px;
                margin-top: 20px;
            }
    
            .pagination button {
                padding: 8px 12px;
                border: 1px solid #ccc;
                background: white;
                cursor: pointer;
                border-radius: 4px;
            }
    
            .pagination button:hover {
                background: #f5f5f5;
            }
    
            .pagination button.active {
                background: #0070f3;
                color: white;
                border-color: #0070f3;
            }
    
            .schedule-section {
                background: #f8f9fa;
                padding: 20px;
                border-radius: 8px;
                margin-bottom: 20px;
            }
    
            .form-group {
                margin-bottom: 16px;
            }
    
            .form-label {
                display: block;
                margin-bottom: 4px;
                font-weight: 500;
                color: #333;
            }
    
            .form-control {
                width: 100%;
                padding: 8px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .form-control:focus {
                outline: none;
                border-color: #0070f3;
                box-shadow: 0 0 0 2px rgba(0,112,243,0.2);
            }
    
            @media (max-width: 768px) {
                .report-toolbar {
                    flex-direction: column;
                    align-items: stretch;
                }
    
                .report-actions {
                    flex-direction: column;
                }
    
                .metrics-grid {
                    grid-template-columns: 1fr;
                }
            }
        </style>
    </head>
    <body>
        <div class="report-container">
            <div class="report-header">
                <h1 class="report-title">${config.entityType} ${config.reportType.charAt(0).toUpperCase() + config.reportType.slice(1)} Report</h1>
                <div class="report-actions">
                    <button class="sap-button" onclick="refreshReport()">
                        <span id="refresh-icon">🔄</span> Refresh
                    </button>
                    <button class="sap-button" onclick="showExportModal()">📤 Export</button>
                    <button class="sap-button primary" onclick="showScheduleModal()">⏰ Schedule</button>
                </div>
            </div>
    
            <div class="report-toolbar">
                <div class="filter-group">
                    <label>Date Range:</label>
                    <input type="date" class="filter-input" id="startDate">
                    <span>to</span>
                    <input type="date" class="filter-input" id="endDate">
                </div>
    
                <div class="filter-group">
                    <label>Quick Filter:</label>
                    <input type="text" class="filter-input" id="quickFilter" placeholder="Search...">
                </div>
    
                <div class="filter-group">
                    <label>Group By:</label>
                    <select class="filter-input" id="groupBy">
                        <option value="">None</option>
                        ${config.dimensions.map(dim => `<option value="${dim}">${dim}</option>`).join('')}
                    </select>
                </div>
    
                <button class="sap-button" onclick="applyFilters()">Apply Filters</button>
                <button class="sap-button" onclick="clearFilters()">Clear</button>
            </div>
    
            <div class="report-content">
                <div class="breadcrumb" id="breadcrumb" style="display: none;">
                    <span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>
                </div>
    
                <div class="report-tabs">
                    <div class="report-tab active" onclick="switchTab('summary')">Summary</div>
                    <div class="report-tab" onclick="switchTab('data')">Data</div>
                    <div class="report-tab" onclick="switchTab('charts')">Charts</div>
                    <div class="report-tab" onclick="switchTab('analytics')">Analytics</div>
                </div>
    
                <div id="summary-tab" class="tab-content active">
                    <div class="metrics-grid">
                        ${config.measures.map(measure => `
                            <div class="metric-card">
                                <div class="metric-value" id="metric-${measure}">-</div>
                                <div class="metric-label">${measure}</div>
                            </div>
                        `).join('')}
                    </div>
    
                    <div class="chart-container">
                        <div class="chart-title">Trend Analysis</div>
                        <canvas id="trendChart" width="400" height="200"></canvas>
                    </div>
                </div>
    
                <div id="data-tab" class="tab-content">
                    <div style="overflow-x: auto;">
                        <table class="data-table" id="dataTable">
                            <thead>
                                <tr>
                                    ${[...config.dimensions, ...config.measures].map(field => `
                                        <th onclick="sortTable('${field}')">${field} ↕️</th>
                                    `).join('')}
                                    ${(config.drillDownLevels?.length || 0) > 0 ? '<th>Actions</th>' : ''}
                                </tr>
                            </thead>
                            <tbody id="dataTableBody">
                                <tr><td colspan="${[...config.dimensions, ...config.measures].length + ((config.drillDownLevels?.length || 0) > 0 ? 1 : 0)}">Loading data...</td></tr>
                            </tbody>
                        </table>
                    </div>
    
                    <div class="pagination" id="pagination"></div>
                </div>
    
                <div id="charts-tab" class="tab-content">
                    ${(config.visualizations || []).map((chart, index) => `
                        <div class="chart-container">
                            <div class="chart-title">${chart.title}</div>
                            <canvas id="chart-${index}" width="400" height="300"></canvas>
                        </div>
                    `).join('')}
                </div>
    
                <div id="analytics-tab" class="tab-content">
                    <div class="chart-container">
                        <div class="chart-title">Advanced Analytics</div>
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
                            <canvas id="correlationChart" width="300" height="300"></canvas>
                            <canvas id="distributionChart" width="300" height="300"></canvas>
                        </div>
                    </div>
    
                    <div class="schedule-section">
                        <h3>Statistical Summary</h3>
                        <div id="statisticalSummary">Loading statistical analysis...</div>
                    </div>
                </div>
            </div>
        </div>
    
        <!-- Export Modal -->
        <div id="exportModal" class="export-modal">
            <div class="export-dialog">
                <h3>Export Report</h3>
                <div class="export-options">
                    ${(config.exportFormats || []).map(format => `
                        <div class="export-option" onclick="selectExportFormat('${format}')">
                            ${format.toUpperCase()}
                        </div>
                    `).join('')}
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideExportModal()">Cancel</button>
                    <button class="sap-button primary" onclick="exportReport()">Export</button>
                </div>
            </div>
        </div>
    
        <!-- Schedule Modal -->
        <div id="scheduleModal" class="export-modal">
            <div class="export-dialog">
                <h3>Schedule Report</h3>
                <div class="form-group">
                    <label class="form-label">Frequency:</label>
                    <select class="form-control" id="scheduleFrequency">
                        <option value="daily">Daily</option>
                        <option value="weekly">Weekly</option>
                        <option value="monthly">Monthly</option>
                        <option value="quarterly">Quarterly</option>
                    </select>
                </div>
                <div class="form-group">
                    <label class="form-label">Recipients (comma-separated emails):</label>
                    <input type="text" class="form-control" id="scheduleRecipients" placeholder="user1@company.com, user2@company.com">
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideScheduleModal()">Cancel</button>
                    <button class="sap-button primary" onclick="scheduleReport()">Schedule</button>
                </div>
            </div>
        </div>
    
        <script>
            // Report state management
            let currentData = [];
            let filteredData = [];
            let currentPage = 1;
            let pageSize = 50;
            let drillDownStack = [];
            let currentLevel = 0;
            let sortColumn = '';
            let sortDirection = 'asc';
            let selectedExportFormat = 'pdf';
    
            // Configuration
            const reportConfig = ${JSON.stringify(config)};
    
            // Initialize report
            document.addEventListener('DOMContentLoaded', function() {
                loadInitialData();
                initializeCharts();
                setDefaultDateRange();
            });
    
            function setDefaultDateRange() {
                const endDate = new Date();
                const startDate = new Date();
                startDate.setMonth(startDate.getMonth() - 1);
    
                document.getElementById('startDate').value = startDate.toISOString().split('T')[0];
                document.getElementById('endDate').value = endDate.toISOString().split('T')[0];
            }
    
            async function loadInitialData() {
                try {
                    showLoading('dataTableBody');
    
                    // Simulate data loading
                    const mockData = generateMockData(100);
                    currentData = mockData;
                    filteredData = [...currentData];
    
                    updateSummaryMetrics();
                    updateDataTable();
                    updateCharts();
                    updateAnalytics();
    
                } catch (error) {
                    this.logger.error('Error loading data:', { error: error });
                    showError('Failed to load report data');
                }
            }
    
            function generateMockData(count) {
                const data = [];
                const statuses = ['Active', 'Inactive', 'Pending', 'Completed'];
                const categories = ['A', 'B', 'C', 'D'];
    
                for (let i = 0; i < count; i++) {
                    const record = {};
    
                    reportConfig.dimensions.forEach(dim => {
                        switch (dim.toLowerCase()) {
                            case 'status':
                                record[dim] = statuses[Math.floor(Math.random() * statuses.length)];
                                break;
                            case 'category':
                                record[dim] = categories[Math.floor(Math.random() * categories.length)];
                                break;
                            case 'date':
                                const date = new Date();
                                date.setDate(date.getDate() - Math.floor(Math.random() * 365));
                                record[dim] = date.toISOString().split('T')[0];
                                break;
                            default:
                                record[dim] = \`\${dim}_\${i + 1}\`;
                        }
                    });
    
                    reportConfig.measures.forEach(measure => {
                        record[measure] = Math.floor(Math.random() * 10000) + 1000;
                    });
    
                    record.id = i + 1;
                    data.push(record);
                }
    
                return data;
            }
    
            function updateSummaryMetrics() {
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    const avg = sum / values.length;
                    const max = Math.max(...values);
    
                    const element = document.getElementById(\`metric-\${measure}\`);
                    if (element) {
                        element.textContent = formatNumber(sum);
                        element.title = \`Average: \${formatNumber(avg)}, Max: \${formatNumber(max)}\`;
                    }
                });
            }
    
            function updateDataTable() {
                const tbody = document.getElementById('dataTableBody');
                const startIndex = (currentPage - 1) * pageSize;
                const endIndex = startIndex + pageSize;
                const pageData = filteredData.slice(startIndex, endIndex);
    
                tbody.innerHTML = pageData.map(row => \`
                    <tr>
                        \${[...reportConfig.dimensions, ...reportConfig.measures].map(field => \`
                            <td>\${formatCellValue(row[field], field)}</td>
                        \`).join('')}
                        \${reportConfig.drillDownLevels.length > 0 ? \`
                            <td>
                                <span class="drill-down-link" onclick="drillDown('\${row.id}', '\${row[reportConfig.dimensions[0]]}')">
                                    Drill Down
                                </span>
                            </td>
                        \` : ''}
                    </tr>
                \`).join('');
    
                updatePagination();
            }
    
            function formatCellValue(value, field) {
                if (value === null || value === undefined) return '-';
    
                if (reportConfig.measures.includes(field)) {
                    return formatNumber(value);
                }
    
                if (field.toLowerCase().includes('date')) {
                    return new Date(value).toLocaleDateString();
                }
    
                return value;
            }
    
            function formatNumber(num) {
                if (num >= 1000000) {
                    return (num / 1000000).toFixed(1) + 'M';
                } else if (num >= 1000) {
                    return (num / 1000).toFixed(1) + 'K';
                }
                return num.toLocaleString();
            }
    
            function updatePagination() {
                const totalPages = Math.ceil(filteredData.length / pageSize);
                const pagination = document.getElementById('pagination');
    
                let paginationHTML = '';
    
                // Previous button
                if (currentPage > 1) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage - 1})">← Previous</button>\`;
                }
    
                // Page numbers
                const startPage = Math.max(1, currentPage - 2);
                const endPage = Math.min(totalPages, currentPage + 2);
    
                for (let i = startPage; i <= endPage; i++) {
                    const activeClass = i === currentPage ? 'active' : '';
                    paginationHTML += \`<button class="\${activeClass}" onclick="changePage(\${i})">\${i}</button>\`;
                }
    
                // Next button
                if (currentPage < totalPages) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage + 1})">Next →</button>\`;
                }
    
                pagination.innerHTML = paginationHTML;
            }
    
            function changePage(page) {
                currentPage = page;
                updateDataTable();
            }
    
            function initializeCharts() {
                // Trend chart
                const trendCtx = document.getElementById('trendChart').getContext('2d');
                new Chart(trendCtx, {
                    type: 'line',
                    data: {
                        labels: getLast12Months(),
                        datasets: reportConfig.measures.map((measure, index) => ({
                            label: measure,
                            data: generateTrendData(),
                            borderColor: getChartColor(index),
                            backgroundColor: getChartColor(index, 0.1),
                            tension: 0.4
                        }))
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Monthly Trend'
                            }
                        }
                    }
                });
    
                // Initialize visualization charts
                reportConfig.visualizations.forEach((chart, index) => {
                    initializeVisualizationChart(chart, index);
                });
    
                // Analytics charts
                initializeAnalyticsCharts();
            }
    
            function initializeVisualizationChart(chartConfig, index) {
                const ctx = document.getElementById(\`chart-\${index}\`).getContext('2d');
    
                new Chart(ctx, {
                    type: chartConfig.type,
                    data: generateChartData(chartConfig),
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: chartConfig.title
                            }
                        }
                    }
                });
            }
    
            function initializeAnalyticsCharts() {
                // Correlation chart
                const correlationCtx = document.getElementById('correlationChart').getContext('2d');
                new Chart(correlationCtx, {
                    type: 'scatter',
                    data: {
                        datasets: [{
                            label: 'Correlation Analysis',
                            data: generateCorrelationData(),
                            backgroundColor: 'rgba(0, 112, 243, 0.6)'
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Measure Correlation'
                            }
                        }
                    }
                });
    
                // Distribution chart
                const distributionCtx = document.getElementById('distributionChart').getContext('2d');
                new Chart(distributionCtx, {
                    type: 'bar',
                    data: {
                        labels: ['Q1', 'Q2', 'Q3', 'Q4'],
                        datasets: [{
                            label: 'Distribution',
                            data: [25, 35, 28, 42],
                            backgroundColor: [
                                'rgba(255, 107, 53, 0.8)',
                                'rgba(0, 112, 243, 0.8)',
                                'rgba(40, 167, 69, 0.8)',
                                'rgba(255, 193, 7, 0.8)'
                            ]
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Quartile Distribution'
                            }
                        }
                    }
                });
            }
    
            function generateChartData(chartConfig) {
                const labels = getUniqueValues(filteredData, chartConfig.xAxis);
                const data = labels.map(label => {
                    const filtered = filteredData.filter(row => row[chartConfig.xAxis] === label);
                    return filtered.reduce((sum, row) => sum + (row[chartConfig.yAxis] || 0), 0);
                });
    
                return {
                    labels: labels,
                    datasets: [{
                        label: chartConfig.yAxis,
                        data: data,
                        backgroundColor: chartConfig.type === 'pie' ?
                            labels.map((_, i) => getChartColor(i, 0.8)) :
                            getChartColor(0, 0.8),
                        borderColor: getChartColor(0),
                        borderWidth: 1
                    }]
                };
            }
    
            function getUniqueValues(data, field) {
                return [...new Set(data.map(row => row[field]))].slice(0, 10);
            }
    
            function generateTrendData() {
                return Array.from({length: 12}, () => Math.floor(Math.random() * 1000) + 500);
            }
    
            function generateCorrelationData() {
                return Array.from({length: 50}, () => ({
                    x: Math.random() * 100,
                    y: Math.random() * 100
                }));
            }
    
            function getLast12Months() {
                const months = [];
                const now = new Date();
                for (let i = 11; i >= 0; i--) {
                    const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
                    months.push(date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }));
                }
                return months;
            }
    
            function getChartColor(index, alpha = 1) {
                const colors = [
                    \`rgba(0, 112, 243, \${alpha})\`,
                    \`rgba(255, 107, 53, \${alpha})\`,
                    \`rgba(40, 167, 69, \${alpha})\`,
                    \`rgba(255, 193, 7, \${alpha})\`,
                    \`rgba(108, 117, 125, \${alpha})\`
                ];
                return colors[index % colors.length];
            }
    
            function updateCharts() {
                // Update existing charts with filtered data
                updateSummaryMetrics();
            }
    
            function updateAnalytics() {
                const summary = document.getElementById('statisticalSummary');
    
                let analyticsHTML = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">';
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const mean = values.reduce((a, b) => a + b, 0) / values.length;
                    const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
                    const stdDev = Math.sqrt(variance);
    
                    analyticsHTML += \`
                        <div class="metric-card">
                            <h4>\${measure}</h4>
                            <p><strong>Mean:</strong> \${formatNumber(mean)}</p>
                            <p><strong>Std Dev:</strong> \${formatNumber(stdDev)}</p>
                            <p><strong>Min:</strong> \${formatNumber(Math.min(...values))}</p>
                            <p><strong>Max:</strong> \${formatNumber(Math.max(...values))}</p>
                        </div>
                    \`;
                });
    
                analyticsHTML += '</div>';
                summary.innerHTML = analyticsHTML;
            }
    
            // Event handlers
            function switchTab(tabName) {
                // Hide all tabs
                document.querySelectorAll('.tab-content').forEach(tab => {
                    tab.classList.remove('active');
                });
                document.querySelectorAll('.report-tab').forEach(tab => {
                    tab.classList.remove('active');
                });
    
                // Show selected tab
                document.getElementById(\`\${tabName}-tab\`).classList.add('active');
                event.target.classList.add('active');
            }
    
            function applyFilters() {
                const startDate = document.getElementById('startDate').value;
                const endDate = document.getElementById('endDate').value;
                const quickFilter = document.getElementById('quickFilter').value.toLowerCase();
                const groupBy = document.getElementById('groupBy').value;
    
                filteredData = currentData.filter(row => {
                    let matchesFilter = true;
    
                    // Date filter
                    if (startDate && endDate && row.date) {
                        const rowDate = new Date(row.date);
                        matchesFilter = matchesFilter && rowDate >= new Date(startDate) && rowDate <= new Date(endDate);
                    }
    
                    // Quick filter
                    if (quickFilter) {
                        const rowText = Object.values(row).join(' ').toLowerCase();
                        matchesFilter = matchesFilter && rowText.includes(quickFilter);
                    }
    
                    return matchesFilter;
                });
    
                // Group by functionality
                if (groupBy) {
                    // Implement grouping logic here
                    this.logger.debug('Grouping by:', groupBy);
                }
    
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function clearFilters() {
                document.getElementById('startDate').value = '';
                document.getElementById('endDate').value = '';
                document.getElementById('quickFilter').value = '';
                document.getElementById('groupBy').value = '';
    
                filteredData = [...currentData];
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function sortTable(column) {
                if (sortColumn === column) {
                    sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
                } else {
                    sortColumn = column;
                    sortDirection = 'asc';
                }
    
                filteredData.sort((a, b) => {
                    let aVal = a[column];
                    let bVal = b[column];
    
                    if (typeof aVal === 'string') {
                        aVal = aVal.toLowerCase();
                        bVal = bVal.toLowerCase();
                    }
    
                    if (sortDirection === 'asc') {
                        return aVal > bVal ? 1 : -1;
                    } else {
                        return aVal < bVal ? 1 : -1;
                    }
                });
    
                updateDataTable();
            }
    
            function drillDown(id, value) {
                drillDownStack.push({
                    level: currentLevel,
                    data: [...filteredData],
                    title: \`\${reportConfig.entityType} Report\`
                });
    
                currentLevel++;
    
                // Filter data for drill-down
                filteredData = currentData.filter(row => row[reportConfig.dimensions[0]] === value);
    
                // Update breadcrumb
                updateBreadcrumb(value);
    
                // Update displays
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
            }
    
            function navigateToLevel(level) {
                if (level < currentLevel && level >= 0) {
                    const targetState = drillDownStack[level];
                    if (targetState) {
                        filteredData = targetState.data;
                        currentLevel = level;
                        drillDownStack = drillDownStack.slice(0, level);
    
                        updateBreadcrumb();
                        updateDataTable();
                        updateSummaryMetrics();
                        updateCharts();
                    }
                }
            }
    
            function updateBreadcrumb(currentItem) {
                const breadcrumb = document.getElementById('breadcrumb');
    
                if (currentLevel === 0) {
                    breadcrumb.style.display = 'none';
                    return;
                }
    
                breadcrumb.style.display = 'block';
                let breadcrumbHTML = '<span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>';
    
                drillDownStack.forEach((item, index) => {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span class="breadcrumb-item" onclick="navigateToLevel(\${index + 1})">\${item.title}</span>\`;
                });
    
                if (currentItem) {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span>\${currentItem}</span>\`;
                }
    
                breadcrumb.innerHTML = breadcrumbHTML;
            }
    
            async function refreshReport() {
                const icon = document.getElementById('refresh-icon');
                icon.innerHTML = '<div class="loading"></div>';
    
                try {
                    await loadInitialData();
                    showMessage('Report refreshed successfully', 'success');
                } catch (error) {
                    showMessage('Failed to refresh report', 'error');
                } finally {
                    icon.innerHTML = '🔄';
                }
            }
    
            function showExportModal() {
                document.getElementById('exportModal').style.display = 'block';
            }
    
            function hideExportModal() {
                document.getElementById('exportModal').style.display = 'none';
            }
    
            function selectExportFormat(format) {
                document.querySelectorAll('.export-option').forEach(option => {
                    option.classList.remove('selected');
                });
                event.target.classList.add('selected');
                selectedExportFormat = format;
            }
    
            async function exportReport() {
                try {
                    switch (selectedExportFormat) {
                        case 'pdf':
                            await exportToPDF();
                            break;
                        case 'excel':
                            await exportToExcel();
                            break;
                        case 'csv':
                            await exportToCSV();
                            break;
                        case 'json':
                            await exportToJSON();
                            break;
                    }
    
                    showMessage(\`Report exported as \${selectedExportFormat.toUpperCase()}\`, 'success');
                    hideExportModal();
                } catch (error) {
                    showMessage('Export failed', 'error');
                }
            }
    
            async function exportToPDF() {
                const { jsPDF } = window.jspdf;
                const doc = new jsPDF();
    
                doc.setFontSize(20);
                doc.text(\`\${reportConfig.entityType} Report\`, 20, 30);
    
                doc.setFontSize(12);
                doc.text(\`Generated: \${new Date().toLocaleDateString()}\`, 20, 45);
                doc.text(\`Records: \${filteredData.length}\`, 20, 55);
    
                // Add summary metrics
                let yPos = 75;
                doc.setFontSize(16);
                doc.text('Summary Metrics', 20, yPos);
                yPos += 15;
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    doc.setFontSize(12);
                    doc.text(\`\${measure}: \${formatNumber(sum)}\`, 20, yPos);
                    yPos += 10;
                });
    
                doc.save(\`\${reportConfig.entityType}_report.pdf\`);
            }
    
            async function exportToExcel() {
                const ws = XLSX.utils.json_to_sheet(filteredData);
                const wb = XLSX.utils.book_new();
                XLSX.utils.book_append_sheet(wb, ws, 'Report Data');
    
                XLSX.writeFile(wb, \`\${reportConfig.entityType}_report.xlsx\`);
            }
    
            async function exportToCSV() {
                const headers = [...reportConfig.dimensions, ...reportConfig.measures];
                const csvContent = [
                    headers.join(','),
                    ...filteredData.map(row => headers.map(header => row[header] || '').join(','))
                ].join('\\n');
    
                const blob = new Blob([csvContent], { type: 'text/csv' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.csv\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            async function exportToJSON() {
                const jsonContent = JSON.stringify(filteredData, null, 2);
                const blob = new Blob([jsonContent], { type: 'application/json' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.json\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            function showScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'block';
            }
    
            function hideScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'none';
            }
    
            async function scheduleReport() {
                const frequency = document.getElementById('scheduleFrequency').value;
                const recipients = document.getElementById('scheduleRecipients').value;
    
                if (!recipients.trim()) {
                    showMessage('Please enter at least one recipient email', 'error');
                    return;
                }
    
                try {
                    // Here you would call your scheduling API
                    this.logger.debug('Scheduling report:', { frequency, recipients });
                    showMessage(\`Report scheduled \${frequency} for \${recipients}\`, 'success');
                    hideScheduleModal();
                } catch (error) {
                    showMessage('Failed to schedule report', 'error');
                }
            }
    
            function showMessage(message, type) {
                // Create toast notification
                const toast = document.createElement('div');
                toast.style.cssText = \`
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    padding: 12px 20px;
                    border-radius: 4px;
                    color: white;
                    font-weight: 500;
                    z-index: 2000;
                    background: \${type === 'success' ? '#28a745' : '#dc3545'};
                    animation: slideIn 0.3s ease;
                \`;
                toast.textContent = message;
    
                document.body.appendChild(toast);
    
                setTimeout(() => {
                    toast.remove();
                }, 3000);
            }
    
            function showLoading(elementId) {
                const element = document.getElementById(elementId);
                if (element) {
                    element.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="loading"></div></div>';
                }
            }
    
            function showError(message) {
                const element = document.getElementById('dataTableBody');
                if (element) {
                    element.innerHTML = \`<tr><td colspan="10" style="text-align: center; color: red; padding: 20px;">\${message}</td></tr>\`;
                }
            }
    
            // Add CSS animation
            const style = document.createElement('style');
            style.textContent = \`
                @keyframes slideIn {
                    from {
                        transform: translateX(100%);
                        opacity: 0;
                    }
                    to {
                        transform: translateX(0);
                        opacity: 1;
                    }
                }
            \`;
            document.head.appendChild(style);
        </script>
    </body>
    </html>`;
      }
    }
  • Zod schema defining the input parameters for the ui-report-builder tool, including entity type, report type, dimensions, measures, filters, drill-down levels, export formats, scheduling options, and visualizations.
    const InputSchema = z.object({
      entityType: z.string().describe('SAP entity type for the report (e.g., "Customer", "SalesOrder")'),
      reportType: z.enum(['summary', 'detailed', 'analytical', 'custom']).describe('Type of report to generate'),
      dimensions: z.array(z.string()).describe('Fields to use as report dimensions'),
      measures: z.array(z.string()).describe('Fields to use as report measures'),
      filters: z.array(z.object({
        field: z.string(),
        operator: z.enum(['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'contains', 'startswith', 'endswith']),
        value: z.union([z.string(), z.number(), z.boolean()])
      })).optional().describe('Optional filters to apply to the report'),
      drillDownLevels: z.array(z.object({
        field: z.string(),
        entity: z.string().optional(),
        navigationProperty: z.string().optional()
      })).optional().describe('Drill-down levels configuration'),
      exportFormats: z.array(z.enum(['pdf', 'excel', 'csv', 'json', 'xml'])).optional().describe('Export formats to enable'),
      schedulingOptions: z.object({
        enabled: z.boolean(),
        frequency: z.enum(['daily', 'weekly', 'monthly', 'quarterly']).optional(),
        recipients: z.array(z.string()).optional()
      }).optional().describe('Report scheduling configuration'),
      visualizations: z.array(z.object({
        type: z.enum(['bar', 'line', 'pie', 'scatter', 'area', 'table']),
        title: z.string(),
        xAxis: z.string(),
        yAxis: z.string(),
        groupBy: z.string().optional()
      })).optional().describe('Chart visualizations for the report')
    });
  • Core helper method that generates the full interactive HTML UI for the report, including styling, charts (Chart.js), data tables, metrics, export functions (PDF, Excel, CSV), scheduling, filtering, sorting, pagination, and drill-down navigation.
      private async generateReportInterface(config: ReportConfig, entityMetadata: any): Promise<string> {
        const reportId = `report_${Date.now()}`;
    
        return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>${config.entityType} Report Builder</title>
        <link rel="stylesheet" href="https://sdk.openui5.org/resources/sap/ui/core/themes/sap_horizon/library.css">
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
        <style>
            body {
                font-family: "72", "72full", Arial, Helvetica, sans-serif;
                margin: 0;
                padding: 20px;
                background-color: #f7f7f7;
            }
    
            .report-container {
                max-width: 1400px;
                margin: 0 auto;
                background: white;
                border-radius: 8px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                overflow: hidden;
            }
    
            .report-header {
                background: linear-gradient(135deg, #0070f3, #0051cc);
                color: white;
                padding: 24px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
    
            .report-title {
                font-size: 24px;
                font-weight: 600;
                margin: 0;
            }
    
            .report-actions {
                display: flex;
                gap: 12px;
            }
    
            .sap-button {
                background: rgba(255,255,255,0.2);
                color: white;
                border: 1px solid rgba(255,255,255,0.3);
                padding: 8px 16px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
                transition: all 0.2s;
            }
    
            .sap-button:hover {
                background: rgba(255,255,255,0.3);
                transform: translateY(-1px);
            }
    
            .sap-button.primary {
                background: #ff6b35;
                border-color: #ff6b35;
            }
    
            .sap-button.primary:hover {
                background: #e55a2b;
            }
    
            .report-toolbar {
                padding: 16px 24px;
                background: #f9f9f9;
                border-bottom: 1px solid #e6e6e6;
                display: flex;
                flex-wrap: wrap;
                gap: 16px;
                align-items: center;
            }
    
            .filter-group {
                display: flex;
                align-items: center;
                gap: 8px;
            }
    
            .filter-input {
                padding: 6px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .report-content {
                padding: 24px;
            }
    
            .report-tabs {
                display: flex;
                border-bottom: 2px solid #e6e6e6;
                margin-bottom: 24px;
            }
    
            .report-tab {
                padding: 12px 20px;
                cursor: pointer;
                border-bottom: 3px solid transparent;
                font-weight: 500;
                transition: all 0.2s;
            }
    
            .report-tab.active {
                border-bottom-color: #0070f3;
                color: #0070f3;
            }
    
            .report-tab:hover {
                background: #f5f5f5;
            }
    
            .tab-content {
                display: none;
            }
    
            .tab-content.active {
                display: block;
            }
    
            .data-table {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .data-table th,
            .data-table td {
                padding: 12px;
                text-align: left;
                border-bottom: 1px solid #e6e6e6;
            }
    
            .data-table th {
                background: #f8f9fa;
                font-weight: 600;
                position: sticky;
                top: 0;
                z-index: 10;
                cursor: pointer;
            }
    
            .data-table th:hover {
                background: #e9ecef;
            }
    
            .data-table tr:hover {
                background: #f8f9fa;
            }
    
            .drill-down-link {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .drill-down-link:hover {
                color: #0051cc;
            }
    
            .chart-container {
                background: white;
                border-radius: 8px;
                padding: 20px;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .chart-title {
                font-size: 18px;
                font-weight: 600;
                margin-bottom: 16px;
                color: #333;
            }
    
            .metrics-grid {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
                gap: 16px;
                margin-bottom: 24px;
            }
    
            .metric-card {
                background: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
                text-align: center;
            }
    
            .metric-value {
                font-size: 32px;
                font-weight: 700;
                color: #0070f3;
                margin-bottom: 8px;
            }
    
            .metric-label {
                font-size: 14px;
                color: #666;
                font-weight: 500;
            }
    
            .breadcrumb {
                background: #f0f0f0;
                padding: 12px 16px;
                border-radius: 4px;
                margin-bottom: 16px;
                font-size: 14px;
            }
    
            .breadcrumb-item {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .breadcrumb-separator {
                margin: 0 8px;
                color: #999;
            }
    
            .export-modal {
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.5);
                z-index: 1000;
            }
    
            .export-dialog {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: white;
                padding: 24px;
                border-radius: 8px;
                min-width: 400px;
            }
    
            .export-options {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
                gap: 12px;
                margin: 16px 0;
            }
    
            .export-option {
                padding: 12px;
                border: 2px solid #e6e6e6;
                border-radius: 4px;
                text-align: center;
                cursor: pointer;
                transition: all 0.2s;
            }
    
            .export-option:hover {
                border-color: #0070f3;
                background: #f0f8ff;
            }
    
            .export-option.selected {
                border-color: #0070f3;
                background: #0070f3;
                color: white;
            }
    
            .loading {
                display: inline-block;
                width: 20px;
                height: 20px;
                border: 3px solid #f3f3f3;
                border-top: 3px solid #0070f3;
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }
    
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
    
            .pagination {
                display: flex;
                justify-content: center;
                align-items: center;
                gap: 8px;
                margin-top: 20px;
            }
    
            .pagination button {
                padding: 8px 12px;
                border: 1px solid #ccc;
                background: white;
                cursor: pointer;
                border-radius: 4px;
            }
    
            .pagination button:hover {
                background: #f5f5f5;
            }
    
            .pagination button.active {
                background: #0070f3;
                color: white;
                border-color: #0070f3;
            }
    
            .schedule-section {
                background: #f8f9fa;
                padding: 20px;
                border-radius: 8px;
                margin-bottom: 20px;
            }
    
            .form-group {
                margin-bottom: 16px;
            }
    
            .form-label {
                display: block;
                margin-bottom: 4px;
                font-weight: 500;
                color: #333;
            }
    
            .form-control {
                width: 100%;
                padding: 8px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .form-control:focus {
                outline: none;
                border-color: #0070f3;
                box-shadow: 0 0 0 2px rgba(0,112,243,0.2);
            }
    
            @media (max-width: 768px) {
                .report-toolbar {
                    flex-direction: column;
                    align-items: stretch;
                }
    
                .report-actions {
                    flex-direction: column;
                }
    
                .metrics-grid {
                    grid-template-columns: 1fr;
                }
            }
        </style>
    </head>
    <body>
        <div class="report-container">
            <div class="report-header">
                <h1 class="report-title">${config.entityType} ${config.reportType.charAt(0).toUpperCase() + config.reportType.slice(1)} Report</h1>
                <div class="report-actions">
                    <button class="sap-button" onclick="refreshReport()">
                        <span id="refresh-icon">🔄</span> Refresh
                    </button>
                    <button class="sap-button" onclick="showExportModal()">📤 Export</button>
                    <button class="sap-button primary" onclick="showScheduleModal()">⏰ Schedule</button>
                </div>
            </div>
    
            <div class="report-toolbar">
                <div class="filter-group">
                    <label>Date Range:</label>
                    <input type="date" class="filter-input" id="startDate">
                    <span>to</span>
                    <input type="date" class="filter-input" id="endDate">
                </div>
    
                <div class="filter-group">
                    <label>Quick Filter:</label>
                    <input type="text" class="filter-input" id="quickFilter" placeholder="Search...">
                </div>
    
                <div class="filter-group">
                    <label>Group By:</label>
                    <select class="filter-input" id="groupBy">
                        <option value="">None</option>
                        ${config.dimensions.map(dim => `<option value="${dim}">${dim}</option>`).join('')}
                    </select>
                </div>
    
                <button class="sap-button" onclick="applyFilters()">Apply Filters</button>
                <button class="sap-button" onclick="clearFilters()">Clear</button>
            </div>
    
            <div class="report-content">
                <div class="breadcrumb" id="breadcrumb" style="display: none;">
                    <span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>
                </div>
    
                <div class="report-tabs">
                    <div class="report-tab active" onclick="switchTab('summary')">Summary</div>
                    <div class="report-tab" onclick="switchTab('data')">Data</div>
                    <div class="report-tab" onclick="switchTab('charts')">Charts</div>
                    <div class="report-tab" onclick="switchTab('analytics')">Analytics</div>
                </div>
    
                <div id="summary-tab" class="tab-content active">
                    <div class="metrics-grid">
                        ${config.measures.map(measure => `
                            <div class="metric-card">
                                <div class="metric-value" id="metric-${measure}">-</div>
                                <div class="metric-label">${measure}</div>
                            </div>
                        `).join('')}
                    </div>
    
                    <div class="chart-container">
                        <div class="chart-title">Trend Analysis</div>
                        <canvas id="trendChart" width="400" height="200"></canvas>
                    </div>
                </div>
    
                <div id="data-tab" class="tab-content">
                    <div style="overflow-x: auto;">
                        <table class="data-table" id="dataTable">
                            <thead>
                                <tr>
                                    ${[...config.dimensions, ...config.measures].map(field => `
                                        <th onclick="sortTable('${field}')">${field} ↕️</th>
                                    `).join('')}
                                    ${(config.drillDownLevels?.length || 0) > 0 ? '<th>Actions</th>' : ''}
                                </tr>
                            </thead>
                            <tbody id="dataTableBody">
                                <tr><td colspan="${[...config.dimensions, ...config.measures].length + ((config.drillDownLevels?.length || 0) > 0 ? 1 : 0)}">Loading data...</td></tr>
                            </tbody>
                        </table>
                    </div>
    
                    <div class="pagination" id="pagination"></div>
                </div>
    
                <div id="charts-tab" class="tab-content">
                    ${(config.visualizations || []).map((chart, index) => `
                        <div class="chart-container">
                            <div class="chart-title">${chart.title}</div>
                            <canvas id="chart-${index}" width="400" height="300"></canvas>
                        </div>
                    `).join('')}
                </div>
    
                <div id="analytics-tab" class="tab-content">
                    <div class="chart-container">
                        <div class="chart-title">Advanced Analytics</div>
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
                            <canvas id="correlationChart" width="300" height="300"></canvas>
                            <canvas id="distributionChart" width="300" height="300"></canvas>
                        </div>
                    </div>
    
                    <div class="schedule-section">
                        <h3>Statistical Summary</h3>
                        <div id="statisticalSummary">Loading statistical analysis...</div>
                    </div>
                </div>
            </div>
        </div>
    
        <!-- Export Modal -->
        <div id="exportModal" class="export-modal">
            <div class="export-dialog">
                <h3>Export Report</h3>
                <div class="export-options">
                    ${(config.exportFormats || []).map(format => `
                        <div class="export-option" onclick="selectExportFormat('${format}')">
                            ${format.toUpperCase()}
                        </div>
                    `).join('')}
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideExportModal()">Cancel</button>
                    <button class="sap-button primary" onclick="exportReport()">Export</button>
                </div>
            </div>
        </div>
    
        <!-- Schedule Modal -->
        <div id="scheduleModal" class="export-modal">
            <div class="export-dialog">
                <h3>Schedule Report</h3>
                <div class="form-group">
                    <label class="form-label">Frequency:</label>
                    <select class="form-control" id="scheduleFrequency">
                        <option value="daily">Daily</option>
                        <option value="weekly">Weekly</option>
                        <option value="monthly">Monthly</option>
                        <option value="quarterly">Quarterly</option>
                    </select>
                </div>
                <div class="form-group">
                    <label class="form-label">Recipients (comma-separated emails):</label>
                    <input type="text" class="form-control" id="scheduleRecipients" placeholder="user1@company.com, user2@company.com">
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideScheduleModal()">Cancel</button>
                    <button class="sap-button primary" onclick="scheduleReport()">Schedule</button>
                </div>
            </div>
        </div>
    
        <script>
            // Report state management
            let currentData = [];
            let filteredData = [];
            let currentPage = 1;
            let pageSize = 50;
            let drillDownStack = [];
            let currentLevel = 0;
            let sortColumn = '';
            let sortDirection = 'asc';
            let selectedExportFormat = 'pdf';
    
            // Configuration
            const reportConfig = ${JSON.stringify(config)};
    
            // Initialize report
            document.addEventListener('DOMContentLoaded', function() {
                loadInitialData();
                initializeCharts();
                setDefaultDateRange();
            });
    
            function setDefaultDateRange() {
                const endDate = new Date();
                const startDate = new Date();
                startDate.setMonth(startDate.getMonth() - 1);
    
                document.getElementById('startDate').value = startDate.toISOString().split('T')[0];
                document.getElementById('endDate').value = endDate.toISOString().split('T')[0];
            }
    
            async function loadInitialData() {
                try {
                    showLoading('dataTableBody');
    
                    // Simulate data loading
                    const mockData = generateMockData(100);
                    currentData = mockData;
                    filteredData = [...currentData];
    
                    updateSummaryMetrics();
                    updateDataTable();
                    updateCharts();
                    updateAnalytics();
    
                } catch (error) {
                    this.logger.error('Error loading data:', { error: error });
                    showError('Failed to load report data');
                }
            }
    
            function generateMockData(count) {
                const data = [];
                const statuses = ['Active', 'Inactive', 'Pending', 'Completed'];
                const categories = ['A', 'B', 'C', 'D'];
    
                for (let i = 0; i < count; i++) {
                    const record = {};
    
                    reportConfig.dimensions.forEach(dim => {
                        switch (dim.toLowerCase()) {
                            case 'status':
                                record[dim] = statuses[Math.floor(Math.random() * statuses.length)];
                                break;
                            case 'category':
                                record[dim] = categories[Math.floor(Math.random() * categories.length)];
                                break;
                            case 'date':
                                const date = new Date();
                                date.setDate(date.getDate() - Math.floor(Math.random() * 365));
                                record[dim] = date.toISOString().split('T')[0];
                                break;
                            default:
                                record[dim] = \`\${dim}_\${i + 1}\`;
                        }
                    });
    
                    reportConfig.measures.forEach(measure => {
                        record[measure] = Math.floor(Math.random() * 10000) + 1000;
                    });
    
                    record.id = i + 1;
                    data.push(record);
                }
    
                return data;
            }
    
            function updateSummaryMetrics() {
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    const avg = sum / values.length;
                    const max = Math.max(...values);
    
                    const element = document.getElementById(\`metric-\${measure}\`);
                    if (element) {
                        element.textContent = formatNumber(sum);
                        element.title = \`Average: \${formatNumber(avg)}, Max: \${formatNumber(max)}\`;
                    }
                });
            }
    
            function updateDataTable() {
                const tbody = document.getElementById('dataTableBody');
                const startIndex = (currentPage - 1) * pageSize;
                const endIndex = startIndex + pageSize;
                const pageData = filteredData.slice(startIndex, endIndex);
    
                tbody.innerHTML = pageData.map(row => \`
                    <tr>
                        \${[...reportConfig.dimensions, ...reportConfig.measures].map(field => \`
                            <td>\${formatCellValue(row[field], field)}</td>
                        \`).join('')}
                        \${reportConfig.drillDownLevels.length > 0 ? \`
                            <td>
                                <span class="drill-down-link" onclick="drillDown('\${row.id}', '\${row[reportConfig.dimensions[0]]}')">
                                    Drill Down
                                </span>
                            </td>
                        \` : ''}
                    </tr>
                \`).join('');
    
                updatePagination();
            }
    
            function formatCellValue(value, field) {
                if (value === null || value === undefined) return '-';
    
                if (reportConfig.measures.includes(field)) {
                    return formatNumber(value);
                }
    
                if (field.toLowerCase().includes('date')) {
                    return new Date(value).toLocaleDateString();
                }
    
                return value;
            }
    
            function formatNumber(num) {
                if (num >= 1000000) {
                    return (num / 1000000).toFixed(1) + 'M';
                } else if (num >= 1000) {
                    return (num / 1000).toFixed(1) + 'K';
                }
                return num.toLocaleString();
            }
    
            function updatePagination() {
                const totalPages = Math.ceil(filteredData.length / pageSize);
                const pagination = document.getElementById('pagination');
    
                let paginationHTML = '';
    
                // Previous button
                if (currentPage > 1) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage - 1})">← Previous</button>\`;
                }
    
                // Page numbers
                const startPage = Math.max(1, currentPage - 2);
                const endPage = Math.min(totalPages, currentPage + 2);
    
                for (let i = startPage; i <= endPage; i++) {
                    const activeClass = i === currentPage ? 'active' : '';
                    paginationHTML += \`<button class="\${activeClass}" onclick="changePage(\${i})">\${i}</button>\`;
                }
    
                // Next button
                if (currentPage < totalPages) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage + 1})">Next →</button>\`;
                }
    
                pagination.innerHTML = paginationHTML;
            }
    
            function changePage(page) {
                currentPage = page;
                updateDataTable();
            }
    
            function initializeCharts() {
                // Trend chart
                const trendCtx = document.getElementById('trendChart').getContext('2d');
                new Chart(trendCtx, {
                    type: 'line',
                    data: {
                        labels: getLast12Months(),
                        datasets: reportConfig.measures.map((measure, index) => ({
                            label: measure,
                            data: generateTrendData(),
                            borderColor: getChartColor(index),
                            backgroundColor: getChartColor(index, 0.1),
                            tension: 0.4
                        }))
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Monthly Trend'
                            }
                        }
                    }
                });
    
                // Initialize visualization charts
                reportConfig.visualizations.forEach((chart, index) => {
                    initializeVisualizationChart(chart, index);
                });
    
                // Analytics charts
                initializeAnalyticsCharts();
            }
    
            function initializeVisualizationChart(chartConfig, index) {
                const ctx = document.getElementById(\`chart-\${index}\`).getContext('2d');
    
                new Chart(ctx, {
                    type: chartConfig.type,
                    data: generateChartData(chartConfig),
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: chartConfig.title
                            }
                        }
                    }
                });
            }
    
            function initializeAnalyticsCharts() {
                // Correlation chart
                const correlationCtx = document.getElementById('correlationChart').getContext('2d');
                new Chart(correlationCtx, {
                    type: 'scatter',
                    data: {
                        datasets: [{
                            label: 'Correlation Analysis',
                            data: generateCorrelationData(),
                            backgroundColor: 'rgba(0, 112, 243, 0.6)'
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Measure Correlation'
                            }
                        }
                    }
                });
    
                // Distribution chart
                const distributionCtx = document.getElementById('distributionChart').getContext('2d');
                new Chart(distributionCtx, {
                    type: 'bar',
                    data: {
                        labels: ['Q1', 'Q2', 'Q3', 'Q4'],
                        datasets: [{
                            label: 'Distribution',
                            data: [25, 35, 28, 42],
                            backgroundColor: [
                                'rgba(255, 107, 53, 0.8)',
                                'rgba(0, 112, 243, 0.8)',
                                'rgba(40, 167, 69, 0.8)',
                                'rgba(255, 193, 7, 0.8)'
                            ]
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Quartile Distribution'
                            }
                        }
                    }
                });
            }
    
            function generateChartData(chartConfig) {
                const labels = getUniqueValues(filteredData, chartConfig.xAxis);
                const data = labels.map(label => {
                    const filtered = filteredData.filter(row => row[chartConfig.xAxis] === label);
                    return filtered.reduce((sum, row) => sum + (row[chartConfig.yAxis] || 0), 0);
                });
    
                return {
                    labels: labels,
                    datasets: [{
                        label: chartConfig.yAxis,
                        data: data,
                        backgroundColor: chartConfig.type === 'pie' ?
                            labels.map((_, i) => getChartColor(i, 0.8)) :
                            getChartColor(0, 0.8),
                        borderColor: getChartColor(0),
                        borderWidth: 1
                    }]
                };
            }
    
            function getUniqueValues(data, field) {
                return [...new Set(data.map(row => row[field]))].slice(0, 10);
            }
    
            function generateTrendData() {
                return Array.from({length: 12}, () => Math.floor(Math.random() * 1000) + 500);
            }
    
            function generateCorrelationData() {
                return Array.from({length: 50}, () => ({
                    x: Math.random() * 100,
                    y: Math.random() * 100
                }));
            }
    
            function getLast12Months() {
                const months = [];
                const now = new Date();
                for (let i = 11; i >= 0; i--) {
                    const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
                    months.push(date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }));
                }
                return months;
            }
    
            function getChartColor(index, alpha = 1) {
                const colors = [
                    \`rgba(0, 112, 243, \${alpha})\`,
                    \`rgba(255, 107, 53, \${alpha})\`,
                    \`rgba(40, 167, 69, \${alpha})\`,
                    \`rgba(255, 193, 7, \${alpha})\`,
                    \`rgba(108, 117, 125, \${alpha})\`
                ];
                return colors[index % colors.length];
            }
    
            function updateCharts() {
                // Update existing charts with filtered data
                updateSummaryMetrics();
            }
    
            function updateAnalytics() {
                const summary = document.getElementById('statisticalSummary');
    
                let analyticsHTML = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">';
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const mean = values.reduce((a, b) => a + b, 0) / values.length;
                    const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
                    const stdDev = Math.sqrt(variance);
    
                    analyticsHTML += \`
                        <div class="metric-card">
                            <h4>\${measure}</h4>
                            <p><strong>Mean:</strong> \${formatNumber(mean)}</p>
                            <p><strong>Std Dev:</strong> \${formatNumber(stdDev)}</p>
                            <p><strong>Min:</strong> \${formatNumber(Math.min(...values))}</p>
                            <p><strong>Max:</strong> \${formatNumber(Math.max(...values))}</p>
                        </div>
                    \`;
                });
    
                analyticsHTML += '</div>';
                summary.innerHTML = analyticsHTML;
            }
    
            // Event handlers
            function switchTab(tabName) {
                // Hide all tabs
                document.querySelectorAll('.tab-content').forEach(tab => {
                    tab.classList.remove('active');
                });
                document.querySelectorAll('.report-tab').forEach(tab => {
                    tab.classList.remove('active');
                });
    
                // Show selected tab
                document.getElementById(\`\${tabName}-tab\`).classList.add('active');
                event.target.classList.add('active');
            }
    
            function applyFilters() {
                const startDate = document.getElementById('startDate').value;
                const endDate = document.getElementById('endDate').value;
                const quickFilter = document.getElementById('quickFilter').value.toLowerCase();
                const groupBy = document.getElementById('groupBy').value;
    
                filteredData = currentData.filter(row => {
                    let matchesFilter = true;
    
                    // Date filter
                    if (startDate && endDate && row.date) {
                        const rowDate = new Date(row.date);
                        matchesFilter = matchesFilter && rowDate >= new Date(startDate) && rowDate <= new Date(endDate);
                    }
    
                    // Quick filter
                    if (quickFilter) {
                        const rowText = Object.values(row).join(' ').toLowerCase();
                        matchesFilter = matchesFilter && rowText.includes(quickFilter);
                    }
    
                    return matchesFilter;
                });
    
                // Group by functionality
                if (groupBy) {
                    // Implement grouping logic here
                    this.logger.debug('Grouping by:', groupBy);
                }
    
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function clearFilters() {
                document.getElementById('startDate').value = '';
                document.getElementById('endDate').value = '';
                document.getElementById('quickFilter').value = '';
                document.getElementById('groupBy').value = '';
    
                filteredData = [...currentData];
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function sortTable(column) {
                if (sortColumn === column) {
                    sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
                } else {
                    sortColumn = column;
                    sortDirection = 'asc';
                }
    
                filteredData.sort((a, b) => {
                    let aVal = a[column];
                    let bVal = b[column];
    
                    if (typeof aVal === 'string') {
                        aVal = aVal.toLowerCase();
                        bVal = bVal.toLowerCase();
                    }
    
                    if (sortDirection === 'asc') {
                        return aVal > bVal ? 1 : -1;
                    } else {
                        return aVal < bVal ? 1 : -1;
                    }
                });
    
                updateDataTable();
            }
    
            function drillDown(id, value) {
                drillDownStack.push({
                    level: currentLevel,
                    data: [...filteredData],
                    title: \`\${reportConfig.entityType} Report\`
                });
    
                currentLevel++;
    
                // Filter data for drill-down
                filteredData = currentData.filter(row => row[reportConfig.dimensions[0]] === value);
    
                // Update breadcrumb
                updateBreadcrumb(value);
    
                // Update displays
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
            }
    
            function navigateToLevel(level) {
                if (level < currentLevel && level >= 0) {
                    const targetState = drillDownStack[level];
                    if (targetState) {
                        filteredData = targetState.data;
                        currentLevel = level;
                        drillDownStack = drillDownStack.slice(0, level);
    
                        updateBreadcrumb();
                        updateDataTable();
                        updateSummaryMetrics();
                        updateCharts();
                    }
                }
            }
    
            function updateBreadcrumb(currentItem) {
                const breadcrumb = document.getElementById('breadcrumb');
    
                if (currentLevel === 0) {
                    breadcrumb.style.display = 'none';
                    return;
                }
    
                breadcrumb.style.display = 'block';
                let breadcrumbHTML = '<span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>';
    
                drillDownStack.forEach((item, index) => {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span class="breadcrumb-item" onclick="navigateToLevel(\${index + 1})">\${item.title}</span>\`;
                });
    
                if (currentItem) {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span>\${currentItem}</span>\`;
                }
    
                breadcrumb.innerHTML = breadcrumbHTML;
            }
    
            async function refreshReport() {
                const icon = document.getElementById('refresh-icon');
                icon.innerHTML = '<div class="loading"></div>';
    
                try {
                    await loadInitialData();
                    showMessage('Report refreshed successfully', 'success');
                } catch (error) {
                    showMessage('Failed to refresh report', 'error');
                } finally {
                    icon.innerHTML = '🔄';
                }
            }
    
            function showExportModal() {
                document.getElementById('exportModal').style.display = 'block';
            }
    
            function hideExportModal() {
                document.getElementById('exportModal').style.display = 'none';
            }
    
            function selectExportFormat(format) {
                document.querySelectorAll('.export-option').forEach(option => {
                    option.classList.remove('selected');
                });
                event.target.classList.add('selected');
                selectedExportFormat = format;
            }
    
            async function exportReport() {
                try {
                    switch (selectedExportFormat) {
                        case 'pdf':
                            await exportToPDF();
                            break;
                        case 'excel':
                            await exportToExcel();
                            break;
                        case 'csv':
                            await exportToCSV();
                            break;
                        case 'json':
                            await exportToJSON();
                            break;
                    }
    
                    showMessage(\`Report exported as \${selectedExportFormat.toUpperCase()}\`, 'success');
                    hideExportModal();
                } catch (error) {
                    showMessage('Export failed', 'error');
                }
            }
    
            async function exportToPDF() {
                const { jsPDF } = window.jspdf;
                const doc = new jsPDF();
    
                doc.setFontSize(20);
                doc.text(\`\${reportConfig.entityType} Report\`, 20, 30);
    
                doc.setFontSize(12);
                doc.text(\`Generated: \${new Date().toLocaleDateString()}\`, 20, 45);
                doc.text(\`Records: \${filteredData.length}\`, 20, 55);
    
                // Add summary metrics
                let yPos = 75;
                doc.setFontSize(16);
                doc.text('Summary Metrics', 20, yPos);
                yPos += 15;
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    doc.setFontSize(12);
                    doc.text(\`\${measure}: \${formatNumber(sum)}\`, 20, yPos);
                    yPos += 10;
                });
    
                doc.save(\`\${reportConfig.entityType}_report.pdf\`);
            }
    
            async function exportToExcel() {
                const ws = XLSX.utils.json_to_sheet(filteredData);
                const wb = XLSX.utils.book_new();
                XLSX.utils.book_append_sheet(wb, ws, 'Report Data');
    
                XLSX.writeFile(wb, \`\${reportConfig.entityType}_report.xlsx\`);
            }
    
            async function exportToCSV() {
                const headers = [...reportConfig.dimensions, ...reportConfig.measures];
                const csvContent = [
                    headers.join(','),
                    ...filteredData.map(row => headers.map(header => row[header] || '').join(','))
                ].join('\\n');
    
                const blob = new Blob([csvContent], { type: 'text/csv' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.csv\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            async function exportToJSON() {
                const jsonContent = JSON.stringify(filteredData, null, 2);
                const blob = new Blob([jsonContent], { type: 'application/json' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.json\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            function showScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'block';
            }
    
            function hideScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'none';
            }
    
            async function scheduleReport() {
                const frequency = document.getElementById('scheduleFrequency').value;
                const recipients = document.getElementById('scheduleRecipients').value;
    
                if (!recipients.trim()) {
                    showMessage('Please enter at least one recipient email', 'error');
                    return;
                }
    
                try {
                    // Here you would call your scheduling API
                    this.logger.debug('Scheduling report:', { frequency, recipients });
                    showMessage(\`Report scheduled \${frequency} for \${recipients}\`, 'success');
                    hideScheduleModal();
                } catch (error) {
                    showMessage('Failed to schedule report', 'error');
                }
            }
    
            function showMessage(message, type) {
                // Create toast notification
                const toast = document.createElement('div');
                toast.style.cssText = \`
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    padding: 12px 20px;
                    border-radius: 4px;
                    color: white;
                    font-weight: 500;
                    z-index: 2000;
                    background: \${type === 'success' ? '#28a745' : '#dc3545'};
                    animation: slideIn 0.3s ease;
                \`;
                toast.textContent = message;
    
                document.body.appendChild(toast);
    
                setTimeout(() => {
                    toast.remove();
                }, 3000);
            }
    
            function showLoading(elementId) {
                const element = document.getElementById(elementId);
                if (element) {
                    element.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="loading"></div></div>';
                }
            }
    
            function showError(message) {
                const element = document.getElementById('dataTableBody');
                if (element) {
                    element.innerHTML = \`<tr><td colspan="10" style="text-align: center; color: red; padding: 20px;">\${message}</td></tr>\`;
                }
            }
    
            // Add CSS animation
            const style = document.createElement('style');
            style.textContent = \`
                @keyframes slideIn {
                    from {
                        transform: translateX(100%);
                        opacity: 0;
                    }
                    to {
                        transform: translateX(0);
                        opacity: 1;
                    }
                }
            \`;
            document.head.appendChild(style);
        </script>
    </body>
    </html>`;
      }
  • Tool registration in authentication scope mapping, associating 'ui-report-builder' with required scope 'ui.reports'.
    const scopeMapping: Record<string, string> = {
      'ui-form-generator': 'ui.forms',
      'ui-data-grid': 'ui.grids',
      'ui-dashboard-composer': 'ui.dashboards',
      'ui-workflow-builder': 'ui.workflows',
      'ui-report-builder': 'ui.reports',
    };
  • Tool registration in intelligent router's UI sequence mapping, linking 'ui-report-builder' to 'uiReport' workflow sequence.
      'ui-report-builder': 'uiReport',
    };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Raistlin82/btp-sap-odata-to-mcp-server-optimized'

If you have feedback or need assistance with the MCP directory API, please join our Discord server