Auto commit: 2026-01-31T14:43:39.337Z
This commit is contained in:
parent
a8024ec263
commit
0927396783
7
db/migrations/001_create_songs_table.sql
Normal file
7
db/migrations/001_create_songs_table.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS songs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
artist VARCHAR(255) NOT NULL,
|
||||
is_active TINYINT(1) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
260
index.php
260
index.php
@ -26,11 +26,22 @@ $promoImage = "assets/pasted-20260130-234122-115a4b49.png";
|
||||
$qrImage = "assets/pasted-20260131-000858-4fff58f0.jpg";
|
||||
$logoImage = "assets/pasted-20260131-002028-7985dfae.png";
|
||||
|
||||
// Fetch latest requests
|
||||
// Fetch latest requests and songs
|
||||
$requests = [];
|
||||
$songs = [];
|
||||
try {
|
||||
$stmt = db()->query("SELECT name, phone, message, created_at FROM listener_requests ORDER BY created_at DESC LIMIT 20");
|
||||
$requests = $stmt->fetchAll();
|
||||
|
||||
$stmtSongs = db()->query("SELECT * FROM songs ORDER BY created_at DESC");
|
||||
$songs = $stmtSongs->fetchAll();
|
||||
$activeSong = null;
|
||||
foreach ($songs as $s) {
|
||||
if ($s["is_active"]) {
|
||||
$activeSong = $s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Silently fail if table doesn't exist yet or other DB error
|
||||
}
|
||||
@ -406,6 +417,75 @@ try {
|
||||
.qr-container:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* New styles for Song Management */
|
||||
.nav-pills-glass .nav-link {
|
||||
color: rgba(255,255,255,0.6);
|
||||
border-radius: 15px;
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.nav-pills-glass .nav-link.active {
|
||||
background: var(--primary-color);
|
||||
color: #fff;
|
||||
box-shadow: 0 5px 15px rgba(255, 45, 85, 0.4);
|
||||
}
|
||||
.song-item {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 20px;
|
||||
padding: 1rem 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.song-item:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
.song-active {
|
||||
border-color: var(--primary-color);
|
||||
background: rgba(255, 45, 85, 0.1);
|
||||
}
|
||||
.song-title-text {
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
}
|
||||
.song-artist-text {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.btn-toggle-song {
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
padding: 5px 15px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-toggle-song.active {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
.btn-delete-song {
|
||||
background: rgba(255,0,0,0.1);
|
||||
color: #ff4444;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-delete-song:hover {
|
||||
background: #ff4444;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -422,8 +502,8 @@ try {
|
||||
</div>
|
||||
|
||||
<div class="song-info mb-4">
|
||||
<span class="d-block fw-bold fs-5" id="songTitle">Conectando...</span>
|
||||
<span class="d-block opacity-50 small" id="artistName">Lili Records Radio</span>
|
||||
<span class="d-block fw-bold fs-5" id="songTitle"><?= ($activeSong ? htmlspecialchars($activeSong["title"]) : "Conectando...") ?></span>
|
||||
<span class="d-block opacity-50 small" id="artistName"><?= ($activeSong ? htmlspecialchars($activeSong["artist"]) : "Lili Records Radio") ?></span>
|
||||
</div>
|
||||
|
||||
<div class="player-controls-row d-flex align-items-center gap-3 justify-content-center">
|
||||
@ -462,45 +542,117 @@ try {
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Right: Image + Comments -->
|
||||
<!-- Right: Image + Content -->
|
||||
<div class="right-content">
|
||||
<img src="<?= $promoImage ?>" alt="Lili Records Promo" class="promo-image">
|
||||
|
||||
<div class="comments-window">
|
||||
<?php if ($isAdmin): ?>
|
||||
<div class="comments-header">
|
||||
<div>
|
||||
<h5 class="mb-1"><i class="fas fa-comments me-2 text-primary"></i>Mensajes de Oyentes</h5>
|
||||
<p class="small opacity-50 mb-0">Gestiona las peticiones en tiempo real</p>
|
||||
</div>
|
||||
<div class="text-center qr-container" style="width: 90px;" data-bs-toggle="modal" data-bs-target="#qrModal">
|
||||
<img src="<?= $qrImage ?>" alt="QR Pago" class="w-100 rounded-3 mb-1">
|
||||
<p class="small fw-bold mb-0">APOYAR</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comments-list" id="commentsList">
|
||||
<?php if (empty($requests)): ?>
|
||||
<div class="text-center py-5 opacity-30">
|
||||
<i class="fas fa-comment-slash fa-4x mb-3"></i>
|
||||
<p>No hay mensajes todavía.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($requests as $req): ?>
|
||||
<div class="comment-item">
|
||||
<span class="comment-user"><?= htmlspecialchars($req['name']) ?></span>
|
||||
<p class="mb-2"><?= htmlspecialchars($req['message']) ?></p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="small opacity-40"><?= date('d M, H:i', strtotime($req['created_at'])) ?></span>
|
||||
<?php if (!empty($req['phone'])): ?>
|
||||
<a href="https://wa.me/<?= preg_replace('/[^0-9]/', '', $req['phone']) ?>" class="btn-wa-reply" target="_blank">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<ul class="nav nav-pills nav-pills-glass mb-4 justify-content-center gap-2" id="adminTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="requests-tab" data-bs-toggle="pill" data-bs-target="#requests-pane" type="button" role="tab">
|
||||
<i class="fas fa-comments me-2"></i>Mensajes
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="songs-tab" data-bs-toggle="pill" data-bs-target="#songs-pane" type="button" role="tab">
|
||||
<i class="fas fa-music me-2"></i>Canciones
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content flex-1 overflow-hidden d-flex flex-column">
|
||||
<!-- Tab: Requests -->
|
||||
<div class="tab-pane fade show active flex-1 overflow-hidden d-flex flex-column" id="requests-pane" role="tabpanel" tabindex="0">
|
||||
<div class="comments-header border-0 mb-3">
|
||||
<div>
|
||||
<h5 class="mb-1">Mensajes de Oyentes</h5>
|
||||
<p class="small opacity-50 mb-0">Peticiones en tiempo real</p>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
<div class="text-center qr-container" style="width: 80px;" data-bs-toggle="modal" data-bs-target="#qrModal">
|
||||
<img src="<?= $qrImage ?>" alt="QR Pago" class="w-100 rounded-3 mb-1">
|
||||
<p class="small fw-bold mb-0">QR</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comments-list" id="commentsList">
|
||||
<?php if (empty($requests)): ?>
|
||||
<div class="text-center py-5 opacity-30">
|
||||
<i class="fas fa-comment-slash fa-4x mb-3"></i>
|
||||
<p>No hay mensajes todavía.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($requests as $req): ?>
|
||||
<div class="comment-item">
|
||||
<span class="comment-user"><?= htmlspecialchars($req['name']) ?></span>
|
||||
<p class="mb-2"><?= htmlspecialchars($req['message']) ?></p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="small opacity-40"><?= date('d M, H:i', strtotime($req['created_at'])) ?></span>
|
||||
<?php if (!empty($req['phone'])): ?>
|
||||
<a href="https://wa.me/<?= preg_replace('/[^0-9]/', '', $req['phone']) ?>" class="btn-wa-reply" target="_blank">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Songs -->
|
||||
<div class="tab-pane fade flex-1 overflow-hidden d-flex flex-column" id="songs-pane" role="tabpanel" tabindex="0">
|
||||
<div class="mb-4">
|
||||
<form action="manage_songs.php" method="POST" class="row g-2">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<div class="col-sm-5">
|
||||
<input type="text" name="title" class="form-control form-control-glass" placeholder="Título canción" required>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<input type="text" name="artist" class="form-control form-control-glass" placeholder="Artista" required>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button type="submit" class="btn btn-send-request text-white w-100 h-100">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="comments-list">
|
||||
<?php if (empty($songs)): ?>
|
||||
<div class="text-center py-5 opacity-30">
|
||||
<i class="fas fa-music fa-4x mb-3"></i>
|
||||
<p>No hay canciones en la lista.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($songs as $song): ?>
|
||||
<div class="song-item <?= $song['is_active'] ? 'song-active' : '' ?>">
|
||||
<div>
|
||||
<span class="song-title-text"><?= htmlspecialchars($song['title']) ?></span>
|
||||
<span class="song-artist-text"><?= htmlspecialchars($song['artist']) ?></span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<form action="manage_songs.php" method="POST" class="m-0">
|
||||
<input type="hidden" name="action" value="toggle_active">
|
||||
<input type="hidden" name="id" value="<?= $song['id'] ?>">
|
||||
<button type="submit" class="btn-toggle-song <?= $song['is_active'] ? 'active' : '' ?>">
|
||||
<?= $song['is_active'] ? 'ACTIVA' : 'ACTIVAR' ?>
|
||||
</button>
|
||||
</form>
|
||||
<form action="manage_songs.php" method="POST" class="m-0" onsubmit="return confirm('¿Eliminar canción?')">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="id" value="<?= $song['id'] ?>">
|
||||
<button type="submit" class="btn-delete-song">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="comments-header border-0 mb-3">
|
||||
<div>
|
||||
@ -514,10 +666,29 @@ try {
|
||||
</div>
|
||||
|
||||
<div class="comments-list" style="max-height: 250px;">
|
||||
<!-- User Song List Section (Optional view) -->
|
||||
<?php if (!empty($songs)): ?>
|
||||
<div class="mb-4">
|
||||
<h6 class="small fw-bold text-uppercase opacity-50 mb-3">Lista de Canciones</h6>
|
||||
<?php foreach (array_slice($songs, 0, 5) as $song): ?>
|
||||
<div class="d-flex justify-content-between align-items-center py-2 px-3 mb-2 bg-white bg-opacity-5 rounded-4">
|
||||
<div>
|
||||
<span class="d-block small fw-bold"><?= htmlspecialchars($song['title']) ?></span>
|
||||
<span class="d-block tiny opacity-50" style="font-size: 0.7rem;"><?= htmlspecialchars($song['artist']) ?></span>
|
||||
</div>
|
||||
<?php if ($song['is_active']): ?>
|
||||
<span class="badge rounded-pill bg-primary" style="font-size: 0.6rem;">SONANDO</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h6 class="small fw-bold text-uppercase opacity-50 mb-3">Últimos Mensajes</h6>
|
||||
<?php if (empty($requests)): ?>
|
||||
<p class="text-center py-4 opacity-40 italic">Sé el primero en escribir...</p>
|
||||
<?php else: ?>
|
||||
<?php foreach (array_slice($requests, 0, 8) as $req): ?>
|
||||
<?php foreach (array_slice($requests, 0, 5) as $req): ?>
|
||||
<div class="comment-item py-2 px-3 mb-2 border-0 bg-white bg-opacity-5 rounded-4">
|
||||
<span class="comment-user small"><?= htmlspecialchars($req['name']) ?></span>
|
||||
<p class="mb-0 small"><?= htmlspecialchars($req['message']) ?></p>
|
||||
@ -611,6 +782,7 @@ try {
|
||||
const artistName = document.getElementById('artistName');
|
||||
const canvas = document.getElementById('visualizer');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const hasActiveSong = <?= json_encode(!empty($activeSong)) ?>;
|
||||
|
||||
let isPlaying = false;
|
||||
let audioContext;
|
||||
@ -627,7 +799,7 @@ try {
|
||||
source = audioContext.createMediaElementSource(audio);
|
||||
source.connect(analyser);
|
||||
analyser.connect(audioContext.destination);
|
||||
analyser.fftSize = 64; // Fewer bars for a cleaner, modern look
|
||||
analyser.fftSize = 64;
|
||||
const bufferLength = analyser.frequencyBinCount;
|
||||
dataArray = new Uint8Array(bufferLength);
|
||||
} catch (e) {
|
||||
@ -646,26 +818,23 @@ try {
|
||||
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
// We only use the first half of the frequency data because the high end is often empty
|
||||
const barCount = dataArray.length / 1.2;
|
||||
const barWidth = (width / barCount);
|
||||
let x = 0;
|
||||
|
||||
for (let i = 0; i < barCount; i++) {
|
||||
let barHeight = (dataArray[i] / 255) * height * 0.8;
|
||||
if (barHeight < 5) barHeight = 5; // Minimum bar height for visibility
|
||||
if (barHeight < 5) barHeight = 5;
|
||||
|
||||
// Create a vibrant gradient for each bar
|
||||
const gradient = ctx.createLinearGradient(0, height - barHeight, 0, height);
|
||||
gradient.addColorStop(0, '#ff2d55'); // Hot Pink
|
||||
gradient.addColorStop(0.5, '#ff512f'); // Vibrant Orange-Red
|
||||
gradient.addColorStop(0, '#ff2d55');
|
||||
gradient.addColorStop(0.5, '#ff512f');
|
||||
gradient.addColorStop(1, 'rgba(255, 45, 85, 0.2)');
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
|
||||
// Draw stylized bars with rounded tops
|
||||
const bx = x + 2; // small padding
|
||||
const bw = barWidth - 4; // bar thickness
|
||||
const bx = x + 2;
|
||||
const bw = barWidth - 4;
|
||||
const bh = barHeight;
|
||||
const by = height - bh;
|
||||
const radius = bw / 2;
|
||||
@ -708,6 +877,7 @@ try {
|
||||
});
|
||||
|
||||
async function fetchMetadata() {
|
||||
if (hasActiveSong) return; {
|
||||
try {
|
||||
const response = await fetch('https://api.radioking.io/widget/radio/lili-record-s-radio/track/current');
|
||||
const data = await response.json();
|
||||
|
||||
49
manage_songs.php
Normal file
49
manage_songs.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
session_start();
|
||||
|
||||
if (empty($_SESSION['is_admin'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'add') {
|
||||
$title = $_POST['title'] ?? '';
|
||||
$artist = $_POST['artist'] ?? '';
|
||||
if ($title && $artist) {
|
||||
$stmt = db()->prepare("INSERT INTO songs (title, artist) VALUES (?, ?)");
|
||||
$stmt->execute([$title, $artist]);
|
||||
}
|
||||
} elseif ($action === 'delete') {
|
||||
$id = $_POST['id'] ?? '';
|
||||
if ($id) {
|
||||
$stmt = db()->prepare("DELETE FROM songs WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
}
|
||||
} elseif ($action === 'toggle_active') {
|
||||
$id = $_POST['id'] ?? '';
|
||||
if ($id) {
|
||||
// First, check the current status
|
||||
$stmt = db()->prepare("SELECT is_active FROM songs WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$current = $stmt->fetchColumn();
|
||||
|
||||
if ($current) {
|
||||
// Deactivate it
|
||||
$stmt = db()->prepare("UPDATE songs SET is_active = 0 WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
} else {
|
||||
// Activate this one and deactivate ALL others
|
||||
$stmt = db()->prepare("UPDATE songs SET is_active = 0");
|
||||
$stmt->execute();
|
||||
$stmt = db()->prepare("UPDATE songs SET is_active = 1 WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: index.php?admin=lili');
|
||||
exit;
|
||||
Loading…
x
Reference in New Issue
Block a user