Server : Apache/2.4.18 (Ubuntu) System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64 User : oppastar ( 1041) PHP Version : 7.0.33-0ubuntu0.16.04.15 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, Directory : /var/www/laciasmara.com/public_html/shop/application/views/admin_new/reviews/ |
Upload File : |
<main class="flex-1 p-4 bg-purple-50"> <div class="flex items-center mb-4"> <h1 class="text-xl font-bold text-[#333]">Daftar Review Pelanggan</h1> <div class="ml-auto flex items-center gap-2"> <button type="button" id="addReview" class="border border-gray-300 text-sm rounded-lg px-4 py-2 bg-white hover:bg-gray-50 gap-4 flex items-center justify-between focus:outline-none focus:ring-1 focus:ring-[#7A4397] hover:bg-[#5a2f73] hover:bg-[#5a2f73] transition duration-300 ease-in-out text-[#333]"> <i data-feather="plus" class="h-4 w-4"></i> <span>Tambah Review</span> </button> <div class="relative" id="dateFilterDropdown"> <button class="border border-gray-300 text-sm rounded-lg px-4 py-2 bg-white hover:bg-gray-50 flex items-center justify-between w-[350px] focus:outline-none focus:ring-1 focus:ring-[#7A4397]"> <div class="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-[#333]" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> </svg> <span id="dateFilterText">Hari ini</span> </div> <i data-feather="chevron-down" class="w-4 h-4 text-gray-700"></i> </button> <div class="hidden absolute right-0 top-full mt-2 bg-white border border-gray-200 rounded-lg shadow-lg z-50 w-[650px]" id="dateFilterMenu"> <div class="p-4"> <div class="flex"> <div class="w-1/2 border-r border-gray-200 pr-2"> <h3 class="text-gray-700 font-medium mb-2">Rentang waktu</h3> <div class="flex flex-col"> <div class="py-3 hover:bg-gray-50 cursor-pointer date-option" data-option="today"> <div class="text-gray-800 pl-2">Hari ini</div> </div> <!-- Kemarin --> <div class="py-3 hover:bg-gray-50 cursor-pointer date-option" data-option="yesterday"> <div class="text-gray-800 pl-2">Kemarin</div> </div> <!-- 7 hari terakhir --> <div class="py-3 hover:bg-gray-50 cursor-pointer date-option" data-option="last7days"> <div class="text-gray-800 pl-2">7 hari terakhir</div> </div> <!-- 30 hari terakhir --> <div class="py-3 hover:bg-gray-50 cursor-pointer date-option" data-option="last30days"> <div class="text-gray-800 pl-2">30 hari terakhir</div> </div> <!-- Bulan ini --> <div class="py-3 hover:bg-gray-50 cursor-pointer date-option" data-option="thisMonth"> <div class="text-gray-800 pl-2">Bulan ini</div> </div> <!-- Tahun ini --> <div class="py-3 hover:bg-gray-50 cursor-pointer date-option" data-option="thisYear"> <div class="text-gray-800 pl-2">Tahun ini</div> </div> <!-- Separator --> <div class="my-2 border-t border-gray-200"></div> <!-- Pilih Tanggal with active indicator --> <div id="customDateOption" class="py-3 hover:bg-gray-50 cursor-pointer date-option relative" data-option="customDate"> <div class="text-gray-800 pl-2 flex items-center"> <span>Pilih Tanggal</span> <div id="customDateIndicator" class="hidden absolute left-0 top-0 bottom-0 w-1 bg-green-500"></div> </div> </div> </div> </div> <!-- Right Column - Dynamic content --> <div class="w-1/2 pl-2"> <!-- Date display for predefined options --> <div id="predefinedDateInfo"> <h3 class="text-gray-700 font-medium mb-2" id="currentMonthTitle">Februari 2025</h3> <div id="dateDisplays"> <div class="py-3" id="todayDateDisplay"> <div class="text-gray-600" id="todayDate">28 Feb 2025 (00:00 - 11:00)</div> </div> <div class="py-3 hidden" id="yesterdayDateDisplay"> <div class="text-gray-600" id="yesterdayDate">27 Feb 2025</div> </div> <div class="py-3 hidden" id="last7daysDateDisplay"> <div class="text-gray-600" id="last7daysDate">21 Feb - 27 Feb 2025</div> </div> <div class="py-3 hidden" id="last30daysDateDisplay"> <div class="text-gray-600" id="last30daysDate">29 Jan - 27 Feb 2025</div> </div> <div class="py-3 hidden" id="thisMonthDateDisplay"> <div class="text-gray-600" id="thisMonthDate">01 Feb - 28 Feb 2025</div> </div> <div class="py-3 hidden" id="perDayDateDisplay"> <div class="text-gray-600">Filter per hari</div> </div> <div class="py-3 hidden" id="perWeekDateDisplay"> <div class="text-gray-600">Filter per minggu</div> </div> <div class="py-3 hidden" id="perMonthDateDisplay"> <div class="text-gray-600">Filter per bulan</div> </div> </div> </div> <!-- Calendar for custom date --> <div id="calendarContainer" class="hidden"> <div class="text-center mb-2"> <div id="customDateMessage" class="text-gray-500 text-sm">Kamu belum pilih tanggal</div> </div> <div id="flatpickrContainer"></div> </div> </div> </div> </div> </div> </div> </div> </div> <?php if ($this->session->flashdata('message')): ?> <div class="alert flex items-center justify-between bg-<?php echo $this->session->flashdata('message_type') === 'success' ? 'green' : 'red'; ?>-100 border-l-4 border-<?php echo $this->session->flashdata('message_type') === 'success' ? 'green' : 'red'; ?>-500 text-<?php echo $this->session->flashdata('message_type') === 'success' ? 'green' : 'red'; ?>-800 px-6 py-4 rounded-lg shadow-lg transition transform duration-300"> <div class="flex items-center"> <i data-feather="<?php echo $this->session->flashdata('message_type') === 'success' ? 'check-circle' : 'x-circle'; ?>" class="h-6 w-6 mr-3"></i> <span class="font-semibold"><?php echo $this->session->flashdata('message'); ?></span> </div> <button class="ml-4 text-<?php echo $this->session->flashdata('message_type') === 'success' ? 'green' : 'red'; ?>-500 hover:text-<?php echo $this->session->flashdata('message_type') === 'success' ? 'green' : 'red'; ?>-700 focus:outline-none" onclick="this.parentElement.style.display='none'"> <i data-feather="x" class="h-5 w-5"></i> </button> </div> <?php endif; ?> <!-- Search and Filters --> <div class="mt-4 flex flex-col gap-4 sm:flex-row sm:flex-wrap text-sm"> <!-- Filter Dropdowns --> <div class="flex flex-wrap gap-2"> <!-- Filter Dropdown --> <div class="relative" id="filterDropdown"> <button class="border border-gray-300 text-sm rounded-lg px-4 py-2 bg-white hover:bg-gray-50 flex items-center justify-between w-[200px] focus:outline-none focus:ring-1 focus:ring-[#7A4397]"> <span id="filterText">Filter</span> <i data-feather="chevron-down" class="w-4 h-4 text-gray-700"></i> </button> <div class="hidden absolute left-0 top-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 min-w-[200px]" id="filterMenu"> <!-- Main Menu Items --> <div class="py-1"> <!-- Jenis Kelamin --> <div class="px-4 py-2 text-sm hover:bg-gray-100 cursor-pointer flex justify-between items-center group relative"> <span>Rating</span> <i data-feather="chevron-right" class="w-4 h-4 text-gray-700"></i> <div class="hidden group-hover:block absolute left-full top-0 bg-white border border-gray-200 rounded-lg shadow-lg min-w-[200px] -mt-1"> <div class="py-1"> <label class="flex items-center px-4 py-2 hover:bg-gray-100 cursor-pointer"> <input type="checkbox" class="mr-2 filter-checkbox" data-filter="rating" data-param="is5Star" data-value="true"> <span>⭐⭐⭐⭐⭐</span> </label> <label class="flex items-center px-4 py-2 hover:bg-gray-100 cursor-pointer"> <input type="checkbox" class="mr-2 filter-checkbox" data-filter="rating" data-param="is4Star" data-value="true"> <span>⭐⭐⭐⭐</span> </label> <label class="flex items-center px-4 py-2 hover:bg-gray-100 cursor-pointer"> <input type="checkbox" class="mr-2 filter-checkbox" data-filter="rating" data-param="is3Star" data-value="true"> <span>⭐⭐⭐</span> </label> <label class="flex items-center px-4 py-2 hover:bg-gray-100 cursor-pointer"> <input type="checkbox" class="mr-2 filter-checkbox" data-filter="rating" data-param="is2Star" data-value="true"> <span>⭐⭐</span> </label> <label class="flex items-center px-4 py-2 hover:bg-gray-100 cursor-pointer"> <input type="checkbox" class="mr-2 filter-checkbox" data-filter="rating" data-param="is1Star" data-value="true"> <span>⭐</span> </label> </div> </div> </div> </div> </div> </div> <div class="flex flex-wrap gap-2 text-sm"> <!-- Sort Select --> <div class="relative w-[150px]"> <select id="sortFilter" name="sort" class="w-full appearance-none border border-gray-300 rounded-lg px-4 py-2 bg-white pr-10 cursor-pointer focus:outline-none focus:ring-1 focus:ring-[#7A4397] truncate"> <option value="" class="truncate">Urutkan</option> <option value="paling_baru">Paling Baru</option> <option value="paling_lama">Paling Lama</option> <option value="tertinggi">Rating Tertinggi</option> <option value="terendah">Rating Terendah</option> </select> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> <i data-feather="chevron-down" class="w-4 h-4"></i> </div> </div> </div> </div> <!-- Search --> <div class="w-full sm:flex-1 sm:min-w-[300px]"> <div class="relative"> <input id="searchInput" type="text" placeholder="Cari berdasarkan nama produk atau nama pelanggan" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-[#7A4397]"> <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> </svg> </div> </div> </div> </div> <!-- Selected Filters Display --> <div id="selectedFilters" class="mt-4 flex flex-wrap gap-2 items-center"> <span id="resetFilters" class="text-[#7A4397] text-sm mr-2 font-semibold cursor-pointer hidden">Reset Filter</span> </div> <div id="reviews-container" class="mt-4"></div> <div id="loading-more" class="hidden flex justify-center items-center p-4"> <div class="animate-spin rounded-full h-6 w-6 border-b-2 border-green-600"></div> <span class="ml-2 text-gray-600">Memuat data...</span> </div> </main> <script> let currentPage = 1; let hasMoreData = true; let isFetching = false; let allReviews = []; let debounceTimer; let searchDebounceTimer; // Filter const filterBtn = document.querySelector('#filterDropdown button'); const filterMenu = document.querySelector('#filterMenu'); const filterText = document.getElementById("filterText"); const selectedFiltersContainer = document.getElementById("selectedFilters"); const filterCheckboxes = document.querySelectorAll('input[type="checkbox"]'); // Date Filter const dateFilterDropdown = document.getElementById('dateFilterDropdown'); const dateFilterButton = dateFilterDropdown.querySelector('button'); const dateFilterMenu = document.getElementById('dateFilterMenu'); const dateFilterText = document.getElementById('dateFilterText'); const dateOptions = document.querySelectorAll('.date-option'); const customDateOption = document.getElementById('customDateOption'); const customDateIndicator = document.getElementById('customDateIndicator'); const calendarContainer = document.getElementById('calendarContainer'); const predefinedDateInfo = document.getElementById('predefinedDateInfo'); const dateDisplays = document.querySelectorAll('#dateDisplays > div'); // Current active option let activeOption = 'today'; // Initialize flatpickr calendar let flatpickrInstance = null; const BASE_URL_IMAGE = "<?= base_url('uploads/product') ?>"; const BASE_URL_PRODUCT = "<?= base_url('product') ?>"; const BASE_URL_FETCH_DATA = "<?= base_url('admin/reviews/get_reviews') ?>"; const BASE_URL = "<?= base_url('admin/reviews') ?>"; const ADD_REVIEW_URL = "<?= base_url('admin/customer-services/review/add') ?>"; const CSRF_TOKEN = "<?= $this->security->get_csrf_token_name(); ?>"; const CSRF_HASH = "<?= $this->security->get_csrf_hash(); ?>"; const BASE_URL_DELETE = "<?= base_url('admin/reviews/delete_review/') ?>"; const BASE_URL_EDIT = "<?= base_url('admin/customer-services/review/edit/') ?>"; document.addEventListener("DOMContentLoaded", function() { // Sort sortFilter.addEventListener('change', () => { updateURLAndFetch(); updateSelectedFilters(); }); // Filter Menu filterBtn.addEventListener('click', () => { filterMenu.classList.toggle('hidden'); }); // Close dropdown when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('#filterDropdown')) { filterMenu.classList.add('hidden'); } }); // Handle checkbox changes filterCheckboxes.forEach(checkbox => { checkbox.addEventListener('change', () => { updateURLAndFetch(); updateFilterText(); updateSelectedFilters(); }); }); document.getElementById("resetFilters").addEventListener("click", resetAllFilters); // Tambahkan event listener untuk search input document.getElementById('searchInput').addEventListener('input', function(e) { clearTimeout(searchDebounceTimer); searchDebounceTimer = setTimeout(() => { searchCustomers(e.target.value); }, 250); }); // Date Filter // Toggle dropdown dateFilterButton.addEventListener('click', function() { dateFilterMenu.classList.toggle('hidden'); }); // Close dropdown when clicking outside document.addEventListener('click', function(event) { if (!dateFilterDropdown.contains(event.target)) { dateFilterMenu.classList.add('hidden'); } }); // Handle date option selection dateOptions.forEach(option => { option.addEventListener('click', function() { const optionType = this.getAttribute('data-option'); // Set this as the active option activeOption = optionType; // Update visual indicators dateOptions.forEach(opt => { opt.classList.remove('bg-gray-100'); }); this.classList.add('bg-gray-100'); // Hide all date displays first dateDisplays.forEach(display => { display.classList.add('hidden'); }); if (optionType === 'customDate') { // Show calendar for custom date predefinedDateInfo.classList.add('hidden'); calendarContainer.classList.remove('hidden'); customDateIndicator.classList.remove('hidden'); // Initialize calendar if not already initializeCalendar(); } else { // Show the relevant date display predefinedDateInfo.classList.remove('hidden'); calendarContainer.classList.add('hidden'); customDateIndicator.classList.add('hidden'); // Show corresponding date info const correspondingDisplay = document.getElementById(`${optionType}DateDisplay`); if (correspondingDisplay) { correspondingDisplay.classList.remove('hidden'); } // Get date text to update the main button const dateInfo = document.getElementById(`${optionType}Date`); if (dateInfo) { const optionText = this.querySelector('div').innerText; dateFilterText.innerText = `${optionText} (${dateInfo.innerText})`; } else { dateFilterText.innerText = this.querySelector('div').innerText; } // Close dropdown after a slight delay to show the selection setTimeout(() => { dateFilterMenu.classList.add('hidden'); }, 300); // Tambahkan param date_filter ke URL dan fetch data updateURLAndFetch('date_filter', optionType); } }); }); function initializeCalendar() { if (flatpickrInstance === null && typeof flatpickr !== 'undefined') { // Create an input element for flatpickr if (!document.getElementById('dateRangePicker')) { const input = document.createElement('input'); input.type = 'text'; input.id = 'dateRangePicker'; input.className = 'hidden'; document.getElementById('flatpickrContainer').appendChild(input); } // Initialize flatpickr flatpickrInstance = flatpickr("#dateRangePicker", { mode: "range", inline: true, dateFormat: "d M Y", maxDate: "today", locale: { rangeSeparator: " - ", firstDayOfWeek: 1, weekdays: { shorthand: ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'], longhand: ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'] }, months: { shorthand: ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'], longhand: ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'] } }, onChange: function(selectedDates, dateStr) { if (selectedDates.length === 2) { document.getElementById('customDateMessage').innerText = dateStr; } else if (selectedDates.length === 1) { document.getElementById('customDateMessage').innerText = 'Pilih tanggal akhir'; } else { document.getElementById('customDateMessage').innerText = 'Kamu belum pilih tanggal'; } }, onClose: function(selectedDates, dateStr) { if (selectedDates.length === 2) { // Update the custom date message document.getElementById('customDateMessage').innerText = dateStr; // Update the main filter text dateFilterText.innerText = `Pilih Tanggal (${dateStr})`; // Close the dropdown dateFilterMenu.classList.add('hidden'); // Simpan tanggal yang dipilih untuk digunakan dalam updateURLAndFetch const startDate = formatDateForApi(selectedDates[0]); const endDate = formatDateForApi(selectedDates[1]); // Update URL and fetch data dengan custom date updateURLAndFetch('date_filter', 'custom', { start_date: startDate, end_date: endDate }); } } }); // Customize calendar appearance const calendarElement = document.querySelector('.flatpickr-calendar'); if (calendarElement) { calendarElement.classList.add('shadow-none', 'border-0'); } } } // Helper function to format date for API function formatDateForApi(date) { return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`; } // Update date values on page load updateDateLabels(); // Set today as active by default const todayOption = document.querySelector('[data-option="today"]'); if (todayOption) { todayOption.classList.add('bg-gray-100'); document.getElementById('todayDateDisplay').classList.remove('hidden'); } // Fetch data customer infinite scroll window.addEventListener('scroll', () => { if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) { fetchReviews(new URLSearchParams(window.location.search), true); } }); // Add Customer Handler const addReviewButton = document.querySelector('button[id="addReview"]'); addReviewButton.addEventListener('click', function(event) { event.preventDefault(); window.open(`${ADD_REVIEW_URL}`, '_blank'); }); // Remove Filter window.removeFilter = function(param) { if (param === "sort") { sortFilter.value = ""; } else { filterCheckboxes.forEach(checkbox => { if (checkbox.dataset.param === param) { checkbox.checked = false; } }); } updateSelectedFilters(); updateURLAndFetch(); updateFilterText(); }; feather.replace(); updateURLAndFetch(); }); function updateDateLabels() { const now = new Date(); const today = new Date(now); const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1); // Update the month title document.getElementById('currentMonthTitle').innerText = today.toLocaleDateString('id-ID', { month: 'long', year: 'numeric' }); // Format function for dates function formatDate(date) { return date.toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }).replace('.', ''); } function formatDateRange(start, end) { return `${formatDate(start)} - ${formatDate(end)}`; } // Today with real time const hour = now.getHours(); const minute = now.getMinutes(); document.getElementById('todayDate').innerText = `${formatDate(today)} (00:00 - ${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')})`; // Yesterday document.getElementById('yesterdayDate').innerText = formatDate(yesterday); // Last 7 days const last7Start = new Date(now); last7Start.setDate(last7Start.getDate() - 7); const last7End = new Date(yesterday); document.getElementById('last7daysDate').innerText = formatDateRange(last7Start, last7End); // Last 30 days const last30Start = new Date(now); last30Start.setDate(last30Start.getDate() - 30); const last30End = new Date(yesterday); document.getElementById('last30daysDate').innerText = formatDateRange(last30Start, last30End); // This month const thisMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); const thisMonthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0); document.getElementById('thisMonthDate').innerText = formatDateRange(thisMonthStart, thisMonthEnd); } // Initialize the scroll listener function initInfiniteScroll() { window.addEventListener('scroll', function() { // Check if we're near the bottom of the page if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) { // If we're within 500px of the bottom, load more data if (!isFetching && hasMoreData) { // Get current URL parameters const params = new URLSearchParams(window.location.search); fetchReviews(params, true); // true means append instead of replace } } }); } function resetAllFilters() { sortFilter.value = ""; // Reset checkbox filter filterCheckboxes.forEach(checkbox => { checkbox.checked = false; }); // Reset filter tanggal document.getElementById("dateFilterText").innerText = "Hari ini (" + new Date().toLocaleDateString('id-ID') + ")"; // Hapus parameter date_filter, start_date, dan end_date dari URL const urlParams = new URLSearchParams(window.location.search); urlParams.delete("date_filter"); urlParams.delete("start_date"); urlParams.delete("end_date"); window.history.replaceState({}, "", window.location.pathname + "?" + urlParams.toString()); // Update tampilan filter yang dipilih updateSelectedFilters(); updateURLAndFetch(); updateFilterText(); } function updateFilterText() { const checkedCount = document.querySelectorAll(".filter-checkbox:checked").length; filterText.textContent = checkedCount > 0 ? `${checkedCount} Filter Terpilih` : "Filter"; } function updateSelectedFilters() { const resetFilters = document.getElementById("resetFilters"); // Menyimpan elemen resetFilters di dalam selectedFiltersContainer selectedFiltersContainer.innerHTML = ""; let hasFilters = false; selectedFiltersContainer.appendChild(resetFilters); // Menambahkan filter 'sort' const selectedSort = sortFilter.value; if (selectedSort) { hasFilters = true; const sortTag = document.createElement("div"); sortTag.className = "flex items-center border border-[#7A4397] rounded-lg px-3 py-1 bg-gray-100 text-sm"; sortTag.innerHTML = ` ${sortFilter.options[sortFilter.selectedIndex].text} <button class="ml-2 text-gray-500 hover:text-gray-700" onclick="removeFilter('sort')">×</button> `; selectedFiltersContainer.appendChild(sortTag); } // Menambahkan filter dari checkbox filterCheckboxes.forEach(checkbox => { if (checkbox.checked) { hasFilters = true; const filterTag = document.createElement("div"); filterTag.className = "flex items-center border border-[#7A4397] rounded-lg px-3 py-1 bg-gray-100 text-sm"; filterTag.innerHTML = ` ${checkbox.nextElementSibling.innerText} <button class="ml-2 text-gray-500 hover:text-gray-700" onclick="removeFilter('${checkbox.dataset.param}')">×</button> `; selectedFiltersContainer.appendChild(filterTag); } }); // Menampilkan atau menyembunyikan tombol reset filter if (hasFilters) { resetFilters.classList.remove("hidden"); } else { resetFilters.classList.add("hidden"); } } function updateURLAndFetch(paramName, value, extraParams = null) { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { const params = new URLSearchParams(window.location.search); // Perbarui atau hapus parameter yang ditentukan if (paramName) { if (value !== null && value !== undefined) { params.set(paramName, value); } else { params.delete(paramName); } } // Handle parameter date if (paramName === 'date_filter') { // Jika date_filter adalah custom, tambahkan extraParams if (value === 'custom' && extraParams) { for (const [key, val] of Object.entries(extraParams)) { params.set(key, val); } } else if (value !== 'custom') { // Hapus parameter start_date dan end_date jika bukan custom params.delete('start_date'); params.delete('end_date'); } } // Set parameter sort const selectedSort = document.getElementById('sortFilter').value; if (selectedSort) { params.set('sort', selectedSort); } else { params.delete('sort'); } // Collect all checkbox state first const checkboxParamMap = {}; // Inisialisasi semua parameter filter sebagai null document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { const param = checkbox.dataset.param; if (param && !checkboxParamMap[param]) { checkboxParamMap[param] = null; } }); // Setel nilai untuk checkbox yang dicentang document.querySelectorAll('input[type="checkbox"]:checked').forEach(checkbox => { const param = checkbox.dataset.param; const value = checkbox.dataset.value; if (param && value) { checkboxParamMap[param] = value; } }); // Terapkan parameter dari checkboxes for (const [param, value] of Object.entries(checkboxParamMap)) { if (value !== null) { params.set(param, value); } else { params.delete(param); } } // Reset pagination currentPage = 1; hasMoreData = true; allReviews = []; // Update URL const newUrl = `${window.location.pathname}?${params.toString()}`; history.pushState(null, '', newUrl); // Fetch customers with new parameters (replace mode) fetchReviews(params, false); }, 300); } function fetchReviews(params = new URLSearchParams(), append = false) { if (isFetching) return; // Jika tidak dalam mode append dan tidak ada data lagi, reset hasMoreData if (!append) { hasMoreData = true; } else if (!hasMoreData) { return; } isFetching = true; const baseUrl = BASE_URL_FETCH_DATA; params.set('page', currentPage); params.set('limit', 10); const url = `${baseUrl}?${params.toString()}`; const progressBar = document.getElementById('progressBar'); progressBar.classList.remove('hidden'); fetch(url) .then(res => res.json()) .then(data => { progressBar.classList.add('hidden'); if (data.length === 0) { hasMoreData = false; if (!append) { const container = document.getElementById('reviews-container'); container.innerHTML = '<div class="flex justify-center items-center p-8 text-gray-500">Tidak ada review yang ditemukan</div>'; } } else { if (!append) { allReviews = data; } else { allReviews = [...allReviews, ...data]; } console.log(allReviews); renderReviews(data, append); currentPage++; } isFetching = false; }) .catch(error => { progressBar.classList.add('hidden'); console.error('Error fetching reviews:', error); isFetching = false; // Tampilkan pesan error const container = document.getElementById('reviews-container'); container.innerHTML = '<div class="flex justify-center items-center p-8 text-red-500">Terjadi kesalahan saat mengambil data. Silakan coba lagi.</div>'; }); } function formatNumber(number) { return new Intl.NumberFormat('id-ID').format(number); } function renderReviews(reviews, append = false) { const container = document.getElementById('reviews-container'); const loadingMore = document.getElementById('loading-more'); if (!append) { container.innerHTML = ''; // Clear existing content } if (!reviews || reviews.length === 0) { if (!append) { container.innerHTML = '<div class="flex justify-center items-center p-8 text-gray-500">Tidak ada review yang ditemukan</div>'; } loadingMore.classList.add('hidden'); return; } // Jika tidak append, buat struktur tabel baru if (!append) { const tableWrapper = document.createElement('div'); tableWrapper.className = 'overflow-x-auto rounded-lg shadow max-w-full'; tableWrapper.innerHTML = ` <table class="w-full divide-y divide-gray-200 table-fixed"> <thead class="bg-gray-50"> <tr> <th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-[12%]">Pelanggan</th> <th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-[12%]">Produk</th> <th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-[26%]">Review</th> <th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-[10%]">Rating</th> <th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-[12%]">Tanggal Review</th> <th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-[8%]">Status</th> <th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-[15%]">Aksi</th> </tr> </thead> <tbody id="reviews-table-body" class="bg-white divide-y divide-gray-200"> </tbody> </table> `; container.appendChild(tableWrapper); } const tableBody = document.getElementById('reviews-table-body') || container.querySelector('tbody'); reviews.forEach(review => { const reviewDate = new Date(review.review_date); const formattedDate = formatDate(reviewDate); const ratingStars = generateStarRating(review.rating); // Buat inisial untuk avatar const initial = review.display_name ? review.display_name.charAt(0).toUpperCase() : (review.customer_name !== "Guest" ? review.customer_name.charAt(0).toUpperCase() : "G"); const row = document.createElement('tr'); row.className = 'hover:bg-gray-50 transition-colors'; row.innerHTML = ` <td class="px-3 py-2"> <div class="flex items-center"> <div class="ml-2 overflow-hidden"> <div class="text-xs font-medium text-[#333] truncate">Display: ${review.display_name}</div> <div class="text-xs text-gray-500 truncate">Nama: ${review.customer_name}</div> <div class="text-xs text-gray-500 truncate">Email: ${review.customer_email}</div> </div> </div> </td> <td class="px-3 py-2"> <span class="text-xs font-medium line-clamp-2">${review.product_title}</span> </td> <td class="px-3 py-2"> <div class="text-xs font-medium text-[#333] mb-1 truncate">Subject: ${review.subject}</div> <div class="text-xs text-gray-600 line-clamp-2">${review.review}</div> </td> <td class="px-3 py-2"> <div class="flex items-center">${ratingStars}</div> </td> <td class="px-3 py-2"> <div class="text-xs text-[#333]">${formattedDate}</div> </td> <td class="px-3 py-2"> <div class="text-xs text-[#333]">${review.status}</div> </td> <td class="px-3 py-2 cursor-pointer"> <div class="flex space-x-1 items-center"> <button data-id="${review.id}" class="review-edit-btn inline-flex items-center px-2 py-1 border border-gray-300 rounded-md text-xs text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#7A4397]"> <i data-feather="edit" class="w-3 h-3 mr-1"></i> Ubah </button> <button data-id="${review.id}" class="review-delete-btn inline-flex items-center px-2 py-1 border border-red-300 rounded-md text-xs text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"> <i data-feather="trash" class="w-3 h-3 mr-1"></i> Hapus </button> </div> </td> `; tableBody.appendChild(row); }); loadingMore.classList.add('hidden'); // Inisialisasi Feather Icons feather.replace({ width: 16, height: 16 }); } document.addEventListener('click', function(event) { // Tombol delete if (event.target.closest('.review-delete-btn')) { const deleteButton = event.target.closest('.review-delete-btn'); const reviewId = deleteButton.dataset.id; if (!confirm("Beneran mau di hapus?")) return; // Gunakan variabel global yang sudah di-define di view const formData = new FormData(); formData.append(CSRF_TOKEN, CSRF_HASH); // Variabel global untuk CSRF fetch(BASE_URL_DELETE + reviewId, { // Variabel global untuk BASE_URL method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' // Mark as AJAX request }, body: formData }) .then(response => { if (!response.ok) { throw new Error('Server returned ' + response.status); } return response.json(); }) .then(data => { if (data.status) { // Success notyf.success(data.message); deleteButton.closest('tr').remove(); // Remove table row after success } else { // Error from server notyf.error("Error: " + data.message); } }) .catch(error => { notyf.error("Terjadi kesalahan: " + error.message); console.error(error); }); } if (event.target.closest('.review-edit-btn')) { const editButton = event.target.closest('.review-edit-btn'); const reviewId = editButton.dataset.id; const editUrl = BASE_URL_EDIT + reviewId; // Redirect to the edit page window.open(editUrl, '_blank'); } }); // Fungsi untuk memformat tanggal function formatDate(date) { const months = [ 'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember' ]; const day = date.getDate().toString().padStart(2, '0'); const month = months[date.getMonth()]; const year = date.getFullYear(); return `${day} ${month} ${year}`; } // Fungsi untuk membuat rating bintang function generateStarRating(rating) { const starCount = parseInt(rating); let starsHtml = ''; for (let i = 1; i <= 5; i++) { if (i <= starCount) { // Full star starsHtml += '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="#FFD700" stroke="#FFD700" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>'; } else { // Empty star starsHtml += '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#D1D5DB" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>'; } } return starsHtml; } function searchCustomers(searchTerm) { // Reset data pencarian dan pagination currentPage = 1; hasMoreData = true; allReviews = []; // Ambil parameter URL saat ini const params = new URLSearchParams(window.location.search); // Tambahkan parameter pencarian atau hapus jika kosong if (searchTerm && searchTerm.trim() !== '') { params.set('search', searchTerm.trim()); } else { params.delete('search'); } // Update URL const newUrl = `${window.location.pathname}?${params.toString()}`; history.pushState(null, '', newUrl); // Fetch dengan parameter pencarian fetchReviews(params, false); } </script>