1<?php
2/**
3 * Ticket Quick Reply + Admin Panel — theservice4u.com
4 * Single file, protected by secret key in URL.
5 * With tid= opens on Reply tab. Without tid= opens on Manage tab.
6 */
7
8define('ACTION_SECRET_KEY', 'd416037efc27f4e0438396ccdf3fb25978c22a2f3dcb7eeb');
9define('ADMIN_USERNAME', 'nitrolivetv');
10define('ADMIN_ID', 2);
11
12$key = $_GET['key'] ?? $_POST['key'] ?? '';
13$ticketid = (int)($_GET['tid'] ?? 0);
14$replyid = (int)($_GET['rid'] ?? 0);
15$confirmed = isset($_GET['confirmed']);
16
17if (!hash_equals(ACTION_SECRET_KEY, $key)) {
18 die("Access Denied");
19}
20
21define('WHMCS', true);
22require __DIR__ . '/init.php';
23use WHMCS\Database\Capsule;
24
25// ── Ensure tables exist ───────────────────────────────────────────────────────
26if (!Capsule::schema()->hasTable('tbl_quick_replies')) {
27 Capsule::schema()->create('tbl_quick_replies', function ($table) {
28 $table->increments('id');
29 $table->string('label');
30 $table->string('color')->nullable();
31 $table->text('reply_text');
32 $table->integer('sort_order')->default(0);
33 $table->boolean('active')->default(1);
34 $table->timestamps();
35 });
36
37 $seed = [
38 ['label' => 'Adult Pin', 'color' => '#1971c2', 'sort_order' => 1,
39 'reply_text' => "The default pin for most apps is 0000. If you set your own pin, you would need to reset the app. We don't store the pin. It is saved to the app itself."],
40 ['label' => 'Change Plan', 'color' => '#0088cc', 'sort_order' => 2,
41 'reply_text' => "If you would like to change your plan, select services from the menu then change plan. If changed before expiration, it will change based on the current expiration so you won't lose any days from your current expiration. Your username and password will be the same."],
42 ['label' => 'Device & App', 'color' => '#495057', 'sort_order' => 3,
43 'reply_text' => "1. What device are you using?\n2. What app are you using?\n3. Who is your internet provider?"],
44 ['label' => 'Trial Limit', 'color' => '#e67700', 'sort_order' => 4,
45 'reply_text' => "Trial plans do not contain VOD or premium sports channels. Trials are intended to test the quality of the service."],
46 ];
47 foreach ($seed as $row) {
48 Capsule::table('tbl_quick_replies')->insert(array_merge($row, [
49 'active' => 1,
50 'created_at' => date('Y-m-d H:i:s'),
51 'updated_at' => date('Y-m-d H:i:s'),
52 ]));
53 }
54}
55
56if (!Capsule::schema()->hasTable('tbl_email_inject')) {
57 Capsule::schema()->create('tbl_email_inject', function ($table) {
58 $table->increments('id');
59 $table->integer('userid');
60 $table->string('ticket_id');
61 $table->string('ticket_subject');
62 $table->string('ticket_status');
63 $table->string('ticket_url');
64 $table->timestamp('created_at');
65 });
66}
67
68$baseUrl = "?key={$key}";
69$ticketUrl = $ticketid ? "{$baseUrl}&tid={$ticketid}" : $baseUrl;
70$manageUrl = $baseUrl;
71$activeTab = $ticketid ? 'reply' : 'manage';
72
73$message = '';
74$error = '';
75$editing = null;
76
77// ── Handle admin POST actions ─────────────────────────────────────────────────
78$postAction = $_POST['action'] ?? '';
79
80if ($_SERVER['REQUEST_METHOD'] === 'POST') {
81 $activeTab = 'manage';
82
83 if ($postAction === 'add' || $postAction === 'edit') {
84 $label = trim($_POST['label'] ?? '');
85 $color = trim($_POST['color_hex'] ?? '#495057');
86 $replyText = trim($_POST['reply_text'] ?? '');
87 $sortOrder = (int)($_POST['sort_order'] ?? 0);
88 $active = isset($_POST['active']) ? 1 : 0;
89
90 if (!$label || !$replyText) {
91 $error = "Label and reply text are required.";
92 } else {
93 $data = [
94 'label' => $label,
95 'color' => $color ?: '#495057',
96 'reply_text' => $replyText,
97 'sort_order' => $sortOrder,
98 'active' => $active,
99 'updated_at' => date('Y-m-d H:i:s'),
100 ];
101 if ($postAction === 'edit') {
102 Capsule::table('tbl_quick_replies')->where('id', (int)$_POST['id'])->update($data);
103 $message = "Button updated.";
104 } else {
105 $data['created_at'] = date('Y-m-d H:i:s');
106 Capsule::table('tbl_quick_replies')->insert($data);
107 $message = "Button added.";
108 }
109 }
110 }
111
112 if ($postAction === 'delete') {
113 Capsule::table('tbl_quick_replies')->where('id', (int)$_POST['id'])->delete();
114 $message = "Button deleted.";
115 }
116
117 if ($postAction === 'toggle') {
118 $row = Capsule::table('tbl_quick_replies')->where('id', (int)$_POST['id'])->first();
119 if ($row) {
120 Capsule::table('tbl_quick_replies')->where('id', $row->id)->update([
121 'active' => $row->active ? 0 : 1,
122 'updated_at' => date('Y-m-d H:i:s'),
123 ]);
124 $message = "Button " . ($row->active ? "disabled." : "enabled.");
125 }
126 }
127}
128
129// Load edit target
130if (isset($_GET['edit']) && !$error) {
131 $editing = Capsule::table('tbl_quick_replies')->where('id', (int)$_GET['edit'])->first();
132 $activeTab = 'manage';
133}
134
135// Load ticket if we have one
136$ticket = null;
137if ($ticketid) {
138 $ticket = Capsule::table('tbltickets')->where('id', $ticketid)->first();
139}
140$admin = Capsule::table('tbladmins')->where('id', ADMIN_ID)->first();
141$buttons = Capsule::table('tbl_quick_replies')->orderBy('sort_order')->orderBy('id')->get();
142$allButtons = $buttons->all();
143
144// ── STEP 3: Confirmed — post reply ────────────────────────────────────────────
145if ($confirmed && $replyid && $ticket) {
146 $reply = Capsule::table('tbl_quick_replies')->where('id', $replyid)->first();
147 if (!$reply) die("Error: Reply option not found.");
148
149 $replyText = $reply->reply_text;
150
151 $result = localAPI('AddTicketReply', [
152 'ticketid' => $ticketid,
153 'message' => $replyText,
154 'adminid' => ADMIN_ID,
155 'status' => 'Answered',
156 ], ADMIN_USERNAME);
157
158 if (($result['result'] ?? '') === 'error' && strpos($result['message'], 'Name and email') !== false) {
159 Capsule::table('tblticketreplies')->insert([
160 'tid' => $ticketid,
161 'userid' => 0,
162 'contactid' => 0,
163 'name' => '',
164 'email' => '',
165 'date' => date('Y-m-d H:i:s'),
166 'message' => $replyText,
167 'admin' => $admin->firstname . ' ' . $admin->lastname,
168 'attachment' => '',
169 'rating' => 0,
170 ]);
171 Capsule::table('tbltickets')->where('id', $ticketid)->update([
172 'status' => 'Answered',
173 'lastreply' => date('Y-m-d H:i:s'),
174 ]);
175 $result = ['result' => 'success'];
176 }
177
178 if (($result['result'] ?? '') === 'success') {
179 $ticketTid = $ticket->tid ?? '';
180 $ticketTitle = $ticket->title ?? '';
181 $ticketViewUrl = 'https://theservice4u.com/clients/viewticket.php?tid=' . $ticketTid . '&c=' . ($ticket->c ?? '');
182
183 Capsule::table('tbl_email_inject')->where('userid', $ticket->userid)->delete();
184 Capsule::table('tbl_email_inject')->insert([
185 'userid' => $ticket->userid,
186 'ticket_id' => '#' . $ticketTid,
187 'ticket_subject' => $ticketTitle,
188 'ticket_status' => 'Answered',
189 'ticket_url' => $ticketViewUrl,
190 'created_at' => date('Y-m-d H:i:s'),
191 ]);
192
193 localAPI('SendEmail', [
194 'messagename' => 'Support Ticket Response',
195 'id' => $ticket->userid,
196 ], ADMIN_USERNAME);
197 }
198
199 $success = ($result['result'] ?? '') === 'success';
200 echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>" . ($success ? "Reply Sent" : "Error") . "</title></head>
201 <body style='background:#0f1117;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;'>
202 <div style='background:#181c27;padding:40px;border-radius:10px;border:1px solid " . ($success ? '#40c057' : '#c92a2a') . ";text-align:center;max-width:420px;width:100%;'>
203 <h2 style='color:" . ($success ? '#40c057' : '#ff6b6b') . ";margin:0 0 10px;'>" . ($success ? 'Reply Posted' : 'Error') . "</h2>";
204 if ($success) {
205 echo "<p style='color:#aab;margin:0 0 6px;'>Ticket <strong style='color:#fff;'>#{$ticketid}</strong> marked <strong style='color:#40c057;'>Answered</strong>.</p>
206 <p style='color:#aab;margin:0 0 24px;font-size:13px;'>Client has been notified by email.</p>
207 <button onclick='window.close()' style='padding:12px 25px;background:#40c057;color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:bold;width:100%;font-size:14px;'>Close Window</button>";
208 } else {
209 echo "<pre style='color:#ff6b6b;text-align:left;font-size:12px;'>" . print_r($result, true) . "</pre>";
210 }
211 echo "</div></body></html>";
212 exit;
213}
214
215// ── Status-only action (Close or Answered, no reply) ─────────────────────────
216$statusAction = $_GET['statusaction'] ?? '';
217if ($statusAction && $ticket && !$confirmed) {
218 $allowedStatuses = ['Closed' => '#c92a2a', 'Answered' => '#2f9e44'];
219 if (array_key_exists($statusAction, $allowedStatuses)) {
220 $color = $allowedStatuses[$statusAction];
221 $confirmUrl = "{$ticketUrl}&statusaction={$statusAction}&confirmed=1";
222 echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>Confirm</title></head>
223 <body style='background:#0f1117;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;'>
224 <div style='background:#181c27;padding:36px;border-radius:10px;border:1px solid {$color};max-width:420px;width:100%;'>
225 <p style='margin:0 0 4px;font-size:11px;color:#555;text-transform:uppercase;'>Ticket #{$ticketid}</p>
226 <h2 style='color:#fff;margin:0 0 20px;font-size:18px;'>" . htmlspecialchars($ticket->title) . "</h2>
227 <p style='color:#aab;font-size:13px;margin:0 0 24px;'>This will mark the ticket as <strong style='color:{$color};'>{$statusAction}</strong>. No reply will be posted.</p>
228 <div style='display:flex;gap:10px;'>
229 <a href='{$ticketUrl}' style='flex:1;padding:12px;background:#2a2d3a;color:#aab;text-align:center;border-radius:6px;text-decoration:none;font-size:13px;font-weight:600;'>Back</a>
230 <a href='{$confirmUrl}' style='flex:2;padding:12px;background:{$color};color:#fff;text-align:center;border-radius:6px;text-decoration:none;font-size:14px;font-weight:700;'>Yes, Mark {$statusAction}</a>
231 </div>
232 </div>
233 </body></html>";
234 exit;
235 }
236}
237
238if ($statusAction && $ticket && $confirmed) {
239 $allowedStatuses = ['Closed', 'Answered'];
240 if (in_array($statusAction, $allowedStatuses)) {
241 localAPI('UpdateTicket', [
242 'ticketid' => $ticketid,
243 'status' => $statusAction,
244 ], ADMIN_USERNAME);
245
246 $color = $statusAction === 'Closed' ? '#c92a2a' : '#2f9e44';
247 echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>Done</title></head>
248 <body style='background:#0f1117;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;'>
249 <div style='background:#181c27;padding:40px;border-radius:10px;border:1px solid {$color};text-align:center;max-width:420px;width:100%;'>
250 <h2 style='color:{$color};margin:0 0 10px;'>Ticket {$statusAction}</h2>
251 <p style='color:#aab;margin:0 0 24px;font-size:13px;'>Ticket <strong style='color:#fff;'>#{$ticketid}</strong> has been marked <strong style='color:{$color};'>{$statusAction}</strong>.</p>
252 <button onclick='window.close()' style='padding:12px 25px;background:{$color};color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:bold;width:100%;font-size:14px;'>Close Window</button>
253 </div>
254 </body></html>";
255 exit;
256 }
257}
258
259// ── Build reply tab content ───────────────────────────────────────────────────
260$replyTabContent = '';
261
262if ($replyid && !$confirmed && $ticket) {
263 // STEP 2: Confirm screen
264 $reply = Capsule::table('tbl_quick_replies')->where('id', $replyid)->first();
265 if ($reply) {
266 $confirmUrl = "{$ticketUrl}&rid={$replyid}&confirmed=1";
267 $color = htmlspecialchars($reply->color ?? '#495057');
268 $label = htmlspecialchars($reply->label);
269 $preview = nl2br(htmlspecialchars($reply->reply_text));
270
271 $replyTabContent = "
272 <div style='background:#0f1117;border:1px solid {$color};border-radius:8px;padding:20px;margin-bottom:20px;'>
273 <p style='margin:0 0 6px;font-size:11px;color:#555;text-transform:uppercase;letter-spacing:1px;'>Reply Preview — <span style='color:{$color};'>{$label}</span></p>
274 <p style='margin:0;color:#ccd;font-size:14px;line-height:1.6;'>{$preview}</p>
275 </div>
276 <p style='margin:0 0 16px;font-size:12px;color:#555;'>This will post the reply, mark the ticket <strong style='color:#fff;'>Answered</strong>, and notify the client by email.</p>
277 <div style='display:flex;gap:10px;'>
278 <a href='{$ticketUrl}' style='flex:1;padding:12px;background:#2a2d3a;color:#aab;text-align:center;border-radius:6px;text-decoration:none;font-size:13px;font-weight:600;'>Back</a>
279 <a href='{$confirmUrl}' style='flex:2;padding:12px;background:{$color};color:#fff;text-align:center;border-radius:6px;text-decoration:none;font-size:14px;font-weight:700;'>Yes, Send Reply</a>
280 </div>";
281 }
282} elseif ($ticket) {
283 // STEP 1: Button grid
284 $activeReplies = array_filter((array)$buttons->all(), fn($b) => $b->active);
285 $btnHtml = '';
286 $count = 0;
287 foreach ($activeReplies as $r) {
288 $count++;
289 $url = "{$ticketUrl}&rid={$r->id}";
290 $color = htmlspecialchars($r->color ?? '#495057');
291 $lbl = htmlspecialchars($r->label);
292
293 if ($count % 4 === 1) $btnHtml .= "<tr>";
294 $isFirst = ($count % 4 === 1);
295 $isLast = ($count % 4 === 0);
296 $pad = $isFirst ? '4px 4px 4px 0' : ($isLast ? '4px 0 4px 4px' : '4px');
297 $btnHtml .= "<td style='padding:{$pad};width:25%;vertical-align:top;'>
298 <a href='{$url}' style='display:block;padding:14px 6px;background:{$color};color:#fff;text-align:center;border-radius:6px;text-decoration:none;font-size:13px;font-weight:700;line-height:1.3;'>{$lbl}</a>
299 </td>";
300 if ($count % 4 === 0) $btnHtml .= "</tr>";
301 }
302 if ($count % 4 !== 0) {
303 for ($i = 0; $i < 4 - ($count % 4); $i++) $btnHtml .= "<td style='width:25%;'></td>";
304 $btnHtml .= "</tr>";
305 }
306
307 $replyTabContent = "
308 <p style='color:#555;margin:0 0 20px;font-size:13px;'>Select a reply below. You will preview it before anything is sent.</p>
309 <table style='width:100%;border-collapse:collapse;'>{$btnHtml}</table>
310 <div style='margin-top:16px;border-top:1px solid #2a2d3a;padding-top:16px;display:flex;gap:8px;'>
311 <a href='{$ticketUrl}&statusaction=Answered' style='flex:1;padding:11px;background:#2f9e44;color:#fff;text-align:center;border-radius:6px;text-decoration:none;font-size:13px;font-weight:700;'>Mark Answered</a>
312 <a href='{$ticketUrl}&statusaction=Closed' style='flex:1;padding:11px;background:#c92a2a;color:#fff;text-align:center;border-radius:6px;text-decoration:none;font-size:13px;font-weight:700;'>Close Ticket</a>
313 </div>";
314} else {
315 $replyTabContent = "<p style='color:#888;font-size:14px;'>No ticket selected. Open this page from a ticket email link.</p>";
316}
317
318// Ticket info header
319$ticketInfoHtml = '';
320if ($ticket) {
321 $ticketInfoHtml = "
322 <div style='background:#0f1117;border:1px solid #2a2d3a;border-radius:8px;padding:14px 16px;margin-bottom:20px;'>
323 <p style='margin:0 0 2px;font-size:11px;color:#555;text-transform:uppercase;letter-spacing:1px;'>Ticket #{$ticketid}</p>
324 <p style='margin:0;color:#fff;font-size:16px;font-weight:700;'>" . htmlspecialchars($ticket->title) . "</p>
325 </div>";
326}
327
328// ── Build manage tab table rows ───────────────────────────────────────────────
329$tableRows = '';
330foreach ($allButtons as $btn) {
331 $editLink = "{$baseUrl}&edit={$btn->id}";
332 $color = htmlspecialchars($btn->color ?? '#495057');
333 $tableRows .= "
334 <tr>
335 <td><span style='display:inline-block;padding:5px 10px;border-radius:4px;font-size:12px;font-weight:700;color:#fff;background:{$color};'>" . htmlspecialchars($btn->label) . "</span></td>
336 <td style='color:#666;font-size:13px;'>" . htmlspecialchars(mb_strimwidth($btn->reply_text, 0, 80, '…')) . "</td>
337 <td style='color:#555;text-align:center;'>" . (int)$btn->sort_order . "</td>
338 <td><span style='display:inline-block;padding:3px 8px;border-radius:4px;font-size:11px;font-weight:700;" . ($btn->active ? "background:#1a2e1a;color:#69db7c;" : "background:#2a2d3a;color:#666;") . "'>" . ($btn->active ? 'Active' : 'Off') . "</span></td>
339 <td>
340 <div style='display:flex;gap:6px;flex-wrap:wrap;'>
341 <a href='{$editLink}' style='padding:6px 12px;background:#1971c2;color:#fff;border-radius:4px;text-decoration:none;font-size:12px;font-weight:700;'>Edit</a>
342 <form method='POST' style='display:inline;'>
343 <input type='hidden' name='key' value='" . htmlspecialchars($key) . "'>
344 <input type='hidden' name='action' value='toggle'>
345 <input type='hidden' name='id' value='{$btn->id}'>
346 <button type='submit' style='padding:6px 12px;background:#2a2d3a;color:#aab;border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:700;'>" . ($btn->active ? 'Disable' : 'Enable') . "</button>
347 </form>
348 <form method='POST' style='display:inline;' onsubmit=\"return confirm('Delete this button?')\">
349 <input type='hidden' name='key' value='" . htmlspecialchars($key) . "'>
350 <input type='hidden' name='action' value='delete'>
351 <input type='hidden' name='id' value='{$btn->id}'>
352 <button type='submit' style='padding:6px 12px;background:#c92a2a;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:700;'>Delete</button>
353 </form>
354 </div>
355 </td>
356 </tr>";
357}
358
359?><!DOCTYPE html>
360<html>
361<head>
362 <meta charset='utf-8'>
363 <title>Ticket Manager</title>
364 <style>
365 * { box-sizing: border-box; margin: 0; padding: 0; }
366 body { background: #0f1117; color: #ccd; font-family: sans-serif; padding: 24px 20px; }
367 .wrap { max-width: 760px; margin: 0 auto; }
368
369 .tabs { display: flex; gap: 4px; }
370 .tab-btn {
371 padding: 10px 22px; border-radius: 8px 8px 0 0; font-size: 13px; font-weight: 700;
372 cursor: pointer; border: 1px solid #2a2d3a; border-bottom: none;
373 background: #0f1117; color: #555; text-decoration: none; display: inline-block;
374 }
375 .tab-btn.active { background: #181c27; color: #fff; }
376
377 .card { background: #181c27; border: 1px solid #2a2d3a; border-radius: 0 8px 8px 8px; padding: 24px; }
378 .tab-panel { display: none; }
379 .tab-panel.active { display: block; }
380
381 label { display: block; font-size: 12px; color: #777; text-transform: uppercase; letter-spacing: .5px; margin-bottom: 5px; margin-top: 14px; }
382 label:first-of-type { margin-top: 0; }
383 input[type=text], textarea, input[type=number] {
384 width: 100%; padding: 10px 12px; background: #0f1117; border: 1px solid #2a2d3a;
385 border-radius: 6px; color: #ccd; font-size: 14px; font-family: sans-serif;
386 }
387 input[type=color] { width: 50px; height: 38px; padding: 2px; background: #0f1117; border: 1px solid #2a2d3a; border-radius: 6px; cursor: pointer; vertical-align: middle; }
388 textarea { min-height: 80px; resize: vertical; line-height: 1.5; }
389 .row { display: flex; gap: 12px; flex-wrap: wrap; }
390 .row > div { flex: 1; min-width: 100px; }
391 .checkbox-row { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
392 .checkbox-row input { width: auto; }
393 .checkbox-row span { font-size: 13px; color: #aab; }
394
395 .btn { display: inline-block; padding: 10px 20px; border-radius: 6px; font-size: 13px; font-weight: 700; cursor: pointer; border: none; text-decoration: none; }
396 .btn-success { background: #2f9e44; color: #fff; }
397 .btn-muted { background: #2a2d3a; color: #aab; }
398 .btn:hover { opacity: .85; }
399
400 .msg-success { background: #1a2e1a; border: 1px solid #2f9e44; color: #69db7c; padding: 11px 14px; border-radius: 6px; margin-bottom: 18px; font-size: 13px; }
401 .msg-error { background: #2e1a1a; border: 1px solid #c92a2a; color: #ff6b6b; padding: 11px 14px; border-radius: 6px; margin-bottom: 18px; font-size: 13px; }
402
403 .divider { border: none; border-top: 1px solid #2a2d3a; margin: 22px 0; }
404 table { width: 100%; border-collapse: collapse; }
405 th { font-size: 11px; color: #555; text-transform: uppercase; letter-spacing: .5px; padding: 0 10px 8px; text-align: left; }
406 td { padding: 10px 10px; border-top: 1px solid #1e2130; vertical-align: middle; }
407 tr:hover td { background: #1a1e2b; }
408 .color-row { display: flex; gap: 8px; align-items: center; }
409 .color-row input[type=text] { width: 90px; }
410 </style>
411</head>
412<body>
413<div class='wrap'>
414
415 <div class='tabs'>
416 <a href='<?= $ticketid ? $ticketUrl : '#' ?>'
417 class='tab-btn <?= $activeTab === 'reply' ? 'active' : '' ?>'
418 <?= !$ticketid ? "onclick='return false;' style='opacity:.35;cursor:default;'" : '' ?>>
419 Reply<?= $ticket ? " — Ticket #{$ticketid}" : '' ?>
420 </a>
421 <a href='<?= $manageUrl ?>' class='tab-btn <?= $activeTab === 'manage' ? 'active' : '' ?>'>
422 Manage Buttons
423 </a>
424 </div>
425
426 <div class='card'>
427
428 <!-- Reply Tab -->
429 <div class='tab-panel <?= $activeTab === 'reply' ? 'active' : '' ?>'>
430 <?= $ticketInfoHtml ?>
431 <?= $replyTabContent ?>
432 </div>
433
434 <!-- Manage Tab -->
435 <div class='tab-panel <?= $activeTab === 'manage' ? 'active' : '' ?>'>
436
437 <?php if ($message): ?>
438 <div class='msg-success'><?= htmlspecialchars($message) ?></div>
439 <?php endif; ?>
440 <?php if ($error): ?>
441 <div class='msg-error'><?= htmlspecialchars($error) ?></div>
442 <?php endif; ?>
443
444 <h2 style='color:#fff;font-size:15px;margin-bottom:18px;'><?= $editing ? 'Edit Button' : 'Add New Button' ?></h2>
445
446 <form method='POST'>
447 <input type='hidden' name='key' value='<?= htmlspecialchars($key) ?>'>
448 <input type='hidden' name='action' value='<?= $editing ? 'edit' : 'add' ?>'>
449 <?php if ($editing): ?>
450 <input type='hidden' name='id' value='<?= $editing->id ?>'>
451 <?php endif; ?>
452
453 <div class='row'>
454 <div>
455 <label>Button Label</label>
456 <input type='text' name='label' value='<?= htmlspecialchars($editing->label ?? '') ?>' placeholder='e.g. Adult Pin' required>
457 </div>
458 <div style='flex:0 0 190px;'>
459 <label>Color</label>
460 <div class='color-row'>
461 <input type='color' id='colorPicker' value='<?= htmlspecialchars($editing->color ?? '#495057') ?>'>
462 <input type='text' id='colorHex' name='color_hex' value='<?= htmlspecialchars($editing->color ?? '#495057') ?>' placeholder='#495057'>
463 </div>
464 </div>
465 <div style='flex:0 0 90px;'>
466 <label>Sort Order</label>
467 <input type='number' name='sort_order' value='<?= (int)($editing->sort_order ?? 0) ?>' min='0'>
468 </div>
469 </div>
470
471 <label>Reply Text</label>
472 <textarea name='reply_text' placeholder='Message sent to the client...' required><?= htmlspecialchars($editing->reply_text ?? '') ?></textarea>
473
474 <div class='checkbox-row'>
475 <input type='checkbox' name='active' <?= (!$editing || $editing->active) ? 'checked' : '' ?>>
476 <span>Active</span>
477 </div>
478
479 <div style='margin-top:16px;display:flex;gap:10px;'>
480 <button type='submit' class='btn btn-success'><?= $editing ? 'Save Changes' : 'Add Button' ?></button>
481 <?php if ($editing): ?>
482 <a href='<?= $manageUrl ?>' class='btn btn-muted'>Cancel</a>
483 <?php endif; ?>
484 </div>
485 </form>
486
487 <hr class='divider'>
488
489 <h2 style='color:#fff;font-size:15px;margin-bottom:16px;'>All Buttons (<?= count($allButtons) ?>)</h2>
490
491 <?php if (!count($allButtons)): ?>
492 <p style='color:#555;font-size:13px;'>No buttons yet.</p>
493 <?php else: ?>
494 <table>
495 <thead>
496 <tr>
497 <th>Button</th>
498 <th>Reply Text</th>
499 <th style='width:55px;text-align:center;'>Order</th>
500 <th style='width:60px;'>Status</th>
501 <th style='width:210px;'>Actions</th>
502 </tr>
503 </thead>
504 <tbody><?= $tableRows ?></tbody>
505 </table>
506 <?php endif; ?>
507 </div>
508
509 </div>
510</div>
511
512<script>
513 const picker = document.getElementById('colorPicker');
514 const hex = document.getElementById('colorHex');
515 if (picker && hex) {
516 picker.addEventListener('input', () => { hex.value = picker.value; });
517 hex.addEventListener('input', () => {
518 if (/^#[0-9a-fA-F]{6}$/.test(hex.value)) picker.value = hex.value;
519 });
520 }
521</script>
522</body>
523</html>
524