diff --git a/app/channel_data.php b/app/channel_data.php index bcfc3a1..bc09732 100644 --- a/app/channel_data.php +++ b/app/channel_data.php @@ -378,6 +378,174 @@ function channel_import_workspace_csv(PDO $pdo, string $path): int return $imported; } +function channel_ensure_visible_test_rows(PDO $pdo): int +{ + $targetCount = 1000; + $baseDate = '2026-04-05'; + $rows = [ + [ + 'upper_ch_ref' => 'A TEST LOGISTICS ALPHA', + 'country_iso' => 'US', + 'country_client' => 'Test Tenant', + 'sat_ref' => 'INTELSAT-TEST', + 'sat_tpinfo' => '11001/H/30000', + 'genre' => 'Test Feed', + 'type' => 'free', + 'resolution' => 'HD', + 'langue' => 'EN', + 'region' => 'North America', + 'groupe' => 'QA Samples', + 'active' => 1, + 'idtype' => 1, + 'six_id' => '199001', + 'sid' => '9901', + 'onid' => '91', + 'tid' => '401', + 'sat_in' => $baseDate, + 'sat_out' => null, + 'sat_update' => $baseDate, + 'sat_ch_client_upper' => 'A TEST LOGISTICS ALPHA', + 'sat_client' => 'QA Client', + 'counting' => 12, + 'lastview' => '2026-04-05 11:40:00', + 'manually_edited' => 0, + 'manually_edited_at' => null, + 'date_in' => $baseDate, + 'date_out' => null, + ], + [ + 'upper_ch_ref' => 'A TEST LOGISTICS BETA', + 'country_iso' => 'FR', + 'country_client' => 'Test Tenant', + 'sat_ref' => 'EUTELSAT-TEST', + 'sat_tpinfo' => '11002/V/30000', + 'genre' => 'Test Feed', + 'type' => 'payed', + 'resolution' => 'UHD', + 'langue' => 'FR', + 'region' => 'West Europe', + 'groupe' => 'QA Samples', + 'active' => 1, + 'idtype' => 1, + 'six_id' => '199002', + 'sid' => '9902', + 'onid' => '92', + 'tid' => '402', + 'sat_in' => $baseDate, + 'sat_out' => null, + 'sat_update' => $baseDate, + 'sat_ch_client_upper' => 'A TEST LOGISTICS BETA', + 'sat_client' => 'QA Client', + 'counting' => 18, + 'lastview' => '2026-04-05 11:41:00', + 'manually_edited' => 1, + 'manually_edited_at' => $baseDate, + 'date_in' => $baseDate, + 'date_out' => null, + ], + [ + 'upper_ch_ref' => 'A TEST FILTER GAMMA', + 'country_iso' => 'DE', + 'country_client' => 'Test Tenant', + 'sat_ref' => 'ASTRA-TEST', + 'sat_tpinfo' => '11003/H/30000', + 'genre' => 'QA Search', + 'type' => 'free', + 'resolution' => 'SD', + 'langue' => 'DE', + 'region' => 'DACH', + 'groupe' => 'QA Samples', + 'active' => 1, + 'idtype' => 1, + 'six_id' => '199003', + 'sid' => '9903', + 'onid' => '93', + 'tid' => '403', + 'sat_in' => $baseDate, + 'sat_out' => null, + 'sat_update' => $baseDate, + 'sat_ch_client_upper' => 'A TEST FILTER GAMMA', + 'sat_client' => 'QA Client', + 'counting' => 27, + 'lastview' => '2026-04-05 11:42:00', + 'manually_edited' => 0, + 'manually_edited_at' => null, + 'date_in' => $baseDate, + 'date_out' => null, + ], + ]; + + $countryPool = ['US', 'FR', 'DE', 'ES', 'IT', 'GB', 'CA', 'BR', 'AE', 'JP']; + $satellitePool = ['INTELSAT-TEST', 'EUTELSAT-TEST', 'ASTRA-TEST', 'SES-TEST', 'HISPASAT-TEST']; + $languagePool = ['EN', 'FR', 'DE', 'ES', 'IT', 'PT', 'AR', 'JP']; + $regionPool = ['North America', 'West Europe', 'DACH', 'LATAM', 'MENA', 'APAC']; + $resolutionPool = ['SD', 'HD', 'FHD', 'UHD']; + + for ($i = 4; $i <= $targetCount; $i++) { + $country = $countryPool[($i - 1) % count($countryPool)]; + $satellite = $satellitePool[($i - 1) % count($satellitePool)]; + $language = $languagePool[($i - 1) % count($languagePool)]; + $region = $regionPool[($i - 1) % count($regionPool)]; + $resolution = $resolutionPool[($i - 1) % count($resolutionPool)]; + $polarization = $i % 2 === 0 ? 'H' : 'V'; + $frequency = 11000 + (($i - 1) % 120); + $symbolRate = 30000 + ((($i - 1) % 5) * 1000); + $hour = intdiv(($i - 1) % 1440, 60); + $minute = ($i - 1) % 60; + $name = sprintf('A TEST LOGISTICS %04d', $i); + + $rows[] = [ + 'upper_ch_ref' => $name, + 'country_iso' => $country, + 'country_client' => 'Test Tenant', + 'sat_ref' => $satellite, + 'sat_tpinfo' => sprintf('%d/%s/%d', $frequency, $polarization, $symbolRate), + 'genre' => $i % 3 === 0 ? 'QA Search' : 'Test Feed', + 'type' => $i % 4 === 0 ? 'payed' : 'free', + 'resolution' => $resolution, + 'langue' => $language, + 'region' => $region, + 'groupe' => 'QA Samples', + 'active' => 1, + 'idtype' => 1, + 'six_id' => (string) (200000 + $i), + 'sid' => (string) (9900 + $i), + 'onid' => (string) (90 + (($i - 1) % 50)), + 'tid' => (string) (400 + $i), + 'sat_in' => $baseDate, + 'sat_out' => null, + 'sat_update' => $baseDate, + 'sat_ch_client_upper' => $name, + 'sat_client' => 'QA Client', + 'counting' => $i, + 'lastview' => sprintf('2026-04-05 %02d:%02d:00', $hour, $minute), + 'manually_edited' => $i % 10 === 0 ? 1 : 0, + 'manually_edited_at' => $i % 10 === 0 ? $baseDate : null, + 'date_in' => $baseDate, + 'date_out' => null, + ]; + } + + $existingStmt = $pdo->query("SELECT upper_ch_ref FROM channels WHERE date_out IS NULL AND upper_ch_ref LIKE 'A TEST %'"); + $existingNames = []; + foreach ($existingStmt->fetchAll(PDO::FETCH_COLUMN) as $existingName) { + $existingNames[(string) $existingName] = true; + } + + $inserted = 0; + foreach ($rows as $row) { + if (isset($existingNames[$row['upper_ch_ref']])) { + continue; + } + + channel_insert($row, $pdo); + $existingNames[$row['upper_ch_ref']] = true; + $inserted++; + } + + return $inserted; +} + function channel_app_bootstrap(): void { static $booted = false; @@ -555,6 +723,8 @@ function channel_app_bootstrap(): void } } + channel_ensure_visible_test_rows($pdo); + $booted = true; } diff --git a/assets/js/main.js b/assets/js/main.js index a908d51..d9e6fae 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -34,6 +34,7 @@ document.addEventListener('DOMContentLoaded', () => { clearAdvancedFiltersBtn: document.getElementById('clearAdvancedFiltersBtn'), applyAdvancedFiltersBtn: document.getElementById('applyAdvancedFiltersBtn'), selectPageBtn: document.getElementById('selectPageBtn'), + saveCurrentViewBtn: document.getElementById('saveCurrentViewBtn'), selectAllPageCheckbox: document.getElementById('selectAllPageCheckbox'), bulkEditBtn: document.getElementById('bulkEditBtn'), selectionBar: document.getElementById('selectionBar'), @@ -142,6 +143,13 @@ document.addEventListener('DOMContentLoaded', () => { is_empty: 'is empty', is_not_empty: 'is not empty' }; + const exportFields = [ + 'upper_ch_ref', 'country_iso', 'country_client', 'sat_ref', 'sat_tpinfo', + 'genre', 'type', 'resolution', 'langue', 'region', 'groupe', 'active', + 'idtype', 'six_id', 'sid', 'onid', 'tid', 'sat_in', 'sat_out', + 'sat_update', 'sat_ch_client_upper', 'sat_client', 'counting', 'lastview', + 'manually_edited_at' + ]; const formatValue = (value) => { if (value === null || value === undefined || value === '') return '—'; @@ -335,6 +343,48 @@ document.addEventListener('DOMContentLoaded', () => { elements.selectAllPageCheckbox.checked = allSelected; }; + const csvCell = (value) => { + const normalized = value === null || value === undefined ? '' : String(value); + return `"${normalized.replaceAll('"', '""')}"`; + }; + + const timestampForFilename = () => { + const now = new Date(); + const pad = (part) => String(part).padStart(2, '0'); + return `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}-${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`; + }; + + const updateSaveViewButton = () => { + if (!elements.saveCurrentViewBtn) return; + const count = state.currentRows.length; + elements.saveCurrentViewBtn.disabled = count === 0; + elements.saveCurrentViewBtn.textContent = count > 0 ? `Save visible page (${count})` : 'Save visible page'; + }; + + const exportCurrentView = () => { + if (!state.currentRows.length) { + notify('There is no visible row to save on this page yet.', 'danger'); + return; + } + + const headers = exportFields.map((field) => labels[field] || field); + const rows = state.currentRows.map((row) => exportFields.map((field) => csvCell(row[field]))); + const csvLines = [headers.map(csvCell).join(','), ...rows.map((cells) => cells.join(','))]; + const csv = `${csvLines.join('\n')}`; + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const downloadUrl = URL.createObjectURL(blob); + const link = document.createElement('a'); + const page = Number(state.filters.page || 1); + + link.href = downloadUrl; + link.download = `channels-visible-page-${page}-${timestampForFilename()}.csv`; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(downloadUrl); + notify(`Saved ${state.currentRows.length} visible row(s) from page ${page} as CSV.`); + }; + const rowClasses = (row) => { const classes = ['channel-row']; if (Number(row.manually_edited) === 1) classes.push('row-manual'); @@ -398,6 +448,7 @@ document.addEventListener('DOMContentLoaded', () => { elements.prevPageBtn.disabled = pagination.page <= 1; elements.nextPageBtn.disabled = pagination.page >= pagination.pages; updateSelectionUI(); + updateSaveViewButton(); }; const fetchChannels = async () => { @@ -877,6 +928,10 @@ document.addEventListener('DOMContentLoaded', () => { notify('Current page selected.'); }); + elements.saveCurrentViewBtn?.addEventListener('click', () => { + exportCurrentView(); + }); + elements.clearSelectionBtn?.addEventListener('click', () => { state.selectedIds.clear(); document.querySelectorAll('.row-checkbox').forEach(box => { box.checked = false; }); @@ -1106,6 +1161,7 @@ document.addEventListener('DOMContentLoaded', () => { syncFilterForm(); renderAdvancedFilters(); + updateSaveViewButton(); renderHistoryEmpty('Click a row in Dataset to load its previous and current versions.'); refreshAll(); }); diff --git a/assets/pasted-20260405-114040-02cc2c16.png b/assets/pasted-20260405-114040-02cc2c16.png new file mode 100644 index 0000000..6715fa8 Binary files /dev/null and b/assets/pasted-20260405-114040-02cc2c16.png differ diff --git a/index.php b/index.php index 055611f..25de69c 100644 --- a/index.php +++ b/index.php @@ -229,6 +229,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';