297 lines
13 KiB
PHP
Executable File

<?php return [
'render' => function($prop) {
// Generate unique ID for this table instance
$tableId = $prop['id'] ?? 'data-grid-' . uniqid();
$data = $prop['items'] ?? [];
$columns = $prop['columns'] ?? null;
$options = $prop['options'] ?? [];
// Auto-generate columns if not provided
if (!$columns && !empty($data)) {
$columns = [];
$firstItem = reset($data);
if (is_array($firstItem)) {
foreach (array_keys($firstItem) as $key) {
$columns[] = [
'field' => $key,
'headerName' => ucfirst(str_replace('_', ' ', $key)),
'sortable' => true,
'filter' => true,
'resizable' => true
];
}
}
}
// Default grid options (Community edition compatible)
$defaultOptions = [
'pagination' => true,
'paginationPageSize' => 20,
'suppressMenuHide' => true,
'animateRows' => true,
'defaultColDef' => [
'sortable' => true,
'filter' => true,
'resizable' => true,
'minWidth' => 100
]
];
$gridOptions = array_merge($defaultOptions, $options);
$gridOptions['columnDefs'] = $columns;
$gridOptions['rowData'] = $data;
?>
<!-- ag-Grid Data Table Component -->
<div class="ag-grid-container" style="margin: 1rem 0;">
<div class="ag-grid-toolbar" style="
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding: 0.75rem;
background: var(--surface-elevated);
border: 1px solid var(--border);
border-radius: 0.5rem 0.5rem 0 0;
border-bottom: none;
">
<div style="display: flex; gap: 1rem; align-items: center; flex: 1;">
<span style="color: var(--text-secondary); font-size: 0.875rem; font-weight: 500;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 0.25rem;">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<line x1="9" y1="9" x2="15" y2="9"/>
<line x1="9" y1="15" x2="15" y2="15"/>
</svg>
<?= count($data) ?> rows
</span>
<!-- Search Input -->
<div style="position: relative; display: flex; align-items: center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--text-secondary)" stroke-width="2" style="position: absolute; left: 0.75rem; z-index: 1;">
<circle cx="11" cy="11" r="8"/>
<path d="m21 21-4.35-4.35"/>
</svg>
<input
type="text"
id="<?= $tableId ?>-search"
placeholder="Search all columns..."
style="
padding: 0.5rem 0.75rem 0.5rem 2.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
background: var(--surface);
color: var(--text-primary);
font-size: 0.875rem;
width: 250px;
transition: all 0.2s ease;
"
onFocus="this.style.borderColor='var(--primary)'; this.style.boxShadow='0 0 0 2px var(--primary-light)';"
onBlur="this.style.borderColor='var(--border)'; this.style.boxShadow='none';"
/>
</div>
</div>
<div style="display: flex; gap: 0.5rem;">
<button id="<?= $tableId ?>-export-csv" style="
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text-secondary);
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.75rem;
transition: all 0.2s ease;
"
onmouseover="this.style.background='var(--primary)'; this.style.color='white'; this.style.borderColor='var(--primary)';"
onmouseout="this.style.background='var(--surface)'; this.style.color='var(--text-secondary)'; this.style.borderColor='var(--border)';">
📊 Export CSV
</button>
<button id="<?= $tableId ?>-clear-filters" style="
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text-secondary);
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.75rem;
transition: all 0.2s ease;
"
onmouseover="this.style.background='var(--surface-elevated)'; this.style.borderColor='var(--primary)';"
onmouseout="this.style.background='var(--surface)'; this.style.borderColor='var(--border)';">
🔄 Clear Filters
</button>
</div>
</div>
<div id="<?= $tableId ?>" class="ag-theme-alpine-dark" style="
height: <?= $prop['height'] ?? '400px' ?>;
width: 100%;
border: 1px solid var(--border);
border-radius: 0 0 0.5rem 0.5rem;
overflow: hidden;
"></div>
</div>
<!-- ag-Grid Theme Override Styles -->
<style>
.ag-theme-alpine-dark {
--ag-background-color: var(--surface);
--ag-foreground-color: var(--text-primary);
--ag-border-color: var(--border);
--ag-secondary-border-color: var(--border);
--ag-header-background-color: var(--surface-elevated);
--ag-header-foreground-color: var(--text-primary);
--ag-odd-row-background-color: var(--surface);
--ag-even-row-background-color: var(--surface-elevated);
--ag-row-hover-color: var(--surface-hover);
--ag-selected-row-background-color: var(--primary-light);
--ag-range-selection-background-color: var(--primary-light);
--ag-range-selection-border-color: var(--primary);
--ag-input-focus-border-color: var(--primary);
--ag-minier-foreground-color: var(--text-secondary);
--ag-subtle-text-color: var(--text-secondary);
--ag-disabled-foreground-color: var(--text-muted);
}
.ag-theme-alpine-dark .ag-header-cell-label {
font-weight: 600;
}
.ag-theme-alpine-dark .ag-cell {
border-right: 1px solid var(--border);
}
.ag-theme-alpine-dark .ag-header-cell {
border-right: 1px solid var(--border);
}
.ag-theme-alpine-dark .ag-paging-panel {
border-top: 1px solid var(--border);
background: var(--surface-elevated);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.ag-grid-toolbar {
flex-direction: column !important;
gap: 0.75rem !important;
align-items: stretch !important;
}
.ag-grid-toolbar > div {
justify-content: center !important;
}
}
</style>
<script>
if (typeof agGrid === 'undefined') {
const agGridCSS = document.createElement('link');
agGridCSS.rel = 'stylesheet';
agGridCSS.href = 'js/ag-grid/ag-grid.css';
document.head.appendChild(agGridCSS);
const agGridThemeCSS = document.createElement('link');
agGridThemeCSS.rel = 'stylesheet';
agGridThemeCSS.href = 'js/ag-grid/ag-theme-alpine.css';
document.head.appendChild(agGridThemeCSS);
const agGridScript = document.createElement('script');
agGridScript.src = 'js/ag-grid/ag-grid-community.min.js';
agGridScript.onload = function() {
initializeGrid_<?= $tableId ?>();
};
document.head.appendChild(agGridScript);
} else {
initializeGrid_<?= $tableId ?>();
}
function initializeGrid_<?= $tableId ?>() {
const gridOptions = <?= json_encode($gridOptions, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
// Enhanced column definitions with better formatting
gridOptions.columnDefs = gridOptions.columnDefs.map(col => {
// Add value formatters based on data type
if (col.field && gridOptions.rowData.length > 0) {
const sampleValue = gridOptions.rowData[0][col.field];
if (typeof sampleValue === 'number') {
col.type = 'numericColumn';
col.cellClass = 'ag-right-aligned-cell';
col.valueFormatter = params => {
if (params.value != null) {
return new Intl.NumberFormat().format(params.value);
}
return '';
};
} else if (typeof sampleValue === 'string' && !isNaN(Date.parse(sampleValue)) && sampleValue.includes('-')) {
col.valueFormatter = params => {
if (params.value) {
const date = new Date(params.value);
return date.toLocaleDateString();
}
return '';
};
} else if (typeof sampleValue === 'boolean') {
col.cellRenderer = params => {
return params.value ?
'<span style="color: #10b981;">✓ Yes</span>' :
'<span style="color: #ef4444;">✗ No</span>';
};
}
}
return col;
});
// Initialize the grid
const gridDiv = document.querySelector('#<?= $tableId ?>');
if (gridDiv && window.agGrid) {
const gridApi = agGrid.createGrid(gridDiv, gridOptions);
// Toolbar button handlers
document.getElementById('<?= $tableId ?>-export-csv')?.addEventListener('click', () => {
gridApi.exportDataAsCsv({
fileName: 'data-export-' + new Date().toISOString().split('T')[0] + '.csv'
});
});
document.getElementById('<?= $tableId ?>-clear-filters')?.addEventListener('click', () => {
gridApi.setFilterModel(null);
if (gridApi.setQuickFilter) {
gridApi.setQuickFilter('');
}
// Clear search input
const searchInput = document.getElementById('<?= $tableId ?>-search');
if (searchInput) {
searchInput.value = '';
}
});
// Auto-size columns on first data render
gridApi.addEventListener('firstDataRendered', () => {
gridApi.autoSizeAllColumns();
});
// Connect search input to quick filter
const searchInput = document.getElementById('<?= $tableId ?>-search');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const filterText = e.target.value;
if (gridApi.setQuickFilter) {
gridApi.setQuickFilter(filterText);
}
});
}
// Store grid API for external access
window['gridApi_<?= $tableId ?>'] = gridApi;
console.log('ag-Grid initialized for <?= $tableId ?> with', gridOptions.rowData.length, 'rows');
}
}
</script>
<?php
},
'about' => 'A powerful data grid component powered by ag-Grid with sorting, filtering, pagination, and export capabilities'
];