telvero_whatson_talpa/plans/copy-blocks-feature-plan.md

28 KiB

Plan: Blokken Kopiëren Functionaliteit

📋 Overzicht

Implementatie van een functie om blokken (inclusief transmissions en bloktijden) te kopiëren van andere dagen naar de huidige dag in de Excel Planner view.

🎯 Functionaliteit Vereisten

Wat wordt gekopieerd?

  • Transmissions: Alle uitzendingen binnen een specifiek blok
  • Bloktijden: De actual_start_time en actual_end_time van het bronblok

Gebruikersflow

  1. Gebruiker opent Excel Planner (planner.php)
  2. Gebruiker klikt op "Kopieer Blok" knop bij een specifiek blok
  3. Modal opent met:
    • Selectie van brondag (datepicker)
    • Selectie van bronblok (dropdown, gefilterd op zelfde zender en template)
  4. Gebruiker bevestigt kopieeractie
  5. Systeem:
    • Verwijdert alle bestaande transmissions in het doelblok
    • Kopieert bloktijden van bronblok naar doelblok
    • Kopieert alle transmissions van bronblok naar doelblok
    • Past start_date aan naar doeldag
    • Zet api_status op 'pending' (moet opnieuw gesynchroniseerd worden)
  6. Pagina herlaadt met gekopieerde data

🏗️ Architectuur

Database Schema

Bestaande tabellen die gebruikt worden:

daily_blocks

- id (primary key)
- template_id (foreign key naar block_templates)
- channel (VARCHAR)
- block_date (DATE)
- actual_start_time (TIME)
- actual_end_time (TIME)

transmissions

- id (primary key)
- infomercial_id (foreign key)
- channel (VARCHAR)
- template (VARCHAR)
- start_date (DATE)
- start_time (TIME)
- duration (TIME)
- api_status (ENUM: pending, synced, error)
- talpa_transmission_id (INT, nullable)

Dataflow Diagram

graph TD
    A[Gebruiker klikt Kopieer Blok] --> B[Modal opent]
    B --> C[Selecteer Brondag]
    C --> D[Selecteer Bronblok]
    D --> E[Bevestig Kopiëren]
    E --> F[API: copy_block.php]
    F --> G{Validatie}
    G -->|Invalid| H[Toon Foutmelding]
    G -->|Valid| I[Start Database Transactie]
    I --> J[Verwijder Bestaande Transmissions]
    J --> K[Update Bloktijden]
    K --> L[Kopieer Transmissions]
    L --> M[Commit Transactie]
    M --> N[Return Success]
    N --> O[Herlaad Pagina]

🔧 Implementatie Details

1. Frontend UI (planner.php)

Nieuwe Knop in Block Header

Locatie: In de block header, naast de bestaande "Sync naar Talpa" knop

<button type="button" 
        class="btn btn-sm btn-info ms-2"
        data-bs-toggle="modal"
        data-bs-target="#copyBlockModal<?= $block['id'] ?>"
        title="Kopieer blok van andere dag">
    <i class="bi bi-files"></i> Kopieer Blok
</button>

Modal voor Blok Kopiëren

<div class="modal fade" id="copyBlockModal<?= $block['id'] ?>" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <form id="copyBlockForm<?= $block['id'] ?>">
                <div class="modal-header">
                    <h5 class="modal-title">
                        <i class="bi bi-files"></i> Kopieer Blok
                    </h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div class="alert alert-info">
                        <strong>Doelblok:</strong><br>
                        <?= htmlspecialchars($block['template_name']) ?><br>
                        <?= htmlspecialchars($block['channel']) ?> - <?= formatDateDutch($selectedDate) ?>
                    </div>
                    
                    <div class="alert alert-warning">
                        <i class="bi bi-exclamation-triangle"></i>
                        <strong>Let op:</strong> Alle bestaande uitzendingen in dit blok worden overschreven!
                    </div>
                    
                    <div class="mb-3">
                        <label class="form-label">Brondag</label>
                        <input type="date" 
                               class="form-control" 
                               id="sourceDate<?= $block['id'] ?>"
                               required
                               onchange="loadSourceBlocks(<?= $block['id'] ?>, '<?= $block['channel'] ?>', <?= $block['template_id'] ?>)">
                    </div>
                    
                    <div class="mb-3">
                        <label class="form-label">Bronblok</label>
                        <select class="form-select" 
                                id="sourceBlock<?= $block['id'] ?>"
                                required>
                            <option value="">Selecteer eerst een brondag...</option>
                        </select>
                        <small class="text-muted">
                            Alleen blokken van hetzelfde type (<?= htmlspecialchars($block['template_name']) ?>) worden getoond
                        </small>
                    </div>
                    
                    <div id="sourceBlockPreview<?= $block['id'] ?>" class="mt-3" style="display: none;">
                        <h6>Preview Bronblok:</h6>
                        <div class="card">
                            <div class="card-body">
                                <div id="sourceBlockInfo<?= $block['id'] ?>"></div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
                        Annuleren
                    </button>
                    <button type="submit" class="btn btn-primary">
                        <i class="bi bi-files"></i> Kopieer Blok
                    </button>
                </div>
            </form>
        </div>
    </div>
</div>

JavaScript Functies

// Load available source blocks when date is selected
function loadSourceBlocks(targetBlockId, channel, templateId) {
    const sourceDate = document.getElementById(`sourceDate${targetBlockId}`).value;
    const sourceBlockSelect = document.getElementById(`sourceBlock${targetBlockId}`);
    
    if (!sourceDate) {
        sourceBlockSelect.innerHTML = '<option value="">Selecteer eerst een brondag...</option>';
        return;
    }
    
    // Show loading
    sourceBlockSelect.innerHTML = '<option value="">Laden...</option>';
    
    fetch(`api/get_available_source_blocks.php?date=${sourceDate}&channel=${channel}&template_id=${templateId}`)
        .then(response => response.json())
        .then(data => {
            if (data.success && data.blocks.length > 0) {
                let options = '<option value="">Selecteer bronblok...</option>';
                data.blocks.forEach(block => {
                    const transmissionCount = block.transmission_count || 0;
                    options += `<option value="${block.id}">
                        ${block.template_name} (${block.actual_start_time.substring(0,5)} - ${block.actual_end_time ? block.actual_end_time.substring(0,5) : '∞'})
                        - ${transmissionCount} uitzendingen
                    </option>`;
                });
                sourceBlockSelect.innerHTML = options;
            } else {
                sourceBlockSelect.innerHTML = '<option value="">Geen blokken beschikbaar op deze dag</option>';
            }
        })
        .catch(error => {
            console.error('Error loading source blocks:', error);
            sourceBlockSelect.innerHTML = '<option value="">Fout bij laden</option>';
        });
}

// Show preview of source block when selected
document.querySelectorAll('[id^="sourceBlock"]').forEach(select => {
    select.addEventListener('change', function() {
        const blockId = this.id.replace('sourceBlock', '');
        const sourceBlockId = this.value;
        
        if (!sourceBlockId) {
            document.getElementById(`sourceBlockPreview${blockId}`).style.display = 'none';
            return;
        }
        
        // Load block details
        fetch(`api/get_block_details.php?block_id=${sourceBlockId}`)
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    const preview = document.getElementById(`sourceBlockInfo${blockId}`);
                    let html = `
                        <p><strong>Starttijd:</strong> ${data.block.actual_start_time.substring(0,5)}</p>
                        <p><strong>Eindtijd:</strong> ${data.block.actual_end_time ? data.block.actual_end_time.substring(0,5) : '∞'}</p>
                        <p><strong>Aantal uitzendingen:</strong> ${data.transmissions.length}</p>
                    `;
                    
                    if (data.transmissions.length > 0) {
                        html += '<hr><p><strong>Uitzendingen:</strong></p><ul class="small">';
                        data.transmissions.forEach(tx => {
                            html += `<li>${tx.start_time.substring(0,5)} - ${tx.title}</li>`;
                        });
                        html += '</ul>';
                    }
                    
                    preview.innerHTML = html;
                    document.getElementById(`sourceBlockPreview${blockId}`).style.display = 'block';
                }
            });
    });
});

// Handle copy block form submission
document.querySelectorAll('[id^="copyBlockForm"]').forEach(form => {
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        
        const blockId = this.id.replace('copyBlockForm', '');
        const sourceDate = document.getElementById(`sourceDate${blockId}`).value;
        const sourceBlockId = document.getElementById(`sourceBlock${blockId}`).value;
        
        if (!sourceDate || !sourceBlockId) {
            alert('Selecteer een brondag en bronblok');
            return;
        }
        
        if (!confirm('Weet je zeker dat je dit blok wilt kopiëren? Alle bestaande uitzendingen worden overschreven!')) {
            return;
        }
        
        // Show loading
        const submitBtn = this.querySelector('button[type="submit"]');
        const originalHTML = submitBtn.innerHTML;
        submitBtn.disabled = true;
        submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Kopiëren...';
        
        // Call API
        fetch('api/copy_block.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                source_block_id: sourceBlockId,
                target_block_id: blockId,
                target_date: '<?= $selectedDate ?>'
            })
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                alert(`✓ Blok succesvol gekopieerd!\n\nGekopieerd: ${data.copied_count} uitzendingen`);
                window.location.reload();
            } else {
                alert('✗ Fout bij kopiëren: ' + (data.error || 'Onbekende fout'));
                submitBtn.disabled = false;
                submitBtn.innerHTML = originalHTML;
            }
        })
        .catch(error => {
            console.error('Error:', error);
            alert('✗ Netwerkfout bij kopiëren');
            submitBtn.disabled = false;
            submitBtn.innerHTML = originalHTML;
        });
    });
});

2. Backend API Endpoints

API: api/get_available_source_blocks.php

Doel: Haal beschikbare bronblokken op voor een specifieke datum, zender en template

Input (GET parameters):

  • date (required): Brondag in Y-m-d formaat
  • channel (required): Zender naam (SBS9, NET5, VERONICA)
  • template_id (required): Template ID

Output (JSON):

{
  "success": true,
  "blocks": [
    {
      "id": 123,
      "template_id": 5,
      "template_name": "SBS9 Dagblok",
      "channel": "SBS9",
      "block_date": "2026-01-15",
      "actual_start_time": "07:00:00",
      "actual_end_time": "15:00:00",
      "transmission_count": 12
    }
  ]
}

Implementatie:

<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../helpers.php';

use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

header('Content-Type: application/json');

try {
    $db = getDbConnection();
    
    // Validate input
    $date = $_GET['date'] ?? null;
    $channel = $_GET['channel'] ?? null;
    $templateId = $_GET['template_id'] ?? null;
    
    if (!$date || !$channel || !$templateId) {
        jsonResponse(['success' => false, 'error' => 'Missing parameters'], 400);
    }
    
    if (!isValidDate($date)) {
        jsonResponse(['success' => false, 'error' => 'Invalid date format'], 400);
    }
    
    // Ensure blocks exist for this date
    ensureDailyBlocks($db, $date, $date);
    
    // Get blocks for this date, channel, and template
    $stmt = $db->prepare("
        SELECT 
            db.*,
            bt.name as template_name,
            COUNT(t.id) as transmission_count
        FROM daily_blocks db
        LEFT JOIN block_templates bt ON db.template_id = bt.id
        LEFT JOIN transmissions t ON t.start_date = db.block_date 
            AND t.channel = db.channel
            AND t.start_time >= db.actual_start_time
            AND t.start_time < COALESCE(db.actual_end_time, '23:59:59')
        WHERE db.block_date = ?
        AND db.channel = ?
        AND db.template_id = ?
        GROUP BY db.id
        ORDER BY db.actual_start_time
    ");
    $stmt->execute([$date, $channel, $templateId]);
    $blocks = $stmt->fetchAll();
    
    jsonResponse([
        'success' => true,
        'blocks' => $blocks
    ]);
    
} catch (Exception $e) {
    jsonResponse(['success' => false, 'error' => $e->getMessage()], 500);
}

API: api/get_block_details.php

Doel: Haal gedetailleerde informatie op over een specifiek blok (voor preview)

Input (GET parameters):

  • block_id (required): ID van het blok

Output (JSON):

{
  "success": true,
  "block": {
    "id": 123,
    "template_name": "SBS9 Dagblok",
    "channel": "SBS9",
    "block_date": "2026-01-15",
    "actual_start_time": "07:00:00",
    "actual_end_time": "15:00:00"
  },
  "transmissions": [
    {
      "id": 456,
      "title": "Product A",
      "start_time": "07:00:00",
      "duration": "00:28:30",
      "series_code": "001a"
    }
  ]
}

Implementatie:

<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../helpers.php';

use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

header('Content-Type: application/json');

try {
    $db = getDbConnection();
    
    $blockId = $_GET['block_id'] ?? null;
    
    if (!$blockId) {
        jsonResponse(['success' => false, 'error' => 'Missing block_id'], 400);
    }
    
    // Get block info
    $stmt = $db->prepare("
        SELECT db.*, bt.name as template_name
        FROM daily_blocks db
        LEFT JOIN block_templates bt ON db.template_id = bt.id
        WHERE db.id = ?
    ");
    $stmt->execute([$blockId]);
    $block = $stmt->fetch();
    
    if (!$block) {
        jsonResponse(['success' => false, 'error' => 'Block not found'], 404);
    }
    
    // Get transmissions in this block
    $stmt = $db->prepare("
        SELECT t.*, c.title, c.series_code
        FROM transmissions t
        JOIN infomercials c ON t.infomercial_id = c.id
        WHERE t.start_date = ?
        AND t.channel = ?
        AND t.start_time >= ?
        AND t.start_time < ?
        ORDER BY t.start_time ASC
    ");
    $stmt->execute([
        $block['block_date'],
        $block['channel'],
        $block['actual_start_time'],
        $block['actual_end_time'] ?? '23:59:59'
    ]);
    $transmissions = $stmt->fetchAll();
    
    jsonResponse([
        'success' => true,
        'block' => $block,
        'transmissions' => $transmissions
    ]);
    
} catch (Exception $e) {
    jsonResponse(['success' => false, 'error' => $e->getMessage()], 500);
}

API: api/copy_block.php

Doel: Kopieer een blok (bloktijden + transmissions) van bronblok naar doelblok

Input (POST JSON):

{
  "source_block_id": 123,
  "target_block_id": 456,
  "target_date": "2026-01-16"
}

Output (JSON):

{
  "success": true,
  "message": "Block copied successfully",
  "copied_count": 12,
  "deleted_count": 5
}

Implementatie:

<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../helpers.php';

use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

header('Content-Type: application/json');

try {
    $db = getDbConnection();
    
    // Get POST data
    $input = json_decode(file_get_contents('php://input'), true);
    
    $sourceBlockId = $input['source_block_id'] ?? null;
    $targetBlockId = $input['target_block_id'] ?? null;
    $targetDate = $input['target_date'] ?? null;
    
    // Validate input
    if (!$sourceBlockId || !$targetBlockId || !$targetDate) {
        jsonResponse(['success' => false, 'error' => 'Missing required parameters'], 400);
    }
    
    if (!isValidDate($targetDate)) {
        jsonResponse(['success' => false, 'error' => 'Invalid target date format'], 400);
    }
    
    // Start transaction
    $db->beginTransaction();
    
    try {
        // Get source block info
        $stmt = $db->prepare("
            SELECT * FROM daily_blocks WHERE id = ?
        ");
        $stmt->execute([$sourceBlockId]);
        $sourceBlock = $stmt->fetch();
        
        if (!$sourceBlock) {
            throw new Exception('Source block not found');
        }
        
        // Get target block info
        $stmt = $db->prepare("
            SELECT * FROM daily_blocks WHERE id = ?
        ");
        $stmt->execute([$targetBlockId]);
        $targetBlock = $stmt->fetch();
        
        if (!$targetBlock) {
            throw new Exception('Target block not found');
        }
        
        // Validate that blocks are compatible (same channel and template)
        if ($sourceBlock['channel'] !== $targetBlock['channel']) {
            throw new Exception('Source and target blocks must be on the same channel');
        }
        
        if ($sourceBlock['template_id'] !== $targetBlock['template_id']) {
            throw new Exception('Source and target blocks must use the same template');
        }
        
        // Step 1: Update target block times
        $stmt = $db->prepare("
            UPDATE daily_blocks
            SET actual_start_time = ?,
                actual_end_time = ?
            WHERE id = ?
        ");
        $stmt->execute([
            $sourceBlock['actual_start_time'],
            $sourceBlock['actual_end_time'],
            $targetBlockId
        ]);
        
        // Step 2: Delete existing transmissions in target block
        $stmt = $db->prepare("
            DELETE FROM transmissions
            WHERE start_date = ?
            AND channel = ?
            AND start_time >= ?
            AND start_time < ?
        ");
        $deletedCount = $stmt->execute([
            $targetDate,
            $targetBlock['channel'],
            $targetBlock['actual_start_time'],
            $targetBlock['actual_end_time'] ?? '23:59:59'
        ]);
        $deletedCount = $stmt->rowCount();
        
        // Step 3: Get source transmissions
        $stmt = $db->prepare("
            SELECT t.*, c.duration
            FROM transmissions t
            JOIN infomercials c ON t.infomercial_id = c.id
            WHERE t.start_date = ?
            AND t.channel = ?
            AND t.start_time >= ?
            AND t.start_time < ?
            ORDER BY t.start_time ASC
        ");
        $stmt->execute([
            $sourceBlock['block_date'],
            $sourceBlock['channel'],
            $sourceBlock['actual_start_time'],
            $sourceBlock['actual_end_time'] ?? '23:59:59'
        ]);
        $sourceTransmissions = $stmt->fetchAll();
        
        // Step 4: Copy transmissions to target block
        $copiedCount = 0;
        $insertStmt = $db->prepare("
            INSERT INTO transmissions
            (infomercial_id, channel, template, start_date, start_time, duration, api_status)
            VALUES (?, ?, ?, ?, ?, ?, 'pending')
        ");
        
        foreach ($sourceTransmissions as $tx) {
            $insertStmt->execute([
                $tx['infomercial_id'],
                $tx['channel'],
                $tx['template'],
                $targetDate,  // Use target date
                $tx['start_time'],
                $tx['duration'],
            ]);
            $copiedCount++;
        }
        
        // Commit transaction
        $db->commit();
        
        jsonResponse([
            'success' => true,
            'message' => 'Block copied successfully',
            'copied_count' => $copiedCount,
            'deleted_count' => $deletedCount,
            'block_times_updated' => true
        ]);
        
    } catch (Exception $e) {
        $db->rollBack();
        throw $e;
    }
    
} catch (Exception $e) {
    jsonResponse([
        'success' => false,
        'error' => $e->getMessage()
    ], 500);
}

3. Database Operaties

Transactie Flow

-- Start transactie
BEGIN;

-- 1. Update doelblok tijden
UPDATE daily_blocks
SET actual_start_time = '07:00:00',
    actual_end_time = '15:00:00'
WHERE id = [target_block_id];

-- 2. Verwijder bestaande transmissions in doelblok
DELETE FROM transmissions
WHERE start_date = '2026-01-16'
AND channel = 'SBS9'
AND start_time >= '07:00:00'
AND start_time < '15:00:00';

-- 3. Kopieer transmissions van bronblok
INSERT INTO transmissions
(infomercial_id, channel, template, start_date, start_time, duration, api_status)
SELECT 
    infomercial_id,
    channel,
    template,
    '2026-01-16' as start_date,  -- Nieuwe datum
    start_time,
    duration,
    'pending' as api_status       -- Reset sync status
FROM transmissions
WHERE start_date = '2026-01-15'
AND channel = 'SBS9'
AND start_time >= '07:00:00'
AND start_time < '15:00:00';

-- Commit transactie
COMMIT;

⚠️ Edge Cases & Validatie

Validatie Regels

  1. Bronblok bestaat: Controleer of source_block_id bestaat in database
  2. Doelblok bestaat: Controleer of target_block_id bestaat in database
  3. Zelfde zender: Bronblok en doelblok moeten op dezelfde zender zijn
  4. Zelfde template: Bronblok en doelblok moeten hetzelfde template gebruiken
  5. Geldige datum: target_date moet een geldige datum zijn in Y-m-d formaat
  6. Niet in het verleden: Optioneel - waarschuwing als doeldatum in het verleden ligt

Edge Cases

Scenario Gedrag
Bronblok heeft geen transmissions Doelblok wordt leeggemaakt, bloktijden worden gekopieerd
Doelblok heeft al transmissions Alle bestaande transmissions worden verwijderd voor kopiëren
Bronblok heeft aangepaste bloktijden Bloktijden worden gekopieerd naar doelblok
Infomercial bestaat niet meer Fout - transactie wordt teruggedraaid
Database fout tijdens kopiëren Transactie wordt teruggedraaid, geen data wordt gewijzigd
Gebruiker annuleert tijdens kopiëren Geen effect - kopiëren gebeurt server-side in één transactie

Foutafhandeling

// Frontend error handling
.catch(error => {
    console.error('Copy block error:', error);
    alert('Er is een fout opgetreden bij het kopiëren van het blok. Probeer het opnieuw.');
});
// Backend error handling
try {
    $db->beginTransaction();
    // ... copy operations ...
    $db->commit();
} catch (Exception $e) {
    $db->rollBack();
    error_log("Copy block error: " . $e->getMessage());
    jsonResponse([
        'success' => false,
        'error' => 'Database error: ' . $e->getMessage()
    ], 500);
}

🎨 UI/UX Overwegingen

Visuele Feedback

  1. Loading States:

    • Spinner tijdens laden van bronblokken
    • Spinner tijdens kopiëren
    • Disabled buttons tijdens operaties
  2. Confirmatie:

    • Waarschuwing dat bestaande data wordt overschreven
    • Bevestigingsdialoog voor kopiëren
  3. Success/Error Messages:

    • Success: "✓ Blok succesvol gekopieerd! Gekopieerd: X uitzendingen"
    • Error: "✗ Fout bij kopiëren: [error message]"
  4. Preview:

    • Toon aantal uitzendingen in bronblok
    • Toon bloktijden van bronblok
    • Optioneel: lijst van uitzendingen

Toegankelijkheid

  • Keyboard navigatie in modal
  • ARIA labels voor screen readers
  • Focus management bij openen/sluiten modal
  • Clear error messages

📝 Testing Checklist

Functionele Tests

  • Kopieer blok met transmissions werkt correct
  • Kopieer leeg blok werkt correct
  • Bloktijden worden correct gekopieerd
  • Bestaande transmissions worden verwijderd
  • api_status wordt gereset naar 'pending'
  • start_date wordt correct aangepast
  • Transactie rollback werkt bij fouten
  • Validatie van zelfde zender werkt
  • Validatie van zelfde template werkt

UI Tests

  • Modal opent correct
  • Brondag selectie werkt
  • Bronblok dropdown wordt correct gevuld
  • Preview toont correcte informatie
  • Loading states worden getoond
  • Success message wordt getoond
  • Error messages worden getoond
  • Pagina herlaadt na succesvol kopiëren

Edge Case Tests

  • Kopieer van dag zonder blokken
  • Kopieer naar dag in het verleden
  • Kopieer met niet-bestaande infomercials
  • Kopieer tijdens database fout
  • Kopieer met ongeldige parameters

🚀 Implementatie Volgorde

  1. Backend API's (eerst testen met Postman/curl):

    • api/get_available_source_blocks.php
    • api/get_block_details.php
    • api/copy_block.php
  2. Frontend UI:

    • Voeg "Kopieer Blok" knop toe aan block headers
    • Implementeer copy block modal
    • Implementeer JavaScript functies
  3. Testing:

    • Test alle API endpoints
    • Test UI flows
    • Test edge cases
  4. Documentatie:

    • Update README.md met nieuwe functionaliteit
    • Voeg screenshots toe (optioneel)

📚 Benodigde Bestanden

Nieuwe Bestanden

  • api/get_available_source_blocks.php - API voor beschikbare bronblokken
  • api/get_block_details.php - API voor blok details (preview)
  • api/copy_block.php - API voor blok kopiëren

Te Wijzigen Bestanden

  • planner.php - Voeg UI toe voor kopieer functionaliteit
  • README.md - Documenteer nieuwe functionaliteit

Geen Wijzigingen Nodig

  • Database schema (bestaande tabellen zijn voldoende)
  • helpers.php (bestaande functies zijn voldoende)
  • Andere PHP bestanden

🔒 Beveiliging

  1. Input Validatie:

    • Valideer alle GET/POST parameters
    • Gebruik prepared statements voor alle queries
    • Valideer datum formaten
  2. Autorisatie:

    • Optioneel: voeg gebruikersauthenticatie toe
    • Controleer of gebruiker rechten heeft om blokken te kopiëren
  3. Database:

    • Gebruik transacties voor atomaire operaties
    • Rollback bij fouten
    • Error logging zonder gevoelige data

📊 Performance Overwegingen

  1. Database Queries:

    • Gebruik indexes op start_date, channel, start_time
    • Batch insert voor transmissions (indien mogelijk)
    • Limit aantal transmissions per blok (praktisch max ~50)
  2. Frontend:

    • Lazy load bronblokken (alleen bij selectie datum)
    • Debounce date picker events
    • Cache block details voor preview
  3. API Response Times:

    • Target: < 500ms voor get_available_source_blocks
    • Target: < 200ms voor get_block_details
    • Target: < 1000ms voor copy_block (inclusief database operaties)

Acceptatie Criteria

De functionaliteit is compleet wanneer:

  1. Gebruiker kan een brondag selecteren in de planner
  2. Gebruiker kan een bronblok selecteren (gefilterd op zelfde template)
  3. Gebruiker ziet een preview van het bronblok
  4. Gebruiker kan het blok kopiëren met één klik
  5. Alle transmissions worden correct gekopieerd
  6. Bloktijden worden correct gekopieerd
  7. Bestaande transmissions in doelblok worden overschreven
  8. api_status wordt gereset naar 'pending'
  9. Pagina toont gekopieerde data na herladen
  10. Foutmeldingen worden duidelijk getoond bij problemen

Geschatte Implementatietijd: 4-6 uur

  • Backend API's: 2-3 uur
  • Frontend UI: 1.5-2 uur
  • Testing & debugging: 0.5-1 uur