596 lines
18 KiB
Markdown
596 lines
18 KiB
Markdown
# Plan: Visuele Sync Status Indicators in Planner
|
|
|
|
## Overzicht
|
|
|
|
Dit plan beschrijft de implementatie van visuele sync status indicators in de planner interface. Gebruikers kunnen direct zien of een transmission gesynchroniseerd is met Talpa of alleen lokaal bestaat.
|
|
|
|
## Huidige Situatie
|
|
|
|
### Database Schema
|
|
De `transmissions` tabel heeft de volgende relevante kolommen:
|
|
- `api_status` - Status van de sync: 'pending', 'synced', 'error'
|
|
- `talpa_transmission_id` - ID van de transmission in Talpa (NULL als nog niet gesynchroniseerd)
|
|
- `api_response` - JSON response van Talpa API
|
|
|
|
### Sync Logica
|
|
- **Nieuwe transmissions**: `api_status = 'pending'`, `talpa_transmission_id = NULL`
|
|
- **Gesynchroniseerde transmissions**: `api_status = 'synced'`, `talpa_transmission_id` heeft waarde
|
|
- **Gefaalde sync**: `api_status = 'error'`, `talpa_transmission_id` kan NULL zijn
|
|
|
|
## Vereisten
|
|
|
|
### Functionele Vereisten
|
|
1. **Per Transmission Indicator**: Elke transmission moet een visuele indicator tonen
|
|
- 🔴 Rood bolletje: Niet gesynchroniseerd (pending/error)
|
|
- 🟢 Groen bolletje: Gesynchroniseerd met Talpa
|
|
|
|
2. **Block-Level Indicator**: Elk blok moet een overall status tonen
|
|
- Alle transmissions gesynchroniseerd → Groen "Gepland" badge
|
|
- Één of meer transmissions niet gesynchroniseerd → Oranje "Deels Gepland" badge
|
|
- Geen transmissions gesynchroniseerd → Rood "Niet Gepland" badge
|
|
- Geen transmissions in blok → Grijs "Leeg" badge
|
|
|
|
3. **Tooltip Informatie**: Bij hover over indicator extra details tonen
|
|
- Sync status
|
|
- Talpa transmission ID (indien beschikbaar)
|
|
- Laatste sync tijd (indien beschikbaar)
|
|
|
|
### Niet-Functionele Vereisten
|
|
- Minimale impact op bestaande code
|
|
- Geen extra database queries (gebruik bestaande data)
|
|
- Responsive design (werkt op mobile)
|
|
- Consistent met bestaande UI styling
|
|
|
|
## Ontwerp
|
|
|
|
### Visuele Elementen
|
|
|
|
#### 1. Transmission-Level Indicator (Kolom "Talpa")
|
|
|
|
```html
|
|
<!-- Groen: Gesynchroniseerd -->
|
|
<td class="text-center">
|
|
<span class="sync-indicator sync-success"
|
|
title="Gesynchroniseerd met Talpa"
|
|
data-bs-toggle="tooltip">
|
|
<i class="bi bi-circle-fill"></i>
|
|
</span>
|
|
</td>
|
|
|
|
<!-- Rood: Niet gesynchroniseerd -->
|
|
<td class="text-center">
|
|
<span class="sync-indicator sync-pending"
|
|
title="Nog niet gesynchroniseerd"
|
|
data-bs-toggle="tooltip">
|
|
<i class="bi bi-circle-fill"></i>
|
|
</span>
|
|
</td>
|
|
|
|
<!-- Oranje: Error -->
|
|
<td class="text-center">
|
|
<span class="sync-indicator sync-error"
|
|
title="Sync fout - klik voor details"
|
|
data-bs-toggle="tooltip">
|
|
<i class="bi bi-exclamation-circle-fill"></i>
|
|
</span>
|
|
</td>
|
|
```
|
|
|
|
**CSS Styling:**
|
|
```css
|
|
.sync-indicator {
|
|
font-size: 1.2rem;
|
|
cursor: help;
|
|
}
|
|
|
|
.sync-indicator.sync-success {
|
|
color: #28a745; /* Groen */
|
|
}
|
|
|
|
.sync-indicator.sync-pending {
|
|
color: #dc3545; /* Rood */
|
|
}
|
|
|
|
.sync-indicator.sync-error {
|
|
color: #ffc107; /* Oranje/Geel */
|
|
}
|
|
|
|
.sync-indicator i {
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.sync-indicator:hover i {
|
|
transform: scale(1.2);
|
|
}
|
|
```
|
|
|
|
#### 2. Block-Level Indicator (In Header)
|
|
|
|
```html
|
|
<div class="block-header d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<?= htmlspecialchars($block['template_name'] ?? 'Blok') ?>
|
|
|
|
<!-- Block Sync Status Badge -->
|
|
<span class="badge block-sync-status block-sync-complete ms-2">
|
|
<i class="bi bi-check-circle-fill"></i> Gepland
|
|
</span>
|
|
|
|
<!-- Buttons -->
|
|
<button type="button" class="btn btn-sm btn-outline-dark ms-2">
|
|
<i class="bi bi-clock"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-success ms-2">
|
|
<i class="bi bi-cloud-upload"></i> Sync naar Talpa
|
|
</button>
|
|
</div>
|
|
<div>
|
|
<?= substr($blockStart, 0, 5) ?> - <?= substr($blockEnd, 0, 5) ?>
|
|
| Resterend: <strong><?= round($remainingMinutes) ?> min</strong>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Badge Varianten:**
|
|
```css
|
|
/* Volledig gesynchroniseerd */
|
|
.block-sync-status.block-sync-complete {
|
|
background-color: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
/* Deels gesynchroniseerd */
|
|
.block-sync-status.block-sync-partial {
|
|
background-color: #ffc107;
|
|
color: #000;
|
|
}
|
|
|
|
/* Niet gesynchroniseerd */
|
|
.block-sync-status.block-sync-none {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
/* Leeg blok */
|
|
.block-sync-status.block-sync-empty {
|
|
background-color: #6c757d;
|
|
color: white;
|
|
}
|
|
|
|
.block-sync-status {
|
|
font-size: 0.85rem;
|
|
padding: 0.35em 0.65em;
|
|
font-weight: 600;
|
|
}
|
|
```
|
|
|
|
### Database Queries
|
|
|
|
#### Query voor Block Sync Status
|
|
```php
|
|
// In planner.php, binnen de block loop
|
|
$stmt = $db->prepare("
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN api_status = 'synced' AND talpa_transmission_id IS NOT NULL THEN 1 ELSE 0 END) as synced,
|
|
SUM(CASE WHEN api_status = 'error' THEN 1 ELSE 0 END) as errors
|
|
FROM transmissions t
|
|
WHERE t.start_date = ?
|
|
AND t.channel = ?
|
|
AND t.start_time >= ?
|
|
AND t.start_time < ?
|
|
");
|
|
$stmt->execute([$selectedDate, $channel, $blockStart, $blockEnd]);
|
|
$blockSyncStats = $stmt->fetch();
|
|
|
|
// Bepaal block status
|
|
$blockSyncClass = 'block-sync-empty';
|
|
$blockSyncLabel = 'Leeg';
|
|
$blockSyncIcon = 'bi-inbox';
|
|
|
|
if ($blockSyncStats['total'] > 0) {
|
|
if ($blockSyncStats['synced'] == $blockSyncStats['total']) {
|
|
$blockSyncClass = 'block-sync-complete';
|
|
$blockSyncLabel = 'Gepland';
|
|
$blockSyncIcon = 'bi-check-circle-fill';
|
|
} elseif ($blockSyncStats['synced'] > 0) {
|
|
$blockSyncClass = 'block-sync-partial';
|
|
$blockSyncLabel = "Deels Gepland ({$blockSyncStats['synced']}/{$blockSyncStats['total']})";
|
|
$blockSyncIcon = 'bi-exclamation-triangle-fill';
|
|
} else {
|
|
$blockSyncClass = 'block-sync-none';
|
|
$blockSyncLabel = 'Niet Gepland';
|
|
$blockSyncIcon = 'bi-x-circle-fill';
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Query voor Transmission Details (Tooltip)
|
|
```php
|
|
// Reeds beschikbaar in bestaande query
|
|
// Voeg toe aan SELECT:
|
|
$stmt = $db->prepare("
|
|
SELECT t.*,
|
|
c.title,
|
|
c.color_code,
|
|
c.series_code,
|
|
t.api_status,
|
|
t.talpa_transmission_id,
|
|
t.api_response
|
|
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
|
|
");
|
|
```
|
|
|
|
### Implementatie Stappen
|
|
|
|
#### Stap 1: Update CSS (assets/css/custom.css)
|
|
Voeg nieuwe CSS classes toe voor sync indicators:
|
|
```css
|
|
/* Sync Status Indicators */
|
|
.sync-indicator {
|
|
font-size: 1.2rem;
|
|
cursor: help;
|
|
display: inline-block;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.sync-indicator.sync-success {
|
|
color: #28a745;
|
|
}
|
|
|
|
.sync-indicator.sync-pending {
|
|
color: #dc3545;
|
|
}
|
|
|
|
.sync-indicator.sync-error {
|
|
color: #ffc107;
|
|
}
|
|
|
|
.sync-indicator:hover {
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
/* Block Sync Status Badges */
|
|
.block-sync-status {
|
|
font-size: 0.85rem;
|
|
padding: 0.35em 0.65em;
|
|
font-weight: 600;
|
|
border-radius: 4px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.block-sync-status.block-sync-complete {
|
|
background-color: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.block-sync-status.block-sync-partial {
|
|
background-color: #ffc107;
|
|
color: #000;
|
|
}
|
|
|
|
.block-sync-status.block-sync-none {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
.block-sync-status.block-sync-empty {
|
|
background-color: #6c757d;
|
|
color: white;
|
|
}
|
|
```
|
|
|
|
#### Stap 2: Update Table Header (planner.php)
|
|
Voeg nieuwe kolom toe in table header (na "Restant", voor "Acties"):
|
|
```php
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th style="width: 40px;"></th>
|
|
<th style="width: 50px;">Code</th>
|
|
<th>Product</th>
|
|
<th style="width: 80px;">Duur</th>
|
|
<th style="width: 70px;">Start</th>
|
|
<th style="width: 70px;">Eind</th>
|
|
<th style="width: 80px;">Restant</th>
|
|
<th style="width: 50px;" class="text-center">Talpa</th> <!-- NIEUW -->
|
|
<th style="width: 100px;">Acties</th>
|
|
</tr>
|
|
</thead>
|
|
```
|
|
|
|
#### Stap 3: Update Transmission Row (planner.php)
|
|
Voeg sync indicator toe in transmission row:
|
|
```php
|
|
<tr style="background-color: <?= $tx['color_code'] ?>15;">
|
|
<!-- Bestaande kolommen -->
|
|
<td>
|
|
<div class="color-indicator" style="background-color: <?= htmlspecialchars($tx['color_code']) ?>;"></div>
|
|
</td>
|
|
<td class="text-center">
|
|
<small><strong><?= htmlspecialchars($tx['series_code'] ?? '-') ?></strong></small>
|
|
</td>
|
|
<td>
|
|
<strong><?= htmlspecialchars($tx['title']) ?></strong>
|
|
</td>
|
|
<td class="duration-cell">
|
|
<?= $durationMinutes ?> min
|
|
</td>
|
|
<td class="time-cell text-success">
|
|
<?= substr($tx['start_time'], 0, 5) ?>
|
|
</td>
|
|
<td class="time-cell text-danger">
|
|
<?= substr($endTime, 0, 5) ?>
|
|
</td>
|
|
<td class="text-center remaining-time">
|
|
<?= $txRemainingMinutes ?> min
|
|
</td>
|
|
|
|
<!-- NIEUWE KOLOM: Talpa Sync Status -->
|
|
<td class="text-center">
|
|
<?php
|
|
$syncClass = 'sync-pending';
|
|
$syncIcon = 'bi-circle-fill';
|
|
$syncTitle = 'Nog niet gesynchroniseerd';
|
|
|
|
if ($tx['api_status'] === 'synced' && !empty($tx['talpa_transmission_id'])) {
|
|
$syncClass = 'sync-success';
|
|
$syncTitle = 'Gesynchroniseerd met Talpa\nID: ' . $tx['talpa_transmission_id'];
|
|
} elseif ($tx['api_status'] === 'error') {
|
|
$syncClass = 'sync-error';
|
|
$syncIcon = 'bi-exclamation-circle-fill';
|
|
$syncTitle = 'Sync fout - klik voor details';
|
|
}
|
|
?>
|
|
<span class="sync-indicator <?= $syncClass ?>"
|
|
title="<?= htmlspecialchars($syncTitle) ?>"
|
|
data-bs-toggle="tooltip"
|
|
data-bs-placement="left">
|
|
<i class="bi <?= $syncIcon ?>"></i>
|
|
</span>
|
|
</td>
|
|
|
|
<!-- Bestaande acties kolom -->
|
|
<td>
|
|
<!-- ... bestaande buttons ... -->
|
|
</td>
|
|
</tr>
|
|
```
|
|
|
|
#### Stap 4: Update Block Header (planner.php)
|
|
Voeg block sync status badge toe:
|
|
```php
|
|
<?php
|
|
// Bereken block sync stats
|
|
$stmt = $db->prepare("
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN api_status = 'synced' AND talpa_transmission_id IS NOT NULL THEN 1 ELSE 0 END) as synced,
|
|
SUM(CASE WHEN api_status = 'error' THEN 1 ELSE 0 END) as errors
|
|
FROM transmissions t
|
|
WHERE t.start_date = ?
|
|
AND t.channel = ?
|
|
AND t.start_time >= ?
|
|
AND t.start_time < ?
|
|
");
|
|
$stmt->execute([$selectedDate, $channel, $blockStart, $blockEnd]);
|
|
$blockSyncStats = $stmt->fetch();
|
|
|
|
// Bepaal block status
|
|
$blockSyncClass = 'block-sync-empty';
|
|
$blockSyncLabel = 'Leeg';
|
|
$blockSyncIcon = 'bi-inbox';
|
|
|
|
if ($blockSyncStats['total'] > 0) {
|
|
if ($blockSyncStats['synced'] == $blockSyncStats['total']) {
|
|
$blockSyncClass = 'block-sync-complete';
|
|
$blockSyncLabel = 'Gepland';
|
|
$blockSyncIcon = 'bi-check-circle-fill';
|
|
} elseif ($blockSyncStats['synced'] > 0) {
|
|
$blockSyncClass = 'block-sync-partial';
|
|
$blockSyncLabel = "Deels ({$blockSyncStats['synced']}/{$blockSyncStats['total']})";
|
|
$blockSyncIcon = 'bi-exclamation-triangle-fill';
|
|
} else {
|
|
$blockSyncClass = 'block-sync-none';
|
|
$blockSyncLabel = 'Niet Gepland';
|
|
$blockSyncIcon = 'bi-x-circle-fill';
|
|
}
|
|
}
|
|
?>
|
|
|
|
<div class="block-header d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<?= htmlspecialchars($block['template_name'] ?? 'Blok') ?>
|
|
|
|
<!-- Block Sync Status Badge -->
|
|
<span class="badge block-sync-status <?= $blockSyncClass ?> ms-2"
|
|
title="<?= $blockSyncStats['synced'] ?> van <?= $blockSyncStats['total'] ?> transmissions gesynchroniseerd"
|
|
data-bs-toggle="tooltip">
|
|
<i class="bi <?= $blockSyncIcon ?>"></i> <?= $blockSyncLabel ?>
|
|
</span>
|
|
|
|
<!-- Bestaande buttons -->
|
|
<button type="button" class="btn btn-sm btn-outline-dark ms-2"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#blockTimeModal<?= $block['id'] ?>"
|
|
title="Starttijd aanpassen">
|
|
<i class="bi bi-clock"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-success ms-2"
|
|
onclick="syncBlockPlanner('<?= $selectedDate ?>', '<?= $channel ?>')"
|
|
title="Sync blok naar Talpa">
|
|
<i class="bi bi-cloud-upload"></i> Sync naar Talpa
|
|
</button>
|
|
</div>
|
|
<div>
|
|
<?= substr($blockStart, 0, 5) ?> - <?= substr($blockEnd, 0, 5) ?>
|
|
| Resterend: <strong><?= round($remainingMinutes) ?> min</strong>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
#### Stap 5: Initialize Tooltips (planner.php)
|
|
Voeg JavaScript toe om Bootstrap tooltips te activeren:
|
|
```javascript
|
|
<script>
|
|
// Initialize tooltips
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
|
});
|
|
});
|
|
|
|
// Existing functions...
|
|
</script>
|
|
```
|
|
|
|
#### Stap 6: Update na Sync Operatie
|
|
Zorg dat na een sync operatie de pagina wordt herladen om nieuwe status te tonen:
|
|
```javascript
|
|
// In syncBlockPlanner functie (reeds aanwezig)
|
|
if (data.success) {
|
|
// ... bestaande alert code ...
|
|
|
|
// Reload page to show updated status
|
|
window.location.reload();
|
|
}
|
|
```
|
|
|
|
## Mermaid Diagram: Sync Status Flow
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Transmission Aangemaakt] --> B{api_status?}
|
|
B -->|pending| C[Rood Bolletje]
|
|
B -->|synced| D{talpa_transmission_id?}
|
|
B -->|error| E[Oranje Waarschuwing]
|
|
|
|
D -->|NULL| C
|
|
D -->|Heeft waarde| F[Groen Bolletje]
|
|
|
|
G[Block Status Check] --> H{Alle transmissions?}
|
|
H -->|Geen| I[Grijs: Leeg]
|
|
H -->|Alle synced| J[Groen: Gepland]
|
|
H -->|Deels synced| K[Oranje: Deels Gepland]
|
|
H -->|Geen synced| L[Rood: Niet Gepland]
|
|
|
|
M[Sync Button Click] --> N[API Call]
|
|
N --> O{Success?}
|
|
O -->|Ja| P[Update Status]
|
|
O -->|Nee| Q[Toon Error]
|
|
P --> R[Reload Page]
|
|
R --> G
|
|
```
|
|
|
|
## Testing Checklist
|
|
|
|
### Visuele Tests
|
|
- [ ] Rood bolletje toont voor nieuwe transmissions (pending)
|
|
- [ ] Groen bolletje toont voor gesynchroniseerde transmissions
|
|
- [ ] Oranje waarschuwing toont voor error status
|
|
- [ ] Tooltips tonen correcte informatie bij hover
|
|
- [ ] Block badge toont "Leeg" voor lege blokken
|
|
- [ ] Block badge toont "Gepland" wanneer alle transmissions synced zijn
|
|
- [ ] Block badge toont "Deels Gepland" met correcte telling
|
|
- [ ] Block badge toont "Niet Gepland" wanneer geen transmissions synced zijn
|
|
|
|
### Functionele Tests
|
|
- [ ] Na sync operatie worden indicators bijgewerkt
|
|
- [ ] Indicators werken correct na drag-and-drop
|
|
- [ ] Indicators werken correct na reorder (up/down)
|
|
- [ ] Indicators werken correct na insert at position
|
|
- [ ] Tooltips werken op desktop
|
|
- [ ] Tooltips werken op mobile (touch)
|
|
|
|
### Edge Cases
|
|
- [ ] Block met 0 transmissions
|
|
- [ ] Block met 1 transmission (synced)
|
|
- [ ] Block met 1 transmission (pending)
|
|
- [ ] Block met mix van synced/pending/error
|
|
- [ ] Transmission met zeer lange talpa_transmission_id
|
|
- [ ] Transmission zonder talpa_transmission_id maar met status 'synced' (data inconsistentie)
|
|
|
|
## Performance Overwegingen
|
|
|
|
### Database Queries
|
|
- **Huidige situatie**: 1 query per block voor transmissions
|
|
- **Nieuwe situatie**: +1 query per block voor sync stats
|
|
- **Optimalisatie**: Combineer beide queries in één:
|
|
|
|
```php
|
|
$stmt = $db->prepare("
|
|
SELECT
|
|
t.*,
|
|
c.title,
|
|
c.color_code,
|
|
c.series_code,
|
|
COUNT(*) OVER() as block_total,
|
|
SUM(CASE WHEN t.api_status = 'synced' AND t.talpa_transmission_id IS NOT NULL THEN 1 ELSE 0 END) OVER() as block_synced
|
|
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
|
|
");
|
|
```
|
|
|
|
Dit gebruikt window functions om block stats te berekenen zonder extra query.
|
|
|
|
### Frontend Performance
|
|
- Tooltips worden lazy geïnitialiseerd (alleen bij hover)
|
|
- Geen extra JavaScript libraries nodig (Bootstrap reeds aanwezig)
|
|
- Minimale CSS overhead (< 1KB)
|
|
|
|
## Rollout Plan
|
|
|
|
### Fase 1: Development
|
|
1. Implementeer CSS changes
|
|
2. Implementeer transmission-level indicators
|
|
3. Test in development environment
|
|
|
|
### Fase 2: Block-Level Indicators
|
|
1. Implementeer block sync stats query
|
|
2. Implementeer block badge
|
|
3. Test met verschillende scenario's
|
|
|
|
### Fase 3: Testing
|
|
1. Uitvoeren van alle tests in checklist
|
|
2. Performance testing met grote blokken (>20 transmissions)
|
|
3. Cross-browser testing (Chrome, Firefox, Safari)
|
|
|
|
### Fase 4: Production
|
|
1. Deploy naar productie
|
|
2. Monitor voor errors
|
|
3. Verzamel gebruiker feedback
|
|
|
|
## Toekomstige Verbeteringen
|
|
|
|
### Fase 2 Features (Optioneel)
|
|
1. **Click-to-Sync**: Klik op rood bolletje om direct te syncen
|
|
2. **Batch Sync**: Selecteer meerdere transmissions en sync in één keer
|
|
3. **Auto-Refresh**: Automatisch status updaten zonder page reload (AJAX)
|
|
4. **Sync History**: Toon laatste sync tijd in tooltip
|
|
5. **Error Details Modal**: Klik op error indicator voor gedetailleerde foutmelding
|
|
|
|
### Technische Verbeteringen
|
|
1. **Caching**: Cache block sync stats voor betere performance
|
|
2. **WebSocket**: Real-time updates van sync status
|
|
3. **Background Sync**: Automatisch syncen op achtergrond
|
|
4. **Conflict Detection**: Waarschuw bij conflicten tussen lokaal en Talpa
|
|
|
|
## Conclusie
|
|
|
|
Deze implementatie voegt duidelijke visuele feedback toe aan de planner zonder de bestaande functionaliteit te verstoren. Gebruikers kunnen in één oogopslag zien welke transmissions gesynchroniseerd zijn en welke blokken volledig gepland zijn in Talpa.
|
|
|
|
De implementatie is:
|
|
- ✅ Minimaal invasief (kleine code changes)
|
|
- ✅ Performance-vriendelijk (minimale extra queries)
|
|
- ✅ Gebruiksvriendelijk (duidelijke visuele feedback)
|
|
- ✅ Uitbreidbaar (basis voor toekomstige features)
|