// Next.js page to show all locations in the database with a link to the location page import { useSession } from "next-auth/react"; import { useEffect, useState, useRef, use } from "react"; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker'; import dayjs from 'dayjs'; // import { getSession } from 'next-auth/client' // import { NextAuth } from 'next-auth/client' import { Publisher, UserRole } from "@prisma/client"; import Layout from "../../../components/layout"; import PublisherCard from "../../../components/publisher/PublisherCard"; import axiosInstance from "../../../src/axiosSecure"; import axiosServer from '../../../src/axiosServer'; const common = require("../../../src/helpers/common"); import toast from "react-hot-toast"; import { levenshteinEditDistance } from "levenshtein-edit-distance"; import ProtectedRoute from '../../../components/protectedRoute'; import ConfirmationModal from '../../../components/ConfirmationModal'; import { relative } from "path"; import { set } from "lodash"; import { utils, write } from 'xlsx'; import { saveAs } from 'file-saver'; interface IProps { initialItems: Publisher[]; } function PublishersPage({ publishers = [] }: IProps) { const [shownPubs, setShownPubs] = useState(publishers); const [filter, setFilter] = useState(""); const [showZeroShiftsOnly, setShowZeroShiftsOnly] = useState(false); const [selectedDate, setSelectedDate] = useState(null); const [filterIsImported, setFilterIsImported] = useState({ checked: false, indeterminate: true, }); const [flterNoTraining, setFilterNoTraining] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [isModalOpenDeleteAllVisible, setIsModalOpenDeleteAllVisible] = useState(false); const [isModalOpenDeleteAllAvaillabilities, setIsModalOpenDeleteAllAvaillabilities] = useState(false); const handleDeleteAllVisible = async () => { setIsDeleting(true); for (const publisher of shownPubs) { try { await axiosInstance.delete(`/api/data/publishers/${publisher.id}`); setShownPubs(shownPubs.filter(p => p.id !== publisher.id)); } catch (error) { console.log(JSON.stringify(error)); } } setIsDeleting(false); setIsModalOpenDeleteAllVisible(false); }; const handleDeleteAllAvailabilities = async () => { setIsDeleting(true); try { const today = new Date(); const targetDate = new Date(today.setDate(today.getDate() - 35)); await axiosInstance.post('/api/?action=deleteAllAvailabilities', { date: targetDate }); } catch (error) { console.error(JSON.stringify(error)); // Log the error } setIsDeleting(false); setIsModalOpenDeleteAllAvaillabilities(false); }; useEffect(() => { // const filteredPublishers = publishers.filter((publisher) => { // return publisher.firstName.toLowerCase().includes(filter.toLowerCase()) // || publisher.lastName.toLowerCase().includes(filter.toLowerCase()); // }); //name filter let filteredPublishersByName = publishers .filter((publisher) => { const fullName = publisher.firstName.toLowerCase() + " " + publisher.lastName.toLowerCase(); const distance = levenshteinEditDistance(fullName, filter.toLowerCase()); const lenDiff = Math.max(fullName.length, filter.length) - Math.min(fullName.length, filter.length); let similarity; if (distance === 0) { similarity = 1; // Exact match } else { similarity = 1 - (distance - lenDiff) / distance; } //console.log("distance: " + distance + "; lenDiff: " + lenDiff + " similarity: " + similarity + "; " + fullName + " =? " + filter + "") return similarity >= 0.95; }); // Email filter let filteredPublishersByEmail = publishers.filter(publisher => publisher.email.toLowerCase().includes(filter.toLowerCase()) ); // Combine name and email filters, removing duplicates let filteredPublishers = [...new Set([...filteredPublishersByName, ...filteredPublishersByEmail])]; // Zero shifts (inactive) and date filter if (showZeroShiftsOnly && selectedDate) { filteredPublishers = publishers.filter(publisher => { // If no assignments at all, include in results if (publisher.assignments.length === 0) return true; // Only include publishers who don't have any assignments after the selected date return !publisher.assignments.some(assignment => { const shiftDate = dayjs(assignment.shift.startTime); return shiftDate.isAfter(selectedDate); }); }); } else if (showZeroShiftsOnly) { // If checkbox is checked but no date selected, show publishers with no assignments filteredPublishers = publishers.filter(publisher => publisher.assignments.length === 0); } // trained filter if (flterNoTraining) { filteredPublishers = filteredPublishers.filter(p => p.isTrained === false); } setShownPubs(filteredPublishers); }, [filter, showZeroShiftsOnly, selectedDate, flterNoTraining]); // Separate effect to handle date reset when checkbox is unchecked useEffect(() => { if (!showZeroShiftsOnly) { setSelectedDate(null); } }, [showZeroShiftsOnly]); const renderPublishers = () => { if (shownPubs.length === 0) { return (
); } else { return shownPubs.map((publisher) => ( )); } }; const handleFilterChange = (event: React.ChangeEvent) => { const { name, value, type, checked } = event.target; // setFilter(event.target.value); if (type === 'text') { setFilter(value); } else if (type === 'checkbox') { if (name === 'filterIsImported') { setFilterIsImported({ checked, indeterminate: false }); } if (name === 'filterTrained') { // const nextState = cbFilterTrainingState === false ? null : cbFilterTrainingState === null ? true : false; // setcbFilterTrainingState(nextState); setFilterNoTraining(checked); } } }; const exportPublishers = async () => { try { const response = await axiosInstance.get('/api/?action=exportPublishersExcel', { responseType: 'arraybuffer' }); // Get filename from Content-Disposition header const contentDisposition = response.headers['content-disposition']; let filename = 'publishers.xlsx'; // default fallback if (contentDisposition) { const filenameMatch = contentDisposition.match(/filename=(.*?)(;|$)/); if (filenameMatch) { filename = decodeURI(filenameMatch[1]); } } const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); // Clean up } catch (error) { console.error(JSON.stringify(error)); toast.error("Грешка при експорт на данни"); } } // Add this function to your component const exportFilteredPublishers = () => { try { // Calculate total statistics const formatDateBG = (date) => { if (!date) return ''; return new Date(date).toLocaleDateString('bg-BG', { day: '2-digit', month: '2-digit', year: 'numeric' }); }; const stats = { totalPublishers: shownPubs.length, activePublishers: shownPubs.filter(p => p.isActive).length, trainedPublishers: shownPubs.filter(p => p.isTrained).length, totalAssignments: shownPubs.reduce((sum, p) => sum + p.assignments.length, 0), totalAvailabilities: shownPubs.reduce((sum, p) => sum + p.availabilities.length, 0), subscribedToReminders: shownPubs.filter(p => p.isSubscribedToReminders).length, subscribedToCoverMe: shownPubs.filter(p => p.isSubscribedToCoverMe).length, familyHeads: shownPubs.filter(p => p.familyHeadId).length, avgAssignments: (shownPubs.reduce((sum, p) => sum + p.assignments.length, 0) / shownPubs.length).toFixed(2), avgAvailabilities: (shownPubs.reduce((sum, p) => sum + p.availabilities.length, 0) / shownPubs.length).toFixed(2) }; // Format the publishers data for Excel const publishersData = shownPubs.map(publisher => ({ 'First Name': publisher.firstName, 'Last Name': publisher.lastName, 'Email': publisher.email, 'Phone': publisher.phone || '', 'Active': publisher.isActive ? 'Yes' : 'No', 'Trained': publisher.isTrained ? 'Yes' : 'No', 'Role': publisher.role ? 'Yes' : 'No', 'Last Login': publisher.lastLogin ? formatDateBG(publisher.lastLogin) : 'Never', 'Subscribed to Reminders': publisher.isSubscribedToReminders ? 'Yes' : 'No', 'Subscribed to CoverMe': publisher.isSubscribedToCoverMe ? 'Yes' : 'No', 'Family Head ID': publisher.familyHeadId || '', 'Assignments Count': publisher.assignments.length, 'Availabilities Count': publisher.availabilities.length, 'Last Assignment': publisher.assignments.length > 0 ? formatDateBG(Math.max(...publisher.assignments.map(a => new Date(a.shift.startTime)))) : 'Never', })); // Create workbook const wb = utils.book_new(); // Create publishers worksheet const ws_data = utils.json_to_sheet(publishersData); // Set column widths for publishers sheet const colWidths = [ { wch: 15 }, // First Name { wch: 15 }, // Last Name { wch: 30 }, // Email { wch: 15 }, // Phone { wch: 10 }, // Active { wch: 10 }, // Trained { wch: 10 }, // Role { wch: 15 }, // Last Login { wch: 20 }, // Subscribed to Reminders { wch: 20 }, // Subscribed to CoverMe { wch: 15 }, // Family Head ID { wch: 15 }, // Assignments Count { wch: 15 }, // Availabilities Count { wch: 15 }, // Last Assignment ]; ws_data['!cols'] = colWidths; // Create summary worksheet const summary_data = [ ['Summary Statistics', ''], ['Total Publishers', stats.totalPublishers], ['Active Publishers', stats.activePublishers], ['Trained Publishers', stats.trainedPublishers], ['Total Assignments', stats.totalAssignments], ['Average Assignments per Publisher', stats.avgAssignments], ['Total Availabilities', stats.totalAvailabilities], ['Average Availabilities per Publisher', stats.avgAvailabilities], ['Subscribed to Reminders', stats.subscribedToReminders], ['Subscribed to CoverMe', stats.subscribedToCoverMe], ['Family Heads', stats.familyHeads], ['Export Date', formatDateBG(new Date())], [''], ['Applied Filters:', ''], ['Name Filter', filter || 'None'], ['Zero Shifts Only', showZeroShiftsOnly ? 'Yes' : 'No'], ['No Training', flterNoTraining ? 'Yes' : 'No'], ]; const ws_summary = utils.aoa_to_sheet(summary_data); // Set column widths for summary sheet ws_summary['!cols'] = [ { wch: 30 }, // Label { wch: 15 }, // Value ]; // Add sheets to workbook utils.book_append_sheet(wb, ws_summary, 'Summary'); utils.book_append_sheet(wb, ws_data, 'Publishers'); // Generate buffer const excelBuffer = write(wb, { bookType: 'xlsx', type: 'array' }); // Create blob and save file const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); // Generate filename with current date and filter indication const date = formatDateBG(new Date()).replace(/\./g, '-'); const isFiltered = filter || showZeroShiftsOnly || flterNoTraining; const filename = `publishers-${isFiltered ? 'filtered-' : ''}${date}.xlsx`; saveAs(blob, filename); toast.success("Експортът е успешен"); } catch (error) { console.error('Export error:', error); toast.error("Грешка при експорт на данни"); } }; return (
setIsModalOpenDeleteAllVisible(false)} onConfirm={handleDeleteAllVisible} message="Сигурни ли сте, че искате да изтриете всички показани в момента вестители?" /> setIsModalOpenDeleteAllAvaillabilities(false)} onConfirm={handleDeleteAllAvailabilities} message="Сигурни ли сте, че искате да изтриете предпочитанията на ВСИЧКИ вестители?" /> {/* export by calling excel helper .ExportPublishersToExcel() */} {/*
*/}
{showZeroShiftsOnly && (
след: setSelectedDate(newDate)} slotProps={{ textField: { size: "small", sx: { width: '140px', '& .MuiInputBase-input': { padding: '4px 8px', fontSize: '0.875rem' } } }, mobileWrapper: { sx: { '& .MuiPickersLayout-root': { minWidth: 'unset' } } } }} format="DD.MM.YYYY" className="min-w-[120px]" />
)} {shownPubs.length} от {publishers.length} вестителя
{renderPublishers()}
); } export default PublishersPage; //import {set} from "date-fns"; export const getServerSideProps = async (context) => { // const axios = await axiosServer(context); // //ToDo: refactor all axios calls to use axiosInstance and this URL // const {data: publishers } = await axios.get('/api/data/publishers?select=id,firstName,lastName,email,isActive,isTrained,isImported,assignments.shift.startTime,availabilities.startTime&dev=fromuseefect'); //use prisma instead of axios const prisma = common.getPrismaClient(); let publishers = await prisma.publisher.findMany({ select: { id: true, firstName: true, lastName: true, email: true, isActive: true, isTrained: true, isImported: true, lastLogin: true, phone: true, isSubscribedToReminders: true, isSubscribedToCoverMe: true, familyHeadId: true, role: true, assignments: { select: { shift: { select: { startTime: true, }, }, }, }, availabilities: { select: { startTime: true, }, }, } }); publishers = JSON.parse(JSON.stringify(publishers)); return { props: { publishers, }, }; };