<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/includes/crud_config.php';
require_login();
$moduleKey = $_GET['module'] ?? '';
$module = crud_module($moduleKey);
if (!$module) {
http_response_code(404);
die('Unknown module.');
}
$id = isset($_GET['id']) ? (int) $_GET['id'] : null;
$isEdit = $id !== null;
$pageTitle = ($isEdit ? 'Edit ' : 'Add ') . $module['singular'];
$row = [];
if ($isEdit) {
$stmt = db()->prepare("SELECT * FROM {$module['table']} WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch() ?: [];
if (!$row) { http_response_code(404); die('Record not found.'); }
}
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!csrf_verify()) {
$errors[] = 'Session expired, please try again.';
} else {
$data = [];
foreach ($module['fields'] as $fkey => $f) {
$type = $f['type'];
if ($type === 'image') {
$data[$fkey] = handle_image_upload($fkey, $f['upload_dir'] ?? 'general', $row[$fkey] ?? null);
} elseif ($type === 'number') {
$data[$fkey] = (int) ($_POST[$fkey] ?? $f['default'] ?? 0);
} else {
$data[$fkey] = trim($_POST[$fkey] ?? '');
if ($data[$fkey] === '' && isset($f['default'])) $data[$fkey] = $f['default'];
}
if (!empty($f['required']) && $data[$fkey] === '') {
$errors[] = $f['label'] . ' is required.';
}
}
// auto-slug for fields marked auto_slug, based on the field flagged slug_source
foreach ($module['fields'] as $fkey => $f) {
if (!empty($f['auto_slug'])) {
$sourceKey = null;
foreach ($module['fields'] as $k2 => $f2) { if (!empty($f2['slug_source'])) $sourceKey = $k2; }
if ($sourceKey && empty($data[$fkey])) {
$data[$fkey] = slugify($data[$sourceKey]);
} elseif (!empty($data[$fkey])) {
$data[$fkey] = slugify($data[$fkey]);
}
}
}
if (!$errors) {
if ($isEdit) {
$set = implode(', ', array_map(fn($k) => "$k = :$k", array_keys($data)));
$stmt = db()->prepare("UPDATE {$module['table']} SET $set WHERE id = :id");
$data['id'] = $id;
$stmt->execute($data);
} else {
$cols = implode(', ', array_keys($data));
$placeholders = implode(', ', array_map(fn($k) => ":$k", array_keys($data)));
$stmt = db()->prepare("INSERT INTO {$module['table']} ($cols) VALUES ($placeholders)");
$stmt->execute($data);
}
flash_set('success', $module['singular'] . ' saved successfully.');
redirect('crud.php?module=' . urlencode($moduleKey));
} else {
$row = array_merge($row, $data);
}
}
}
require __DIR__ . '/partials/header.php';
require __DIR__ . '/partials/sidebar.php';
require __DIR__ . '/partials/topbar.php';
?>
<?php if ($errors): ?>
<div class="mb-6 px-4 py-3 rounded-xl bg-red-50 text-brand-red text-sm font-medium border border-red-200">
<ul class="list-disc list-inside"><?php foreach ($errors as $err): ?><li><?= e($err) ?></li><?php endforeach; ?></ul>
</div>
<?php endif; ?>
<div class="max-w-3xl bg-white rounded-2xl shadow-sm p-6">
<form method="post" enctype="multipart/form-data" class="space-y-5">
<?= csrf_field() ?>
<?php foreach ($module['fields'] as $fkey => $f):
$value = $row[$fkey] ?? ($f['default'] ?? '');
?>
<div>
<label class="block text-sm font-medium mb-1.5"><?= e($f['label']) ?><?= !empty($f['required']) ? ' <span class="text-brand-red">*</span>' : '' ?></label>
<?php if ($f['type'] === 'textarea'): ?>
<textarea name="<?= e($fkey) ?>" rows="4" class="w-full rounded-xl border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-green"><?= e($value) ?></textarea>
<?php elseif ($f['type'] === 'select'): ?>
<select name="<?= e($fkey) ?>" class="w-full rounded-xl border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-green">
<?php foreach ($f['options'] as $optVal => $optLabel): ?>
<option value="<?= e($optVal) ?>" <?= (string) $value === (string) $optVal ? 'selected' : '' ?>><?= e($optLabel) ?></option>
<?php endforeach; ?>
</select>
<?php elseif ($f['type'] === 'select_dynamic'):
$opts = db()->query($f['options_query'])->fetchAll(); ?>
<select name="<?= e($fkey) ?>" class="w-full rounded-xl border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-green">
<?php foreach ($opts as $o): $optId = $o['id']; $optLabel = $o['name'] ?? reset($o); ?>
<option value="<?= e($optId) ?>" <?= (string) $value === (string) $optId ? 'selected' : '' ?>><?= e($optLabel) ?></option>
<?php endforeach; ?>
</select>
<?php elseif ($f['type'] === 'image'): ?>
<?php if (!empty($value)): ?>
<img src="<?= e(resolve_image($value)) ?>" class="h-20 rounded-lg mb-2 border border-gray-200">
<?php endif; ?>
<input type="file" name="<?= e($fkey) ?>" accept="image/*" class="w-full text-sm">
<p class="text-xs text-gray-400 mt-1">Leave empty to keep the current image. Upload a file to replace it.</p>
<?php elseif ($f['type'] === 'number'): ?>
<input type="number" name="<?= e($fkey) ?>" value="<?= e((string) $value) ?>" class="w-full rounded-xl border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-green">
<?php else: ?>
<input type="text" name="<?= e($fkey) ?>" value="<?= e((string) $value) ?>" class="w-full rounded-xl border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-brand-green">
<?php endif; ?>
</div>
<?php endforeach; ?>
<div class="flex items-center gap-3 pt-2">
<button type="submit" class="bg-gradient-to-r from-brand-red to-brand-green text-white font-semibold px-6 py-3 rounded-xl hover:brightness-110 transition">
<i class="fa-solid fa-floppy-disk mr-2"></i>Save <?= e($module['singular']) ?>
</button>
<a href="crud.php?module=<?= urlencode($moduleKey) ?>" class="text-gray-500 text-sm font-medium hover:text-gray-700">Cancel</a>
</div>
</form>
</div>
<?php require __DIR__ . '/partials/footer.php'; ?>