BROOKO icon
BROOKO UK NETWORK
Where code meets creativity & adventure
File viewer

calendar_admin.php

Type
php
Size
24.58 KB
Modified
15 May
calendar_admin.php 24.58 KB
<?php
require_once __DIR__ . '/../bootstrap.php';

requireAdmin();

$pageTitle = 'Calendar Admin';

$msg = [];
$err = [];

function isSystemCalendarCategory(array $cat): bool {
    $id = (int)($cat['id'] ?? 0);
    $name = strtolower(trim((string)($cat['name'] ?? '')));
    $systemIds = [10, 20, 30, 40, 50];
    $systemNames = ['jobs', 'meetings', 'incidents', 'annual leave', 'other'];
    return in_array($id, $systemIds, true) || in_array($name, $systemNames, true);
}

$selectedCategoryId = isset($_GET['category_id']) ? (int)$_GET['category_id'] : 0;
$selectedCategory = null;


// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
    // Save Category
    if (isset($_POST['action']) && $_POST['action'] === 'save_category') {
        $id = isset($_POST['id']) && $_POST['id'] ? (int)$_POST['id'] : null;
        $name = trim($_POST['name'] ?? '');
        $color = trim($_POST['color'] ?? '#3788d8');
        $sortOrder = isset($_POST['sort_order']) ? (int)$_POST['sort_order'] : 0;
        
        if (!$name) {
            $err[] = 'Category name is required';
        }
        
        if (empty($err)) {
            try {
                if ($id) {
                    $stmt = $pdo->prepare("UPDATE calendar_categories SET name=?, color=?, sort_order=? WHERE id=?");
                    $stmt->execute([$name, $color, $sortOrder, $id]);
                    $msg[] = 'Category updated successfully';
                } else {
                    $stmt = $pdo->prepare("INSERT INTO calendar_categories (name, color, sort_order) VALUES (?, ?, ?)");
                    $stmt->execute([$name, $color, $sortOrder]);
                    $msg[] = 'Category created successfully';
                }
                logActivity('calendar.categories.manage', 'calendar_category', $id ?? $pdo->lastInsertId(), "Saved category: $name");
            } catch (PDOException $e) {
                $err[] = 'Database error: ' . $e->getMessage();
            }
        }
    }
    
    // Delete Category
    if (isset($_POST['action']) && $_POST['action'] === 'delete_category') {
        $id = (int)$_POST['id'];
        try {
            $chk = $pdo->prepare("SELECT id, name FROM calendar_categories WHERE id=?");
            $chk->execute([$id]);
            $catRow = $chk->fetch(PDO::FETCH_ASSOC);

            if ($catRow && isSystemCalendarCategory($catRow)) {
                $err[] = 'System categories cannot be deleted.';
            } else {
                $stmt = $pdo->prepare("DELETE FROM calendar_categories WHERE id=?");
                $stmt->execute([$id]);
                $msg[] = 'Category deleted successfully';
                logActivity('calendar.categories.manage', 'calendar_category', $id, 'Deleted category');
            }
        } catch (PDOException $e) {
            $err[] = 'Failed to delete category: ' . $e->getMessage();
        }
    }
    
    // Save Custom Field
    if (isset($_POST['action']) && $_POST['action'] === 'save_field') {
        $id = isset($_POST['field_id']) && $_POST['field_id'] ? (int)$_POST['field_id'] : null;
        $categoryId = (int)$_POST['category_id'];
        $fieldName = trim($_POST['field_name'] ?? '');
        $fieldLabel = trim($_POST['field_label'] ?? '');
        $fieldType = trim($_POST['field_type'] ?? 'text');
        $fieldOptionsText = trim($_POST['field_options'] ?? '');
        $required = isset($_POST['required']) ? 1 : 0;
        $sortOrder = isset($_POST['field_sort_order']) ? (int)$_POST['field_sort_order'] : 0;

        // Guard: system categories should never have custom fields
        try {
            $chk = $pdo->prepare("SELECT id, name FROM calendar_categories WHERE id=?");
            $chk->execute([$categoryId]);
            $catRow = $chk->fetch(PDO::FETCH_ASSOC);
            if (!$catRow) {
                $err[] = 'Invalid category selected';
            } elseif (isSystemCalendarCategory($catRow)) {
                $err[] = 'Custom fields are only allowed on custom categories.';
            }
        } catch (Throwable $e) {
            $err[] = 'Could not validate category.';
        }
        
        if (!$fieldName || !$fieldLabel) {
            $err[] = 'Field name and label are required';
        }

        // Only allow types supported by the DB enum
        $allowedTypes = ['text','textarea','number','select','user'];
        if (!in_array($fieldType, $allowedTypes, true)) {
            $err[] = 'Invalid field type';
        }

        // Convert field options (newline separated) to JSON array for select fields
        $fieldOptionsJson = null;
        if ($fieldType === 'select') {
            $lines = array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $fieldOptionsText))));
            $fieldOptionsJson = $lines ? json_encode($lines) : json_encode([]);
        }
        
        if (empty($err)) {
            try {
                if ($id) {
                    $stmt = $pdo->prepare("UPDATE calendar_category_fields SET field_name=?, field_label=?, field_type=?, field_options=?, required=?, sort_order=? WHERE id=?");
                    $stmt->execute([$fieldName, $fieldLabel, $fieldType, $fieldOptionsJson, $required, $sortOrder, $id]);
                    $msg[] = 'Field updated successfully';
                } else {
                    $stmt = $pdo->prepare("INSERT INTO calendar_category_fields (category_id, field_name, field_label, field_type, field_options, required, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?)");
                    $stmt->execute([$categoryId, $fieldName, $fieldLabel, $fieldType, $fieldOptionsJson, $required, $sortOrder]);
                    $msg[] = 'Field created successfully';
                }
                logActivity('calendar.categories.fields', 'calendar_field', $id ?? $pdo->lastInsertId(), "Saved field: $fieldLabel");
            } catch (PDOException $e) {
                $err[] = 'Database error: ' . $e->getMessage();
            }
        }
    }
    
    // Delete Field
    if (isset($_POST['action']) && $_POST['action'] === 'delete_field') {
        $id = (int)$_POST['field_id'];
        try {
            $stmt = $pdo->prepare("DELETE FROM calendar_category_fields WHERE id=?");
            $stmt->execute([$id]);
            $msg[] = 'Field deleted successfully';
            logActivity('calendar.categories.fields', 'calendar_field', $id, 'Deleted field');
        } catch (PDOException $e) {
            $err[] = 'Failed to delete field: ' . $e->getMessage();
        }
    }
}

// Fetch categories
$categories = [];
try {
    $stmt = $pdo->query("SELECT * FROM calendar_categories ORDER BY sort_order ASC, name ASC");
    $categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Throwable $e) {
    error_log("Categories fetch error: " . $e->getMessage());
}



// Resolve selected category (only for custom categories)
if ($selectedCategoryId > 0) {
    foreach ($categories as $c) {
        if ((int)$c['id'] === $selectedCategoryId) {
            $selectedCategory = $c;
            break;
        }
    }
    if ($selectedCategory && isSystemCalendarCategory($selectedCategory)) {
        $selectedCategoryId = 0;
        $selectedCategory = null;
    }
}

// Custom categories (non-system)
$customCategories = array_values(array_filter($categories, fn($c) => !isSystemCalendarCategory($c)));

// Fetch fields (only for the selected custom category)
$fields = [];
if ($selectedCategoryId > 0 && $selectedCategory) {
    try {
        $stmt = $pdo->prepare("
            SELECT f.*, c.name as category_name
            FROM calendar_category_fields f
            JOIN calendar_categories c ON f.category_id = c.id
            WHERE f.category_id = ?
            ORDER BY f.sort_order ASC, f.id ASC
        ");
        $stmt->execute([$selectedCategoryId]);
        $fields = $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable $e) {
        error_log("Fields fetch error: " . $e->getMessage());
    }
}

// --- Calendar Settings (stored in system_info) ---
if (($_POST['action'] ?? '') === 'set_calendar_settings') {
    $newDefault = (int)($_POST['calendar_default_category_id'] ?? 10);
    if ($newDefault <= 0) { $newDefault = 10; }

    try {
        $stmt = $pdo->prepare(
            "INSERT INTO system_info (`key`,`value`) VALUES ('calendar_default_category_id', ?) "
            . "ON DUPLICATE KEY UPDATE `value`=VALUES(`value`)"
        );
        $stmt->execute([(string)$newDefault]);
        $alerts[] = ['type' => 'success', 'message' => 'Calendar settings saved successfully'];
    } catch (Throwable $e) {
        error_log("Calendar settings save error: " . $e->getMessage());
        $alerts[] = ['type' => 'error', 'message' => 'Failed to save calendar settings'];
    }
}

$calendarDefaultCategoryId = 10;
try {
    $stmt = $pdo->prepare("SELECT `value` FROM system_info WHERE `key`='calendar_default_category_id' LIMIT 1");
    $stmt->execute();
    $val = $stmt->fetchColumn();
    if ($val !== false && (int)$val > 0) { $calendarDefaultCategoryId = (int)$val; }
} catch (Throwable $e) {
    // ignore
}

include __DIR__ . '/../partials/header.php';
?>

<div class="content-header">
    <div>
        <h1 class="content-title">📅 Calendar Admin</h1>
        <p class="content-subtitle">Manage calendar categories and custom fields</p>
    </div>
    <button class="btn btn-primary" type="button" onclick="openCategoryModal()">
        ➕ Add Category
    </button>
</div>

<?php if ($msg): foreach ($msg as $m): ?>
    <div class="alert alert-success"><?= htmlspecialchars($m) ?></div>
<?php endforeach; endif; ?>

<?php if ($err): foreach ($err as $e): ?>
    <div class="alert alert-error"><?= htmlspecialchars($e) ?></div>
<?php endforeach; endif; ?>

<!-- Calendar Settings -->
<div class="card">
    <div class="card-header">
        <h3 class="card-title">Calendar Settings</h3>
    </div>
    <div class="card-body">
        <form method="post" class="form-inline" style="gap: 12px; flex-wrap: wrap;">
            <input type="hidden" name="action" value="set_calendar_settings">

            <div class="form-group">
                <label for="calendar_default_category_id" style="min-width: 180px;">Default category for new events</label>
                <select id="calendar_default_category_id" name="calendar_default_category_id" class="input" style="min-width: 260px;">
                    <?php foreach ($categories as $cat): ?>
                        <option value="<?= (int)$cat['id'] ?>" <?= ((int)$cat['id'] === (int)$calendarDefaultCategoryId) ? 'selected' : '' ?>>
                            <?= htmlspecialchars($cat['name']) ?> (<?= (int)$cat['id'] ?>)
                        </option>
                    <?php endforeach; ?>
                </select>
            </div>

            <button type="submit" class="btn btn-primary">Save Settings</button>
        </form>
        <p class="text-muted" style="margin-top: 10px;">These defaults are applied during install and can be changed any time.</p>
    </div>
</div>

<!-- Categories -->
<div class="card">
    <div class="card-header">
        <h3 class="card-title">Calendar Categories (<?= count($categories) ?>)</h3>
    </div>
    <div class="card-body">
        <?php if (empty($categories)): ?>
            <p class="text-muted">No categories found. Create one to get started.</p>
        <?php else: ?>
            <table class="table">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>Color</th>
                        <th>Sort Order</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($categories as $cat): ?>
                    <tr>
                        <td><?= (int)$cat['id'] ?></td>
                        <td><?= htmlspecialchars($cat['name']) ?></td>
                        <td>
                            <span style="display: inline-block; width: 20px; height: 20px; background: <?= htmlspecialchars($cat['color']) ?>; border-radius: 3px; border: 1px solid #444;"></span>
                            <?= htmlspecialchars($cat['color']) ?>
                        </td>
                        <td><?= (int)$cat['sort_order'] ?></td>
                        <td>
                            <div style="display: flex; gap: 6px; flex-wrap: wrap;">
                                <button class="btn btn-xs btn-secondary" onclick='editCategory(<?= json_encode($cat) ?>)'>Edit</button>
                                <?php if (!isSystemCalendarCategory($cat)): ?>
                                    <a class="btn btn-xs btn-primary" href="?category_id=<?= (int)$cat['id'] ?>#custom-fields">Fields</a>
                                    <form method="POST" style="margin: 0;" onsubmit="return confirm('Delete this category?');">
                                        <input type="hidden" name="action" value="delete_category">
                                        <input type="hidden" name="id" value="<?= $cat['id'] ?>">
                                        <button type="submit" class="btn btn-danger btn-xs">🗑</button>
                                    </form>
                                <?php endif; ?>
                            </div>
                        </td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        <?php endif; ?>
    </div>
</div>

<?php if ($selectedCategoryId > 0 && $selectedCategory): ?>
<!-- Custom Fields -->
<div class="card" id="custom-fields" style="margin-top: 24px;">
    <div class="card-header">
        <h3 class="card-title">Custom Fields — <?= htmlspecialchars($selectedCategory['name']) ?> (<?= count($fields) ?>)</h3>
        <button class="btn btn-primary" type="button" onclick="openFieldModal()">
            ➕ Add Field
        </button>
    </div>
    <div class="card-body">
        <?php if (empty($fields)): ?>
            <p class="text-muted">No custom fields found.</p>
        <?php else: ?>
            <table class="table">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Field Name</th>
                        <th>Field Label</th>
                        <th>Type</th>
                        <th>Required</th>
                        <th>Sort</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($fields as $field): ?>
                    <tr>
                        <td><?= (int)$field['id'] ?></td>
                        <td><?= htmlspecialchars($field['field_name']) ?></td>
                        <td><?= htmlspecialchars($field['field_label']) ?></td>
                        <td><?= htmlspecialchars($field['field_type']) ?></td>
                        <td><?= $field['required'] ? '✅' : '—' ?></td>
                        <td><?= (int)($field['sort_order'] ?? 0) ?></td>
                        <td>
                            <div style="display: flex; gap: 6px;">
                                <button class="btn btn-xs btn-secondary" onclick='editField(<?= json_encode($field) ?>)'>Edit</button>
                                <form method="POST" style="margin: 0;" onsubmit="return confirm('Delete this field?');">
                                    <input type="hidden" name="action" value="delete_field">
                                    <input type="hidden" name="field_id" value="<?= $field['id'] ?>">
                                    <button type="submit" class="btn btn-danger btn-xs">🗑</button>
                                </form>
                            </div>
                        </td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        <?php endif; ?>
    </div>
</div>


<?php else: ?>
<div class="card" id="custom-fields" style="margin-top: 24px;">
  <div class="card-header">
    <h3 class="card-title">Custom Fields</h3>
  </div>
  <div class="card-body">
    <p class="text-muted">Select a <strong>custom</strong> category and click <strong>Fields</strong> to manage its custom fields.</p>
  </div>
</div>
<?php endif; ?>

<!-- Category Modal -->
<div class="modal-overlay" id="categoryModal" style="display: none;">
    <div class="modal modal-medium">
        <div class="modal-header">
            <h3 class="modal-title" id="categoryModalTitle">Add Category</h3>
            <button class="modal-close" type="button" onclick="closeCategoryModal()">×</button>
        </div>
        <form method="POST" id="categoryForm">
            <input type="hidden" name="action" value="save_category">
            <input type="hidden" name="id" id="category_id">
            <div class="modal-body">
                <div class="form-group">
                    <label>Category Name *</label>
                    <input type="text" name="name" id="category_name" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>Color</label>
                    <input type="color" name="color" id="category_color" class="form-control" value="#3788d8">
                </div>
                <div class="form-group">
                    <label>Sort Order</label>
                    <input type="number" name="sort_order" id="category_sort_order" class="form-control" value="0">
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" onclick="closeCategoryModal()">Cancel</button>
                <button type="submit" class="btn btn-primary">Save</button>
            </div>
        </form>
    </div>
</div>

<!-- Field Modal -->
<div class="modal-overlay" id="fieldModal" style="display: none;">
    <div class="modal modal-medium">
        <div class="modal-header">
            <h3 class="modal-title" id="fieldModalTitle">Add Field</h3>
            <button class="modal-close" type="button" onclick="closeFieldModal()">×</button>
        </div>
        <form method="POST" id="fieldForm">
            <input type="hidden" name="action" value="save_field">
            <input type="hidden" name="field_id" id="field_id">
            <div class="modal-body">
                <div class="form-group">
                    <label>Category *</label>
                    <select name="category_id" id="field_category_id" class="form-control" required>
                        <option value="">Select Category</option>
                        <?php if (empty($customCategories)): ?>
                            <option value="" disabled>(No custom categories yet)</option>
                        <?php else: ?>
                            <?php foreach ($customCategories as $cat): ?>
                                <option value="<?= (int)$cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
                            <?php endforeach; ?>
                        <?php endif; ?>
                    </select>
                    <?php if (empty($customCategories)): ?>
                        <div class="text-muted" style="margin-top:6px;">Create a custom category first, then add fields to it.</div>
                    <?php endif; ?>
                </div>
                <div class="form-group">
                    <label>Field Name (internal) *</label>
                    <input type="text" name="field_name" id="field_name" class="form-control" required placeholder="e.g., customer_address">
                </div>
                <div class="form-group">
                    <label>Field Label (displayed) *</label>
                    <input type="text" name="field_label" id="field_label" class="form-control" required placeholder="e.g., Customer Address">
                </div>
                <div class="form-group">
                    <label>Field Type</label>
                    <select name="field_type" id="field_type" class="form-control">
                        <option value="text">Text</option>
                        <option value="textarea">Textarea</option>
                        <option value="number">Number</option>
                        <option value="select">Select</option>
                        <option value="user">User</option>
                    </select>
                </div>

                <div class="form-group" id="field_options_group" style="display:none;">
                    <label>Field Options (one per line)</label>
                    <textarea name="field_options" id="field_options" class="form-control" rows="4" placeholder="Option 1
Option 2"></textarea>
                    <div class="text-muted" style="margin-top:6px;">Only used for Select fields.</div>
                </div>
                <div class="form-group">
                    <label>
                        <input type="checkbox" name="required" id="field_required">
                        Required Field
                    </label>
                </div>
                <div class="form-group">
                    <label>Sort Order</label>
                    <input type="number" name="field_sort_order" id="field_sort_order" class="form-control" value="0">
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" onclick="closeFieldModal()">Cancel</button>
                <button type="submit" class="btn btn-primary">Save</button>
            </div>
        </form>
    </div>
</div>

<script>
function openCategoryModal() {
    document.getElementById('categoryModalTitle').textContent = 'Add Category';
    document.getElementById('categoryForm').reset();
    document.getElementById('category_id').value = '';
    document.getElementById('categoryModal').style.display = 'flex';
}

function editCategory(cat) {
    document.getElementById('categoryModalTitle').textContent = 'Edit Category';
    document.getElementById('category_id').value = cat.id;
    document.getElementById('category_name').value = cat.name;
    document.getElementById('category_color').value = cat.color;
    document.getElementById('category_sort_order').value = cat.sort_order;
    document.getElementById('categoryModal').style.display = 'flex';
}

function closeCategoryModal() {
    document.getElementById('categoryModal').style.display = 'none';
}

function openFieldModal() {
    const selectedCategoryId = <?= (int)$selectedCategoryId ?>;

    document.getElementById('fieldModalTitle').textContent = 'Add Field';
    document.getElementById('fieldForm').reset();
    document.getElementById('field_id').value = '';
    document.getElementById('field_options').value = '';
    // If a category is selected (custom only), preselect it
    if (selectedCategoryId > 0) {
        const catSel = document.getElementById('field_category_id');
        if (catSel) {
            catSel.value = selectedCategoryId;
        }
    }
    updateFieldOptionsVisibility();
    document.getElementById('fieldModal').style.display = 'flex';
}

function editField(field) {
    document.getElementById('fieldModalTitle').textContent = 'Edit Field';
    document.getElementById('field_id').value = field.id;
    document.getElementById('field_category_id').value = field.category_id;
    document.getElementById('field_name').value = field.field_name;
    document.getElementById('field_label').value = field.field_label;
    document.getElementById('field_type').value = field.field_type;
    document.getElementById('field_required').checked = field.required == 1;
    document.getElementById('field_sort_order').value = field.sort_order;

    // Populate select options (stored as JSON array)
    let optsText = '';
    if (field.field_options) {
        try {
            const parsed = (typeof field.field_options === 'string') ? JSON.parse(field.field_options) : field.field_options;
            if (Array.isArray(parsed)) {
                optsText = parsed.join("\n");
            }
        } catch (e) {
            // ignore malformed JSON
        }
    }
    document.getElementById('field_options').value = optsText;
    updateFieldOptionsVisibility();
    document.getElementById('fieldModal').style.display = 'flex';
}

function updateFieldOptionsVisibility() {
    const typeSel = document.getElementById('field_type');
    const group = document.getElementById('field_options_group');
    if (!typeSel || !group) return;
    group.style.display = (typeSel.value === 'select') ? 'block' : 'none';
}

// Wire up onchange
document.addEventListener('DOMContentLoaded', function () {
    const typeSel = document.getElementById('field_type');
    if (typeSel) {
        typeSel.addEventListener('change', updateFieldOptionsVisibility);
        updateFieldOptionsVisibility();
    }
});

function closeFieldModal() {
    document.getElementById('fieldModal').style.display = 'none';
}
</script>

<?php include __DIR__ . '/../partials/footer.php'; ?>