diff --git a/api/add_song_to_lineup.php b/api/add_song_to_lineup.php new file mode 100644 index 0000000..dbc54d1 --- /dev/null +++ b/api/add_song_to_lineup.php @@ -0,0 +1,59 @@ + false, 'message' => 'Invalid input.']); + exit; +} + +$lineup_id = $data['lineup_id']; +$song_id = $data['song_id']; + +try { + $pdo = db(); + + // Check if the song is already in the lineup + $stmt = $pdo->prepare("SELECT 1 FROM lineup_songs WHERE lineup_id = ? AND song_id = ?"); + $stmt->execute([$lineup_id, $song_id]); + if ($stmt->fetch()) { + http_response_code(409); // 409 Conflict + echo json_encode(['success' => false, 'message' => 'השיר כבר קיים בליינאפ.']); + exit; + } + + // Get the current number of songs in the lineup to determine the new order. + // This is much faster than calculating MAX(song_order). + $stmt = $pdo->prepare("SELECT COUNT(*) as song_count FROM lineup_songs WHERE lineup_id = ?"); + $stmt->execute([$lineup_id]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + $new_order = $result ? (int)$result['song_count'] : 0; + + + // Insert the new song + $stmt = $pdo->prepare("INSERT INTO lineup_songs (lineup_id, song_id, song_order) VALUES (?, ?, ?)"); + + if ($stmt->execute([$lineup_id, $song_id, $new_order])) { + // Fetch the added song details to return to the client + $song_stmt = $pdo->prepare("SELECT * FROM songs WHERE id = ?"); + $song_stmt->execute([$song_id]); + $song = $song_stmt->fetch(PDO::FETCH_ASSOC); + + echo json_encode(['success' => true, 'message' => 'השיר נוסף בהצלחה!', 'song' => $song]); + } else { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'לא ניתן היה להוסיף את השיר.']); + } + +} catch (PDOException $e) { + // Log error to a file for debugging + error_log("Add song to lineup failed: " . $e->getMessage()); + http_response_code(500); + // Return a more specific error message for debugging + echo json_encode(['success' => false, 'message' => 'שגיאת שרת: ' . $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/api/lineups_api.php b/api/lineups_api.php new file mode 100644 index 0000000..9da844f --- /dev/null +++ b/api/lineups_api.php @@ -0,0 +1,34 @@ + false, 'error' => 'Invalid request']; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $data = json_decode(file_get_contents('php://input'), true); + + if (isset($data['action'])) { + switch ($data['action']) { + case 'create': + if (!empty($data['name'])) { + try { + $pdo = db(); + $stmt = $pdo->prepare("INSERT INTO lineups (name) VALUES (?)"); + $stmt->execute([$data['name']]); + $response = ['success' => true, 'lineup_id' => $pdo->lastInsertId()]; + } catch (PDOException $e) { + $response['error'] = 'Database error: ' . $e->getMessage(); + } + } else { + $response['error'] = 'Lineup name is required.'; + } + break; + + default: + $response['error'] = 'Unknown action.'; + break; + } + } +} + +echo json_encode($response); diff --git a/api/remove_song_from_lineup.php b/api/remove_song_from_lineup.php new file mode 100644 index 0000000..7d70d8f --- /dev/null +++ b/api/remove_song_from_lineup.php @@ -0,0 +1,28 @@ +prepare("DELETE FROM lineup_songs WHERE lineup_id = ? AND song_id = ?"); + $stmt->execute([$lineup_id, $song_id]); + + echo json_encode(['success' => true]); + } catch (PDOException $e) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } + } else { + http_response_code(400); + echo json_encode(['success' => false, 'message' => 'Invalid input.']); + } +} else { + http_response_code(405); + echo json_encode(['success' => false, 'message' => 'Method not allowed.']); +} +?> \ No newline at end of file diff --git a/api/search_songs.php b/api/search_songs.php new file mode 100644 index 0000000..d08ff87 --- /dev/null +++ b/api/search_songs.php @@ -0,0 +1,23 @@ +prepare($sql); +$stmt->execute($params); +$songs = $stmt->fetchAll(); + +// --- Presentation --- +header('Content-Type: application/json'); +echo json_encode($songs); diff --git a/assets/js/lineup_details_page.js b/assets/js/lineup_details_page.js new file mode 100644 index 0000000..0f6be58 --- /dev/null +++ b/assets/js/lineup_details_page.js @@ -0,0 +1,118 @@ + +document.addEventListener('DOMContentLoaded', function () { + const lineupId = document.getElementById('lineup-id').value; + const searchInput = document.getElementById('song-search-input'); + const searchResultsContainer = document.getElementById('search-results'); + const lineupSongList = document.getElementById('lineup-song-list'); + + // 1. Search for songs + searchInput.addEventListener('keyup', function () { + const query = this.value; + + if (query.length < 2) { + searchResultsContainer.innerHTML = ''; + return; + } + + fetch('api/search_songs.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `query=${encodeURIComponent(query)}&lineup_id=${lineupId}` + }) + .then(response => response.json()) + .then(songs => { + searchResultsContainer.innerHTML = ''; + if (songs.length > 0) { + const list = document.createElement('ul'); + list.className = 'list-group'; + songs.forEach(song => { + const listItem = document.createElement('li'); + listItem.className = 'list-group-item d-flex justify-content-between align-items-center'; + listItem.innerHTML = ` + ${song.artist} - ${song.name} + + `; + list.appendChild(listItem); + }); + searchResultsContainer.appendChild(list); + } else { + searchResultsContainer.innerHTML = '

לא נמצאו שירים תואמים.

'; + } + }) + .catch(error => { + console.error('Error searching for songs:', error); + searchResultsContainer.innerHTML = '

אירעה שגיאה בחיפוש.

'; + }); + }); + + // 2. Add a song to the lineup + searchResultsContainer.addEventListener('click', function (e) { + if (e.target && e.target.classList.contains('add-song-btn')) { + const songId = e.target.dataset.songId; + + fetch('api/add_song_to_lineup.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `song_id=${songId}&lineup_id=${lineupId}` + }) + .then(response => response.json()) + .then(result => { + if (result.success) { + // Reload the page to see the updated list. It's simple and reliable. + location.reload(); + } else { + alert('ההוספה נכשלה: ' + (result.message || 'שגיאה לא ידועה')); + } + }) + .catch(error => { + console.error('Error adding song:', error); + alert('אירעה שגיאה קריטית בעת ההוספה.'); + }); + } + }); + + // 3. Remove a song from the lineup + lineupSongList.addEventListener('click', function (e) { + if (e.target && e.target.classList.contains('remove-song-btn')) { + const songId = e.target.dataset.songId; + + if (!confirm('האם אתה בטוח שברצונך להסיר את השיר הזה?')) { + return; + } + + fetch('api/remove_song_from_lineup.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `song_id=${songId}&lineup_id=${lineupId}` + }) + .then(response => response.json()) + .then(result => { + if (result.success) { + // Remove the song from the list in the UI + e.target.closest('li.list-group-item').remove(); + + // If the list is now empty, show the "empty" message + if (lineupSongList.children.length === 0) { + const emptyMessage = document.createElement('li'); + emptyMessage.id = 'empty-lineup-message'; + emptyMessage.className = 'list-group-item text-center text-muted'; + emptyMessage.textContent = 'אין עדיין שירים בליינאפ זה.'; + lineupSongList.appendChild(emptyMessage); + } + } else { + alert('ההסרה נכשלה: ' + (result.message || 'שגיאה לא ידועה')); + } + }) + .catch(error => { + console.error('Error removing song:', error); + alert('אירעה שגיאה קריטית בעת ההסרה.'); + }); + } + }); +}); diff --git a/assets/js/main.js b/assets/js/main.js index c19d73a..3464641 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,81 +1,41 @@ document.addEventListener('DOMContentLoaded', function () { - // Initialize toast notifications - const toastEl = document.getElementById('notificationToast'); - if (toastEl) { - const toast = new bootstrap.Toast(toastEl, { delay: 3000 }); - toast.show(); - } + // Handle Create Lineup form submission + const createLineupForm = document.getElementById('createLineupForm'); + if (createLineupForm) { + createLineupForm.addEventListener('submit', function(event) { + event.preventDefault(); + const lineupName = document.getElementById('lineupName').value; + // The submit button is outside the form, so we find it in the modal footer + const createButton = this.closest('.modal-content').querySelector('.modal-footer button[type="submit"]'); + const originalButtonText = createButton.innerHTML; - const songModalEl = document.getElementById('songModal'); - if (!songModalEl) return; + createButton.disabled = true; + createButton.innerHTML = ' יוצר...'; - const songModal = new bootstrap.Modal(songModalEl); - const modalTitle = songModalEl.querySelector('.modal-title'); - const songForm = document.getElementById('songForm'); - const actionInput = document.getElementById('action'); - const songIdInput = document.getElementById('song_id'); - - // Function to reset the modal to its "Add Song" state - const resetModal = () => { - songForm.reset(); - actionInput.value = 'create'; - songIdInput.value = ''; - modalTitle.textContent = 'הוספת שיר חדש'; - }; - - // Handle clicks on edit buttons - document.querySelectorAll('.edit-btn').forEach(button => { - button.addEventListener('click', function () { - const song = JSON.parse(this.dataset.song); - - resetModal(); // Start with a clean slate - - actionInput.value = 'update'; - modalTitle.textContent = 'עריכת שיר'; - songIdInput.value = song.id; - - document.getElementById('name').value = song.name; - document.getElementById('bpm').value = song.bpm; - - if (song.song_key && song.song_key.trim() !== '') { - const keyParts = song.song_key.split(' '); - document.getElementById('key_note').value = keyParts[0]; - document.getElementById('key_scale').value = keyParts[1] || 'Major'; - } else { - document.getElementById('key_note').value = ''; - document.getElementById('key_scale').value = 'Major'; - } - - document.getElementById('notes').value = song.notes; - document.getElementById('tags').value = song.tags; - - if (song.duration_seconds) { - const minutes = Math.floor(song.duration_seconds / 60); - const seconds = song.duration_seconds % 60; - document.getElementById('duration_minutes').value = minutes; - document.getElementById('duration_seconds').value = seconds; - } - - songModal.show(); - }); - }); - - // Handle click on the link to add the first song - const addSongBtnLink = document.getElementById('addSongBtnLink'); - if(addSongBtnLink) { - addSongBtnLink.addEventListener('click', (e) => { - e.preventDefault(); - resetModal(); - songModal.show(); + fetch('api/lineups_api.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'create', name: lineupName }), + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + window.location.reload(); // Reload to see the new lineup + } else { + alert('שגיאה ביצירת הליינאפ: ' + data.error); + } + }) + .catch(error => { + console.error('Error:', error); + alert('אירעה שגיאה בלתי צפויה.'); + }) + .finally(() => { + // This might not be reached if the page reloads, but it's good practice + createButton.disabled = false; + createButton.innerHTML = originalButtonText; + }); }); } - - // Reset the modal form when it's opened via the main "Add Song" button - // The main button works via data-attributes, so we just need to hook into the event - songModalEl.addEventListener('show.bs.modal', function (event) { - // If the trigger was NOT an edit button, reset the form for adding. - if (event.relatedTarget && !event.relatedTarget.classList.contains('edit-btn')) { - resetModal(); - } - }); -}); \ No newline at end of file +}); diff --git a/assets/js/songs_page.js b/assets/js/songs_page.js new file mode 100644 index 0000000..0cc98d7 --- /dev/null +++ b/assets/js/songs_page.js @@ -0,0 +1,178 @@ +document.addEventListener('DOMContentLoaded', function () { + // Live search for songs page + const searchInput = document.getElementById('searchInput'); + const clearSearchBtn = document.getElementById('clearSearchBtn'); + const tableBody = document.getElementById('songsTableBody'); + const originalNoSongsRow = document.getElementById('noSongsRow')?.cloneNode(true); + + if (searchInput && tableBody) { + // Function to format duration from seconds to MM:SS + const formatDuration = (seconds) => { + if (seconds === null || seconds < 0) return '00:00'; + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; + }; + + // Function to render table rows + const renderTable = (songs, searchTerm) => { + tableBody.innerHTML = ''; // Clear existing rows + + if (songs.length === 0) { + const noResultsRow = document.createElement('tr'); + noResultsRow.innerHTML = `לא נמצאו שירים התואמים את החיפוש "${searchTerm}".`; + if (searchTerm === '') { + if(originalNoSongsRow){ + tableBody.appendChild(originalNoSongsRow); + } else { + noResultsRow.innerHTML = `עדיין אין שירים במאגר. הוסף את השיר הראשון שלך!`; + tableBody.appendChild(noResultsRow); + } + } else { + tableBody.appendChild(noResultsRow); + } + } else { + songs.forEach((song, index) => { + const row = document.createElement('tr'); + const tagsHtml = song.tags.split(',').map(tag => { + const trimmedTag = tag.trim(); + return trimmedTag ? `${escapeHTML(trimmedTag)}` : ''; + }).join(' '); + + row.innerHTML = ` + ${index + 1} + ${escapeHTML(song.artist)} + ${escapeHTML(song.name)} + ${escapeHTML(song.bpm)} + ${escapeHTML(song.song_key)} + ${formatDuration(song.duration_seconds)} + ${tagsHtml} + ${escapeHTML(song.notes)} + + +
+ + + +
+ + `; + tableBody.appendChild(row); + }); + } + }; + + // Debounce function to limit API calls + let debounceTimeout; + const handleSearch = () => { + const searchTerm = searchInput.value.trim(); + clearTimeout(debounceTimeout); + + clearSearchBtn.style.display = searchTerm ? 'block' : 'none'; + + debounceTimeout = setTimeout(() => { + fetch(`api/search_songs.php?search=${encodeURIComponent(searchTerm)}`) + .then(response => response.json()) + .then(songs => { + renderTable(songs, searchTerm); + }) + .catch(error => console.error('Error fetching search results:', error)); + }, 300); // 300ms delay + }; + + searchInput.addEventListener('input', handleSearch); + + clearSearchBtn.addEventListener('click', () => { + searchInput.value = ''; + handleSearch(); + }); + } + + function escapeHTML(str) { + if (str === null || str === undefined) return ''; + return String(str).replace(/[&<>'"/]/g, function (s) { + return { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }[s]; + }); + } + + const songModalEl = document.getElementById('songModal'); + if (songModalEl) { + const songModal = new bootstrap.Modal(songModalEl); + const modalTitle = songModalEl.querySelector('.modal-title'); + const songForm = document.getElementById('songForm'); + const actionInput = document.getElementById('action'); + const songIdInput = document.getElementById('song_id'); + + // Function to reset the modal to its "Add Song" state + const resetModal = () => { + songForm.reset(); + actionInput.value = 'create'; + songIdInput.value = ''; + modalTitle.textContent = 'הוספת שיר חדש'; + }; + + // Use event delegation for edit buttons since they are dynamically loaded + document.body.addEventListener('click', function(event) { + const editBtn = event.target.closest('.edit-btn'); + if (editBtn) { + const song = JSON.parse(editBtn.dataset.song); + + resetModal(); // Start with a clean slate + + actionInput.value = 'update'; + modalTitle.textContent = 'עריכת שיר'; + songIdInput.value = song.id; + + document.getElementById('name').value = song.name; + document.getElementById('artist').value = song.artist; + document.getElementById('bpm').value = song.bpm; + + if (song.song_key && song.song_key.trim() !== '') { + const keyParts = song.song_key.split(' '); + document.getElementById('key_note').value = keyParts[0]; + document.getElementById('key_scale').value = keyParts[1] || 'Major'; + } else { + document.getElementById('key_note').value = ''; + document.getElementById('key_scale').value = 'Major'; + } + + document.getElementById('notes').value = song.notes; + document.getElementById('tags').value = song.tags; + + if (song.duration_seconds) { + const minutes = Math.floor(song.duration_seconds / 60); + const seconds = song.duration_seconds % 60; + document.getElementById('duration_minutes').value = minutes; + document.getElementById('duration_seconds').value = seconds; + } + + songModal.show(); + } + + const addSongLink = event.target.closest('#addSongBtnLink'); + if(addSongLink){ + event.preventDefault(); + resetModal(); + songModal.show(); + } + }); + + // Reset the modal form when it's opened via the main "Add Song" button + songModalEl.addEventListener('show.bs.modal', function (event) { + if (event.relatedTarget && !event.relatedTarget.classList.contains('edit-btn')) { + resetModal(); + } + }); + } +}); \ No newline at end of file diff --git a/assets/pasted-20251024-093024-ed968ccb.png b/assets/pasted-20251024-093024-ed968ccb.png new file mode 100644 index 0000000..6347707 Binary files /dev/null and b/assets/pasted-20251024-093024-ed968ccb.png differ diff --git a/assets/pasted-20251024-093716-af880ec1.png b/assets/pasted-20251024-093716-af880ec1.png new file mode 100644 index 0000000..5c05210 Binary files /dev/null and b/assets/pasted-20251024-093716-af880ec1.png differ diff --git a/assets/pasted-20251024-094241-a54f51c9.png b/assets/pasted-20251024-094241-a54f51c9.png new file mode 100644 index 0000000..7fd1854 Binary files /dev/null and b/assets/pasted-20251024-094241-a54f51c9.png differ diff --git a/assets/pasted-20251024-100423-adeccb17.png b/assets/pasted-20251024-100423-adeccb17.png new file mode 100644 index 0000000..3d20678 Binary files /dev/null and b/assets/pasted-20251024-100423-adeccb17.png differ diff --git a/assets/pasted-20251024-101729-6227f5cd.png b/assets/pasted-20251024-101729-6227f5cd.png new file mode 100644 index 0000000..a1e1eea Binary files /dev/null and b/assets/pasted-20251024-101729-6227f5cd.png differ diff --git a/assets/pasted-20251024-105800-5649a69d.png b/assets/pasted-20251024-105800-5649a69d.png new file mode 100644 index 0000000..ef98b40 Binary files /dev/null and b/assets/pasted-20251024-105800-5649a69d.png differ diff --git a/db/migrate.php b/db/migrate.php deleted file mode 100644 index 7065604..0000000 --- a/db/migrate.php +++ /dev/null @@ -1,38 +0,0 @@ - PDO::ERRMODE_EXCEPTION, - ]); - $pdo_admin->exec("CREATE DATABASE IF NOT EXISTS `".DB_NAME."` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); - // echo "Database '".DB_NAME."' created or already exists.\n"; - - // 2. Connect to the specific database to create tables - $pdo = db(); - if ($pdo === null) { - throw new Exception("Failed to connect to the database. The db() function returned null."); - } - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - $sql = " - CREATE TABLE IF NOT EXISTS songs ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - bpm INT, - song_key VARCHAR(50), - duration_seconds INT, - notes TEXT, - tags VARCHAR(255), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"; - - $pdo->exec($sql); - // echo "Table 'songs' created successfully (if it didn\'t exist).\n"; - -} catch (Exception $e) { - http_response_code(500); - die("DB ERROR: " . $e->getMessage()); -} \ No newline at end of file diff --git a/db/migrations/001_initial_schema.sql b/db/migrations/001_initial_schema.sql new file mode 100644 index 0000000..a06e042 --- /dev/null +++ b/db/migrations/001_initial_schema.sql @@ -0,0 +1,26 @@ +-- Initial Schema for Lineup Application + +CREATE TABLE IF NOT EXISTS `lineups` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `songs` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `title` VARCHAR(255) NOT NULL, + `artist` VARCHAR(255) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `lineup_songs` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `lineup_id` INT NOT NULL, + `song_id` INT NOT NULL, + `display_order` INT NOT NULL DEFAULT 0, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (`lineup_id`) REFERENCES `lineups`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`song_id`) REFERENCES `songs`(`id`) ON DELETE CASCADE, + UNIQUE KEY `lineup_song_unique` (`lineup_id`, `song_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + diff --git a/db/migrations/002_add_indexes.sql b/db/migrations/002_add_indexes.sql new file mode 100644 index 0000000..913a627 --- /dev/null +++ b/db/migrations/002_add_indexes.sql @@ -0,0 +1,7 @@ +-- Add indexes to lineup_songs for faster lookups +ALTER TABLE `lineup_songs` ADD INDEX `idx_lineup_id` (`lineup_id`); +ALTER TABLE `lineup_songs` ADD INDEX `idx_song_id` (`song_id`); + +-- Add a unique constraint to prevent adding the same song to a lineup twice +-- This also creates an index +ALTER TABLE `lineup_songs` ADD UNIQUE `unique_lineup_song` (`lineup_id`, `song_id`); diff --git a/db/migrations/003_add_display_order_column.sql b/db/migrations/003_add_display_order_column.sql new file mode 100644 index 0000000..0d2644d --- /dev/null +++ b/db/migrations/003_add_display_order_column.sql @@ -0,0 +1,2 @@ +-- Add display_order column to lineup_songs table +ALTER TABLE `lineup_songs` ADD COLUMN `display_order` INT NOT NULL DEFAULT 0 AFTER `song_id`; diff --git a/db/migrations/004_add_order_index.sql b/db/migrations/004_add_order_index.sql new file mode 100644 index 0000000..1d6f54f --- /dev/null +++ b/db/migrations/004_add_order_index.sql @@ -0,0 +1,2 @@ +-- Add index for display_order to speed up sorting +ALTER TABLE `lineup_songs` ADD INDEX `idx_display_order` (`display_order`); diff --git a/db/migrations/005_add_artist_to_songs.sql b/db/migrations/005_add_artist_to_songs.sql new file mode 100644 index 0000000..a5877b9 --- /dev/null +++ b/db/migrations/005_add_artist_to_songs.sql @@ -0,0 +1 @@ +ALTER TABLE `songs` ADD `artist` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL AFTER `name`; diff --git a/db/migrations/006_add_song_order_index.sql b/db/migrations/006_add_song_order_index.sql new file mode 100644 index 0000000..2e96789 --- /dev/null +++ b/db/migrations/006_add_song_order_index.sql @@ -0,0 +1,2 @@ +-- Add index for song_order to speed up sorting +ALTER TABLE `lineup_songs` ADD INDEX `idx_song_order` (`song_order`); diff --git a/debug_songs.php b/debug_songs.php new file mode 100644 index 0000000..4318fe6 --- /dev/null +++ b/debug_songs.php @@ -0,0 +1,44 @@ +מצב מאגר השירים'; +echo '

בעמוד זה מוצגת רשימת השירים כפי שהיא שמורה כרגע במסד הנתונים.

'; + +try { + require_once 'db/config.php'; + $pdo = db(); + + $stmt = $pdo->query("SELECT * FROM songs ORDER BY id ASC"); + $songs = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($songs)) { + echo '
המאגר ריק. לא נמצאו שירים.
'; + } else { + echo ''; + echo ''; + echo ''; + foreach ($songs as $song) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
IDNameArtistBPMKeyDurationTagsNotes
' . htmlspecialchars($song['id']) . '' . htmlspecialchars($song['name']) . '' . htmlspecialchars($song['artist'] ?? 'N/A') . '' . htmlspecialchars($song['bpm'] ?? 'N/A') . '' . htmlspecialchars($song['song_key'] ?? 'N/A') . '' . htmlspecialchars($song['duration_seconds'] ?? 'N/A') . '' . htmlspecialchars($song['tags'] ?? 'N/A') . '' . htmlspecialchars($song['notes'] ?? 'N/A') . '
'; + echo '

סה"כ שירים: ' . count($songs) . '

'; + } + +} catch (Exception $e) { + echo '
'; + echo 'שגיאה חמורה!
'; + echo 'לא ניתן היה להתחבר למסד הנתונים או לשלוף את המידע.
'; + echo 'פרטי השגיאה: ' . $e->getMessage(); + echo '
'; +} +?> diff --git a/includes/header.php b/includes/header.php index 78fbcf0..e2bb676 100644 --- a/includes/header.php +++ b/includes/header.php @@ -28,14 +28,15 @@ diff --git a/index.php b/index.php index a54858a..a2631ed 100644 --- a/index.php +++ b/index.php @@ -1,4 +1,7 @@ - +

ברוכים הבאים ל-Ari Stage

diff --git a/lineup_details.php b/lineup_details.php new file mode 100644 index 0000000..f2b8a04 --- /dev/null +++ b/lineup_details.php @@ -0,0 +1,95 @@ +
מזהה ליינאפ לא תקין.
'; + require_once 'includes/footer.php'; + exit; +} + +$lineup_id = intval($_GET['id']); +$db = db(); + +// Fetch lineup details +$stmt = $db->prepare("SELECT * FROM lineups WHERE id = ?"); +$stmt->execute([$lineup_id]); +$lineup = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$lineup) { + echo '
הליינאפ לא נמצא.
'; + require_once 'includes/footer.php'; + exit; +} + +// Fetch songs for this lineup (to be implemented) +$songs_stmt = $db->prepare(" + SELECT s.* FROM songs s + JOIN lineup_songs ls ON s.id = ls.song_id + WHERE ls.lineup_id = ? + ORDER BY ls.song_order ASC +"); +$songs_stmt->execute([$lineup_id]); +$songs = $songs_stmt->fetchAll(PDO::FETCH_ASSOC); + +?> + +
+
+

+ חזרה לרשימת הליינאפים +
+ + +
+ +
+

הוספת שירים

+
+
+
+ + +
+
+ +
+
+
+
+ + +
+

שירים בליינאפ

+
+
    + +
  • + אין עדיין שירים בליינאפ זה. +
  • + + +
  • + + + - + + +
  • + + +
+
+
+
+
+ + + + + + diff --git a/lineups.php b/lineups.php new file mode 100644 index 0000000..53b04a6 --- /dev/null +++ b/lineups.php @@ -0,0 +1,72 @@ + + +
+
+

הליינאפים שלי

+ +
+ + query("SELECT * FROM lineups ORDER BY created_at DESC"); + $lineups = $stmt->fetchAll(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + // Handle DB error + $lineups = []; + echo "
שגיאה בטעינת הליינאפים: " . htmlspecialchars($e->getMessage()) . "
"; + } + ?> + + +
+ +

עדיין לא יצרת ליינאפים.

+

לחץ על "צור ליינאפ חדש" כדי להתחיל.

+
+ +
+ + +
+
+ נוצר ב: +
+ +
+ +
+ + +
+ + + + + diff --git a/migrate.php b/migrate.php new file mode 100644 index 0000000..f57eecb --- /dev/null +++ b/migrate.php @@ -0,0 +1,77 @@ +exec("CREATE TABLE IF NOT EXISTS `migrations` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `migration_file` VARCHAR(255) NOT NULL, + `applied_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `migration_file_unique` (`migration_file`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + echo "1. 'migrations' table is ready.\n"; + + // 2. Get all migrations that have already been run + $stmt = $pdo->query("SELECT `migration_file` FROM `migrations`"); + $applied_migrations = $stmt->fetchAll(PDO::FETCH_COLUMN); + echo "2. Found " . count($applied_migrations) . " applied migrations.\n"; + + // 3. Find all migration files on disk + $migration_files = glob('db/migrations/*.sql'); + sort($migration_files); + echo "3. Found " . count($migration_files) . " migration files on disk.\n"; + + + // 4. Apply pending migrations + $migrations_applied_count = 0; + foreach ($migration_files as $file) { + $basename = basename($file); + if (in_array($basename, $applied_migrations)) { + echo "- Skipping already applied migration: {$basename}\n"; + continue; + } + + echo "+ Applying new migration: {$basename}...\n"; + $sql = file_get_contents($file); + if (empty(trim($sql))) { + echo " ...file is empty, skipping.\n"; + continue; + } + + try { + $pdo->beginTransaction(); + + $pdo->exec($sql); + + $insert_stmt = $pdo->prepare("INSERT INTO `migrations` (migration_file) VALUES (?)"); + $insert_stmt->execute([$basename]); + + $pdo->commit(); + echo " ...SUCCESS!\n"; + $migrations_applied_count++; + + } catch (Exception $e) { + $pdo->rollBack(); + echo " ...ERROR: " . $e->getMessage() . "\n"; + echo "\nMigration failed. No changes were made to the database schema.\n"; + exit; + } + } + + if ($migrations_applied_count > 0) { + echo "\nFinished. Applied {" . $migrations_applied_count . "} new migrations successfully.\n"; + } else { + echo "\nFinished. Database is already up to date.\n"; + } + +} catch (Exception $e) { + echo "A critical error occurred: " . $e->getMessage() . "\n"; +} + +?> \ No newline at end of file diff --git a/songs.php b/songs.php index fe62850..80a4cb6 100644 --- a/songs.php +++ b/songs.php @@ -1,4 +1,5 @@ prepare("INSERT INTO songs (name, bpm, song_key, duration_seconds, notes, tags) VALUES (?, ?, ?, ?, ?, ?)"); - $stmt->execute([$name, $bpm, $song_key, $duration_seconds, $notes, $tags]); + $stmt = $pdo->prepare("INSERT INTO songs (name, artist, bpm, song_key, duration_seconds, notes, tags) VALUES (?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$name, $artist, $bpm, $song_key, $duration_seconds, $notes, $tags]); $_SESSION['notification'] = ['message' => 'השיר נוצר בהצלחה!', 'type' => 'success']; } else { // update $id = (int)($_POST['id'] ?? 0); if ($id > 0) { - $stmt = $pdo->prepare("UPDATE songs SET name=?, bpm=?, song_key=?, duration_seconds=?, notes=?, tags=? WHERE id=?"); - $stmt->execute([$name, $bpm, $song_key, $duration_seconds, $notes, $tags, $id]); + $stmt = $pdo->prepare("UPDATE songs SET name=?, artist=?, bpm=?, song_key=?, duration_seconds=?, notes=?, tags=? WHERE id=?"); + $stmt->execute([$name, $artist, $bpm, $song_key, $duration_seconds, $notes, $tags, $id]); $_SESSION['notification'] = ['message' => 'השיר עודכן בהצלחה!', 'type' => 'success']; } } @@ -64,8 +66,19 @@ if (isset($_SESSION['notification'])) { } -// Fetch all songs to display -$songs = $pdo->query("SELECT * FROM songs ORDER BY name ASC")->fetchAll(); +// Fetch songs with search functionality +$search = $_GET['search'] ?? ''; +$sql = "SELECT * FROM songs"; +$params = []; +if (!empty($search)) { + $sql .= " WHERE name LIKE ? OR artist LIKE ? OR bpm LIKE ? OR song_key LIKE ? OR notes LIKE ? OR tags LIKE ?"; + $searchTerm = "%{$search}%"; + $params = [$searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm]; +} +$sql .= " ORDER BY id ASC"; +$stmt = $pdo->prepare($sql); +$stmt->execute($params); +$songs = $stmt->fetchAll(); function format_duration($seconds) { if ($seconds === null || $seconds < 0) return '00:00'; @@ -98,28 +111,50 @@ include 'includes/header.php'; -
+ +
+
+
+ + + +
+
+
+ +
+ + + - + - - + + - + $song): ?> + + @@ -131,6 +166,7 @@ include 'includes/header.php'; +
#שם האמן שם השיר BPM סולם משך תגיםהערות פעולות
עדיין אין שירים במאגר. הוסף את השיר הראשון שלך!
+ + לא נמצאו שירים התואמים את החיפוש "". + + עדיין אין שירים במאגר. הוסף את השיר הראשון שלך! + +