File: /home/muratemr/theotto.tr/admin/reservations.php
<?php
/**
* admin/reservations.php
*/
declare(strict_types=1);
$page_title = 'Rezervasyonlar';
$active_menu = 'reservations';
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/csrf.php';
require_permission('reservations');
require_once __DIR__ . '/../includes/admin_layout.php';
// Geçmiş onaylı rezervasyonları otomatik arşivle
db_run("
UPDATE reservations
SET status = 'arsiv'
WHERE status = 'onaylandi'
AND date < CURDATE()
");
$active_tab = htmlspecialchars($_GET['tab'] ?? 'bekliyor', ENT_QUOTES);
$allowed_tabs = ['bekliyor','onaylandi','reddedildi','iptal','arsiv'];
if (!in_array($active_tab, $allowed_tabs)) $active_tab = 'bekliyor';
$filter_date = get_str('date');
$filter_phone = get_str('phone');
$filter_name = get_str('name');
$page = max(1, get_int('page', 1));
$per_page = 20;
function build_res_query(string $status, string $date, string $phone, string $name): array
{
$where = ['r.status = ?'];
$params = [$status];
if ($date !== '') {
$where[] = 'r.date = ?';
$params[] = $date;
}
if ($phone !== '') {
$where[] = 'r.phone LIKE ?';
$params[] = '%' . $phone . '%';
}
if ($name !== '') {
$where[] = 'r.full_name LIKE ?';
$params[] = '%' . $name . '%';
}
return ['where' => implode(' AND ', $where), 'params' => $params];
}
$qb = build_res_query($active_tab, $filter_date, $filter_phone, $filter_name);
$total = (int)db_value("SELECT COUNT(*) FROM reservations r WHERE {$qb['where']}", $qb['params']);
$pg = paginate($total, $page, $per_page);
$reservations = db_rows("
SELECT r.*, c.tag_id
FROM reservations r
LEFT JOIN customers c ON c.id = r.customer_id
WHERE {$qb['where']}
ORDER BY r.date " . ($active_tab === 'bekliyor' ? 'ASC' : 'DESC') . ", r.time ASC
LIMIT {$pg['limit']} OFFSET {$pg['offset']}
", $qb['params']);
$tab_counts = [];
foreach ($allowed_tabs as $t) {
$tab_counts[$t] = (int)db_value("SELECT COUNT(*) FROM reservations WHERE status=?", [$t]);
}
?>
<!-- Otomatik Yenileme + Bildirim Bar -->
<div class="d-flex align-items-center justify-content-between mb-3">
<div class="d-flex align-items-center gap-2">
<select id="resRefreshSelect" class="form-select form-select-sm" style="width:auto;background:#1a1a1a;color:var(--text-primary);border-color:var(--dark-border)">
<option value="0">Otomatik yenileme kapalı</option>
<option value="15">15 saniye</option>
<option value="30" selected>30 saniye</option>
<option value="60">1 dakika</option>
<option value="120">2 dakika</option>
</select>
<span class="refresh-badge" id="resRefreshBadge" onclick="location.reload()" title="Şimdi yenile" style="cursor:pointer">
<i class="bi bi-arrow-clockwise me-1"></i><span id="resRefreshCountdown">30s</span>
</span>
<span class="badge bg-secondary" id="newResBadge" style="display:none">
<i class="bi bi-bell-fill me-1"></i><span id="newResBadgeCount">0</span> yeni
</span>
</div>
</div>
<!-- Yeni rezervasyon bildirim modalı -->
<div class="notification-modal-overlay d-none" id="resNewModal">
<div class="notification-modal-box alarm-box">
<div class="alarm-bell-wrap mb-3">
<i class="bi bi-bell-fill alarm-bell-icon"></i>
</div>
<div class="alarm-title mb-1">YENİ REZERVASYON!</div>
<div class="alarm-subtitle mb-3">Onay bekliyor</div>
<div id="resNewContent" class="mb-4 text-start alarm-content"></div>
<button class="btn btn-gold px-5 py-2 fw-bold" id="resNewOkBtn" style="font-size:15px;min-width:200px">
<i class="bi bi-check-lg me-2"></i>TAMAM, GÖRDÜM
</button>
<div class="mt-3" style="font-size:11px;color:var(--text-muted)">
Bu butona basana kadar alarm çalmaya devam eder
</div>
</div>
</div>
<!-- Sekmeler -->
<ul class="nav nav-tabs mb-3" id="resTabs">
<?php
$tab_labels = ['bekliyor'=>'Bekliyor','onaylandi'=>'Onaylanan','reddedildi'=>'Reddedilen','iptal'=>'İptal','arsiv'=>'Arşiv'];
$tab_colors = ['bekliyor'=>'warning','onaylandi'=>'success','reddedildi'=>'danger','iptal'=>'secondary','arsiv'=>'info'];
foreach ($allowed_tabs as $t):
$active_cls = $t === $active_tab ? ' active' : '';
?>
<li class="nav-item">
<a class="nav-link<?= $active_cls ?>" href="?tab=<?= $t ?>">
<?= $tab_labels[$t] ?>
<span class="badge bg-<?= $tab_colors[$t] ?> ms-1"><?= $tab_counts[$t] ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
<!-- Filtreler -->
<div class="card mb-3">
<div class="card-body py-2">
<form method="GET" class="row g-2 align-items-end">
<input type="hidden" name="tab" value="<?= e($active_tab) ?>">
<div class="col-12 col-md-3">
<label class="form-label">Tarih</label>
<input type="date" name="date" class="form-control form-control-sm" value="<?= e($filter_date) ?>">
</div>
<div class="col-12 col-md-3">
<label class="form-label">Telefon</label>
<input type="text" name="phone" class="form-control form-control-sm" value="<?= e($filter_phone) ?>" placeholder="Telefon ara...">
</div>
<div class="col-12 col-md-3">
<label class="form-label">Ad Soyad</label>
<input type="text" name="name" class="form-control form-control-sm" value="<?= e($filter_name) ?>" placeholder="İsim ara...">
</div>
<div class="col-12 col-md-3 d-flex gap-2">
<button type="submit" class="btn btn-sm btn-gold flex-fill">
<i class="bi bi-search me-1"></i>Filtrele
</button>
<a href="?tab=<?= e($active_tab) ?>" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-x-lg"></i>
</a>
</div>
</form>
</div>
</div>
<!-- Tablo -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span><?= $tab_labels[$active_tab] ?> (<?= $total ?>)</span>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Tarih</th>
<th>Saat</th>
<th>Ad Soyad</th>
<th>Telefon</th>
<th>Kişi</th>
<th>Masa</th>
<?php if (in_array($active_tab, ['onaylandi','arsiv'])): ?>
<th>Geldi mi?</th>
<?php endif; ?>
<th>Durum</th>
<th>İşlemler</th>
</tr>
</thead>
<tbody>
<?php foreach ($reservations as $r): ?>
<tr>
<td><?= format_date($r['date']) ?></td>
<td><?= format_time($r['time']) ?></td>
<td>
<?= e($r['full_name']) ?>
<?php if ($r['special_request']): ?>
<i class="bi bi-chat-dots text-muted ms-1 fs-12" data-bs-toggle="tooltip" title="<?= e($r['special_request']) ?>"></i>
<?php endif; ?>
</td>
<td>
<?= e($r['phone']) ?>
<a href="#" onclick="openWhatsApp('<?= e($r['phone']) ?>','')" class="ms-1" title="WhatsApp">
<i class="bi bi-whatsapp text-success fs-12"></i>
</a>
</td>
<td><?= $r['guest_count'] ?></td>
<td><?= $r['table_no'] ? e($r['table_no']) : '<span class="text-muted">—</span>' ?></td>
<?php if (in_array($active_tab, ['onaylandi','arsiv'])): ?>
<td>
<?php if ($r['visit_status'] === 'geldi'): ?>
<span class="badge bg-success">Geldi</span>
<?php elseif ($r['visit_status'] === 'gelmedi'): ?>
<span class="badge bg-danger">Gelmedi</span>
<?php else: ?>
<span class="badge bg-secondary">Belirsiz</span>
<?php endif; ?>
</td>
<?php endif; ?>
<td><?= reservation_status_badge($r['status']) ?></td>
<td>
<div class="d-flex flex-wrap gap-1">
<?php if ($r['status'] === 'bekliyor'): ?>
<button class="btn btn-sm btn-success btn-action" onclick="openConfirmModal(<?= $r['id'] ?>, '<?= e($r['full_name']) ?>', '<?= e($r['phone']) ?>')">
<i class="bi bi-check-lg"></i>
</button>
<button class="btn btn-sm btn-danger btn-action" onclick="openRejectModal(<?= $r['id'] ?>)">
<i class="bi bi-x-lg"></i>
</button>
<?php endif; ?>
<?php if (in_array($r['status'], ['bekliyor','onaylandi'])): ?>
<button class="btn btn-sm btn-secondary btn-action" onclick="openCancelModal(<?= $r['id'] ?>)">
<i class="bi bi-slash-circle"></i>
</button>
<?php endif; ?>
<?php if ($r['status'] === 'onaylandi'): ?>
<button class="btn btn-sm btn-info btn-action" onclick="archiveRes(<?= $r['id'] ?>)" title="Arşivle">
<i class="bi bi-archive"></i>
</button>
<?php endif; ?>
<?php if (in_array($r['status'], ['onaylandi','arsiv'])): ?>
<button class="btn btn-sm btn-outline-secondary btn-action" onclick="openVisitModal(<?= $r['id'] ?>, '<?= e($r['visit_status'] ?? '') ?>', '<?= e($r['visit_note'] ?? '') ?>')" title="Geldi/Gelmedi">
<i class="bi bi-person-check"></i>
</button>
<?php endif; ?>
<button class="btn btn-sm btn-outline-secondary btn-action" onclick="openEditModal(<?= $r['id'] ?>)" title="Düzenle">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-success btn-action"
onclick="resendWA('<?= e($r['phone']) ?>', '<?= e(addslashes($r['full_name'])) ?>', '<?= e($r['status']) ?>', '<?= e($r['table_no'] ?? '') ?>')"
title="WhatsApp Mesajı Tekrarla">
<i class="bi bi-whatsapp"></i>
</button>
<?php if (is_admin()): ?>
<button class="btn btn-sm btn-outline-danger btn-action" onclick="deleteRes(<?= $r['id'] ?>)" title="Sil">
<i class="bi bi-trash"></i>
</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($reservations)): ?>
<tr><td colspan="9" class="text-center text-muted py-4">Kayıt bulunamadı.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
<?php if ($pg['total_pages'] > 1): ?>
<div class="card-body border-top pt-3 pb-2">
<nav>
<ul class="pagination pagination-sm mb-0 justify-content-center">
<li class="page-item <?= $pg['current_page'] <= 1 ? 'disabled' : '' ?>">
<a class="page-link" href="?tab=<?= $active_tab ?>&page=<?= $pg['current_page']-1 ?>&date=<?= e($filter_date) ?>&phone=<?= e($filter_phone) ?>&name=<?= e($filter_name) ?>">«</a>
</li>
<?php for ($i = max(1,$pg['current_page']-2); $i <= min($pg['total_pages'],$pg['current_page']+2); $i++): ?>
<li class="page-item <?= $i === $pg['current_page'] ? 'active' : '' ?>">
<a class="page-link" href="?tab=<?= $active_tab ?>&page=<?= $i ?>&date=<?= e($filter_date) ?>&phone=<?= e($filter_phone) ?>&name=<?= e($filter_name) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<li class="page-item <?= $pg['current_page'] >= $pg['total_pages'] ? 'disabled' : '' ?>">
<a class="page-link" href="?tab=<?= $active_tab ?>&page=<?= $pg['current_page']+1 ?>&date=<?= e($filter_date) ?>&phone=<?= e($filter_phone) ?>&name=<?= e($filter_name) ?>">»</a>
</li>
</ul>
</nav>
</div>
<?php endif; ?>
</div>
<!-- ===== MODALLER ===== -->
<!-- Onayla Modalı -->
<div class="modal fade" id="confirmModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title"><i class="bi bi-check-circle-fill text-success me-2"></i>Rezervasyonu Onayla</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="mb-3">Rezervasyonu onaylamak için masa numarasını giriniz.</p>
<div class="mb-3">
<label class="form-label">Masa No (opsiyonel)</label>
<input type="text" id="confirmTableNo" class="form-control" placeholder="Örn: 5, A3...">
</div>
<div class="mb-2">
<label class="form-label">Admin Notu (opsiyonel)</label>
<textarea id="confirmAdminNote" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button type="button" class="btn btn-success" id="confirmSubmitBtn">
<i class="bi bi-check-lg me-1"></i>Onayla
</button>
</div>
</div>
</div>
</div>
<!-- Reddet Modalı -->
<div class="modal fade" id="rejectModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title"><i class="bi bi-x-circle-fill text-danger me-2"></i>Rezervasyonu Reddet</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<label class="form-label">Red Sebebi</label>
<textarea id="rejectReason" class="form-control" rows="3" placeholder="Müşteriye iletilecek red sebebi..."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button type="button" class="btn btn-danger" id="rejectSubmitBtn">
<i class="bi bi-x-lg me-1"></i>Reddet
</button>
</div>
</div>
</div>
</div>
<!-- İptal Modalı -->
<div class="modal fade" id="cancelModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title"><i class="bi bi-slash-circle text-secondary me-2"></i>Rezervasyonu İptal Et</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<label class="form-label">İptal Sebebi</label>
<textarea id="cancelReason" class="form-control" rows="3"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Vazgeç</button>
<button type="button" class="btn btn-secondary" id="cancelSubmitBtn">İptal Et</button>
</div>
</div>
</div>
</div>
<!-- Geldi/Gelmedi Modalı -->
<div class="modal fade" id="visitModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title"><i class="bi bi-person-check me-2"></i>Ziyaret Durumu</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Durum</label>
<select id="visitStatus" class="form-select">
<option value="">Belirsiz</option>
<option value="geldi">Geldi</option>
<option value="gelmedi">Gelmedi</option>
</select>
</div>
<div class="mb-2">
<label class="form-label">Ziyaret Notu</label>
<textarea id="visitNote" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button type="button" class="btn btn-gold" id="visitSubmitBtn">Kaydet</button>
</div>
</div>
</div>
</div>
<!-- Düzenle Modalı -->
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title"><i class="bi bi-pencil me-2"></i>Rezervasyon Düzenle</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="editModalBody">
<div class="text-center py-4"><div class="spinner-border spinner-border-sm text-secondary"></div></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button type="button" class="btn btn-gold" id="editSubmitBtn">Kaydet</button>
</div>
</div>
</div>
</div>
<script>
// Mesaj şablonları (DB'den PHP tarafından aktarılıyor)
const WA_MSGS = <?php
$rest_name = get_setting('restaurant_name', 'Restoran');
$wa_data = [
'restaurant_name' => $rest_name,
'res_confirm' => get_setting('msg_res_confirm',
"Sayın {isim},
Rezervasyonunuz onaylanmıştır. ✅
Masa No: {masa_no}
Sizi bekliyoruz! 🌟
{restoran}"
),
'res_reject' => get_setting('msg_res_reject',
"Sayın {isim},
Üzgünüz, rezervasyon talebiniz onaylanamadı. ❌
Başka bir tarih için rezervasyon yapabilirsiniz.
{restoran}"
),
'res_cancel' => get_setting('msg_res_cancel',
"Sayın {isim},
Rezervasyonunuz iptal edilmiştir.
Başka bir tarih için rezervasyon yapabilirsiniz.
{restoran}"
),
];
echo json_encode($wa_data, JSON_UNESCAPED_UNICODE);
?>;
let currentResId = null;
let currentResPhone = '';
let currentResName = '';
// ---- Onayla ----
function openConfirmModal(id, name, phone) {
currentResId = id;
currentResName = name;
currentResPhone = phone;
document.getElementById('confirmTableNo').value = '';
document.getElementById('confirmAdminNote').value = '';
openModal('confirmModal');
}
document.getElementById('confirmSubmitBtn').addEventListener('click', async () => {
const res = await apiPost('/api/admin/reservations.php', {
action: 'confirm',
id: currentResId,
table_no: document.getElementById('confirmTableNo').value,
admin_note: document.getElementById('confirmAdminNote').value,
});
if (res.success) {
closeModal('confirmModal');
showToast('Rezervasyon onaylandı!', 'success');
// Modal kapandıktan sonra WhatsApp aç (popup blocker engellemesi önlenir)
const tableNo = document.getElementById('confirmTableNo').value || '-';
const msg = buildResMsg(WA_MSGS['res_confirm'] || '', {
'{isim}' : currentResName,
'{masa_no}' : tableNo,
'{restoran}': WA_MSGS['restaurant_name'] || '',
});
setTimeout(() => {
openWhatsApp(currentResPhone, msg);
setTimeout(() => location.reload(), 800);
}, 400);
} else {
showToast(res.message, 'error');
}
});
// ---- Reddet ----
function openRejectModal(id) {
currentResId = id;
document.getElementById('rejectReason').value = '';
openModal('rejectModal');
}
document.getElementById('rejectSubmitBtn').addEventListener('click', async () => {
const reason = document.getElementById('rejectReason').value.trim();
const res = await apiPost('/api/admin/reservations.php', {
action: 'reject',
id: currentResId,
cancel_reason: reason,
});
if (res.success) {
closeModal('rejectModal');
showToast('Rezervasyon reddedildi.', 'success');
if (currentResPhone) {
const msg = buildResMsg(WA_MSGS['res_reject'] || '', {
'{isim}' : currentResName,
'{restoran}': WA_MSGS['restaurant_name'] || '',
}) + (reason ? '\n\nSebep: ' + reason : '');
setTimeout(() => {
openWhatsApp(currentResPhone, msg);
setTimeout(() => location.reload(), 800);
}, 400);
} else {
setTimeout(() => location.reload(), 800);
}
} else {
showToast(res.message, 'error');
}
});
// ---- İptal ----
function openCancelModal(id) {
currentResId = id;
document.getElementById('cancelReason').value = '';
openModal('cancelModal');
}
document.getElementById('cancelSubmitBtn').addEventListener('click', async () => {
const res = await apiPost('/api/admin/reservations.php', {
action: 'cancel',
id: currentResId,
cancel_reason: document.getElementById('cancelReason').value,
});
if (res.success) {
closeModal('cancelModal');
showToast('Rezervasyon iptal edildi.', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showToast(res.message, 'error');
}
});
// ---- Arşivle ----
async function archiveRes(id) {
if (!confirm('Bu rezervasyonu arşivlemek istiyor musunuz?')) return;
const res = await apiPost('/api/admin/reservations.php', { action: 'archive', id });
if (res.success) {
showToast('Arşivlendi.', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showToast(res.message, 'error');
}
}
// ---- Geldi/Gelmedi ----
function openVisitModal(id, status, note) {
currentResId = id;
document.getElementById('visitStatus').value = status || '';
document.getElementById('visitNote').value = note || '';
openModal('visitModal');
}
document.getElementById('visitSubmitBtn').addEventListener('click', async () => {
const res = await apiPost('/api/admin/reservations.php', {
action: 'visit',
id: currentResId,
visit_status: document.getElementById('visitStatus').value,
visit_note: document.getElementById('visitNote').value,
});
if (res.success) {
closeModal('visitModal');
showToast('Ziyaret durumu güncellendi.', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showToast(res.message, 'error');
}
});
// ---- Düzenle ----
async function openEditModal(id) {
currentResId = id;
document.getElementById('editModalBody').innerHTML = '<div class="text-center py-4"><div class="spinner-border spinner-border-sm text-secondary"></div></div>';
openModal('editModal');
const data = await apiGet('/api/admin/reservations.php?action=get&id=' + id);
if (!data.success) { showToast(data.message, 'error'); return; }
const r = data.reservation;
document.getElementById('editModalBody').innerHTML = `
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Ad Soyad</label>
<input type="text" id="editName" class="form-control" value="${r.full_name}">
</div>
<div class="col-md-6">
<label class="form-label">Telefon</label>
<input type="text" id="editPhone" class="form-control" value="${r.phone}">
</div>
<div class="col-md-4">
<label class="form-label">Kişi Sayısı</label>
<input type="number" id="editGuests" class="form-control" value="${r.guest_count}" min="1" max="99">
</div>
<div class="col-md-4">
<label class="form-label">Tarih</label>
<input type="date" id="editDate" class="form-control" value="${r.date}">
</div>
<div class="col-md-4">
<label class="form-label">Saat</label>
<input type="time" id="editTime" class="form-control" value="${r.time.substring(0,5)}">
</div>
<div class="col-md-6">
<label class="form-label">Masa No</label>
<input type="text" id="editTableNo" class="form-control" value="${r.table_no || ''}">
</div>
<div class="col-md-6">
<label class="form-label">Özel İstek</label>
<input type="text" id="editSpecial" class="form-control" value="${r.special_request || ''}">
</div>
<div class="col-12">
<label class="form-label">Admin Notu</label>
<textarea id="editAdminNote" class="form-control" rows="2">${r.admin_note || ''}</textarea>
</div>
</div>
`;
}
document.getElementById('editSubmitBtn').addEventListener('click', async () => {
const res = await apiPost('/api/admin/reservations.php', {
action: 'update',
id: currentResId,
full_name: document.getElementById('editName').value,
phone: document.getElementById('editPhone').value,
guest_count: document.getElementById('editGuests').value,
date: document.getElementById('editDate').value,
time: document.getElementById('editTime').value,
table_no: document.getElementById('editTableNo').value,
special_request: document.getElementById('editSpecial').value,
admin_note: document.getElementById('editAdminNote').value,
});
if (res.success) {
closeModal('editModal');
showToast('Rezervasyon güncellendi.', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showToast(res.message, 'error');
}
});
// ---- Sil ----
function buildResMsg(template, vars) {
// Değişkenleri değiştir
let msg = template;
for (const [k, v] of Object.entries(vars)) {
msg = msg.split(k).join(v || '');
}
// Emoji shortcode'ları çevir
msg = msg
.replace(/✅/g, String.fromCodePoint(0x2705))
.replace(/❌/g, String.fromCodePoint(0x274C))
.replace(/🌟/g, String.fromCodePoint(0x1F31F))
.replace(/🎂/g, String.fromCodePoint(0x1F382))
.replace(/🎁/g, String.fromCodePoint(0x1F381))
.replace(/✨/g, String.fromCodePoint(0x2728));
return msg;
}
function resendWA(phone, name, status, tableNo) {
let tplKey = '';
if (status === 'onaylandi' || status === 'arsiv') tplKey = 'confirm';
else if (status === 'reddedildi') tplKey = 'reject';
else if (status === 'iptal') tplKey = 'cancel';
const tpl = tplKey ? (WA_MSGS['res_' + tplKey] || '') : '';
const msg = buildResMsg(tpl, {
'{isim}' : name,
'{masa_no}' : tableNo || '-',
'{restoran}': WA_MSGS['restaurant_name'] || '',
});
// Mesajı önizleme modalında göster, kullanıcı onaylasın
openResWAPreview(phone, name, status, msg);
}
function openResWAPreview(phone, name, status, msg) {
const statusLabels = {
'onaylandi' : 'Onay Mesajı',
'arsiv' : 'Onay Mesajı',
'reddedildi': 'Red Mesajı',
'iptal' : 'İptal Mesajı',
'bekliyor' : 'Mesaj',
};
document.getElementById('resWAPreviewTitle').textContent =
(statusLabels[status] || 'WhatsApp Mesajı') + ' — ' + name;
document.getElementById('resWAPreviewBody').textContent = msg || '(Bu durum için mesaj şablonu ayarlanmamış)';
document.getElementById('resWASendBtn').onclick = () => {
if (msg) openWhatsApp(phone, msg);
closeModal('resWAPreviewModal');
};
document.getElementById('resWASendBtn').disabled = !msg;
openModal('resWAPreviewModal');
}
async function deleteRes(id) {
if (!confirm('Bu rezervasyonu kalıcı olarak silmek istiyor musunuz?')) return;
const res = await apiPost('/api/admin/reservations.php', { action: 'delete', id });
if (res.success) {
showToast('Silindi.', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showToast(res.message, 'error');
}
}
// ================================================================
// OTOMATİK YENİLEME + YENİ RES BİLDİRİMİ (reservations.php)
// ================================================================
let resRefreshSeconds = 30;
let resRefreshTimer = null;
let resCountdownVal = 30;
let resAudioCtx = null;
let resAudioInterval = null;
// Son görülen ID - dashboard ile aynı sessionStorage key'i paylaşıyor
const _RES_DB_MAX = <?php echo (int)(db_value('SELECT COALESCE(MAX(id),0) FROM reservations') ?: 0); ?>;
(function() {
const stored = parseInt(sessionStorage.getItem('otto_last_res_id') || '0');
if (stored > 0 && stored <= _RES_DB_MAX) {
window.resLastKnownId = stored;
} else {
window.resLastKnownId = _RES_DB_MAX;
sessionStorage.setItem('otto_last_res_id', _RES_DB_MAX);
}
})();
let resLastKnownId = window.resLastKnownId;
// AudioContext - her etkileşimde unlock
function getResAudioCtx() {
if (!resAudioCtx) {
try { resAudioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
catch(e) { return null; }
}
return resAudioCtx;
}
['click','keydown','touchstart','touchend'].forEach(ev => {
document.addEventListener(ev, function() {
const ctx = getResAudioCtx();
if (ctx && ctx.state === 'suspended') ctx.resume();
}, { passive: true });
});
function resPlayAlarm() {
const ctx = getResAudioCtx();
if (!ctx) return;
const go = () => {
const t = ctx.currentTime;
[440, 480].forEach(freq => {
[[t+0.05,t+0.55],[t+1.40,t+1.90]].forEach(pair => {
pair.forEach(st => {
try {
const osc = ctx.createOscillator(), g = ctx.createGain();
osc.connect(g); g.connect(ctx.destination);
osc.type = 'sine'; osc.frequency.value = freq;
g.gain.setValueAtTime(0, st);
g.gain.linearRampToValueAtTime(0.6, st+0.03);
g.gain.setValueAtTime(0.6, st+0.37);
g.gain.linearRampToValueAtTime(0, st+0.42);
osc.start(st); osc.stop(st+0.45);
} catch(e) {}
});
});
});
};
if (ctx.state === 'suspended') { ctx.resume().then(go); } else { go(); }
}
// Yeni rezervasyon kontrolü
async function resCheckNew() {
try {
const url = '/api/admin/check_new_reservations.php?last_id=' + resLastKnownId + '&_=' + Date.now();
const res = await fetch(url, {
headers: { 'X-CSRF-Token': CSRF_TOKEN, 'X-Requested-With': 'XMLHttpRequest' },
cache: 'no-store'
});
if (!res.ok) return;
let data;
try { data = await res.json(); } catch(e) { return; }
if (!data.success) return;
// max_id her zaman güncelle
if (data.max_id > resLastKnownId) {
resLastKnownId = data.max_id;
sessionStorage.setItem('otto_last_res_id', resLastKnownId);
}
if (data.new_count > 0) {
const r = data.reservations[0];
document.getElementById('resNewContent').innerHTML =
`<b>Ad Soyad:</b> ${r.full_name}<br>
<b>Telefon:</b> ${r.phone}<br>
<b>Tarih:</b> ${r.date}<br>
<b>Saat:</b> ${r.time}<br>
<b>Kişi Sayısı:</b> ${r.guest_count}` +
(data.new_count > 1 ? `<br><span class="text-warning">+${data.new_count-1} rezervasyon daha!</span>` : '');
document.getElementById('resNewModal').classList.remove('d-none');
// Rozet
const badge = document.getElementById('newResBadge');
const cnt = document.getElementById('newResBadgeCount');
cnt.textContent = (parseInt(cnt.textContent) || 0) + data.new_count;
badge.style.display = '';
// Alarm
if (!resAudioInterval) { resAudioInterval = setInterval(resPlayAlarm, 2800); }
resPlayAlarm();
}
} catch(e) { console.warn('resCheckNew hata:', e.message); }
}
document.getElementById('resNewOkBtn').addEventListener('click', () => {
document.getElementById('resNewModal').classList.add('d-none');
if (resAudioInterval) { clearInterval(resAudioInterval); resAudioInterval = null; }
});
// Otomatik yenileme (sayfa reload)
function resStartAutoRefresh(sec) {
if (resRefreshTimer) clearInterval(resRefreshTimer);
resCountdownVal = sec;
document.getElementById('resRefreshCountdown').textContent = sec + 's';
resRefreshTimer = setInterval(() => {
resCountdownVal--;
const el = document.getElementById('resRefreshCountdown');
if (el) el.textContent = resCountdownVal + 's';
if (resCountdownVal <= 0) {
// Modal açıksa sayfayı yenileme, sayacı sıfırla
const modalOpen = !document.getElementById('resNewModal').classList.contains('d-none');
if (modalOpen) {
resCountdownVal = sec;
} else {
location.reload();
}
}
}, 1000);
}
document.getElementById('resRefreshSelect').addEventListener('change', function() {
resRefreshSeconds = parseInt(this.value);
if (resRefreshTimer) clearInterval(resRefreshTimer);
if (resRefreshSeconds > 0) {
resStartAutoRefresh(resRefreshSeconds);
} else {
document.getElementById('resRefreshCountdown').textContent = '—';
}
});
// Sayfa açılınca hemen başlat
resStartAutoRefresh(resRefreshSeconds);
setInterval(resCheckNew, 15000);
resCheckNew();
</script>
<!-- WhatsApp Mesaj Önizleme Modalı -->
<div class="modal fade" id="resWAPreviewModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary">
<h6 class="modal-title" id="resWAPreviewTitle">
<i class="bi bi-whatsapp text-success me-2"></i>WhatsApp Mesajı
</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="resWAPreviewBody"
style="background:#0d1117;border:1px solid #2a2a2a;border-radius:8px;
padding:16px;font-size:14px;white-space:pre-wrap;line-height:1.7;
color:#e8e8e8;min-height:80px">
</div>
<div class="mt-2 d-flex align-items-center justify-content-between">
<span style="font-size:11px;color:var(--text-muted)">
<i class="bi bi-info-circle me-1"></i>
Mesaj şablonunu <a href="messages.php" style="color:var(--gold)">Mesaj Ayarları</a>'ndan düzenleyebilirsiniz.
</span>
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">İptal</button>
<button type="button" class="btn btn-success btn-sm" id="resWASendBtn">
<i class="bi bi-whatsapp me-1"></i>WhatsApp'ta Gönder
</button>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../includes/admin_layout_end.php'; ?>