whoami7 - Manager
:
/
home
/
dataiclx
/
vielorbe.com
/
wp-content
/
plugins
/
suremails
/
src
/
screens
/
logs
/
Upload File:
files >> //home/dataiclx/vielorbe.com/wp-content/plugins/suremails/src/screens/logs/logs.js
// Logs.js import { useState, useEffect, useRef } from '@wordpress/element'; import { X, Eye as EyeIcon, Trash as DeleteIcon, RefreshCw as ResendIcon, Calendar, Search, } from 'lucide-react'; import { toast, Select, Input, Button, Badge, Pagination, DatePicker, Table, } from '@bsf/force-ui'; import { __ } from '@wordpress/i18n'; import EmptyLogs from './empty-logs'; import NoFilteredLogs from './no-filtered-logs'; import EmailLogDrawer from './email-log-drawer'; import LogsSkeleton from './logs-skeleton'; import ConfirmationDialog from '@components/confirmation-dialog/confirmation-dialog'; // Import the ConfirmationDialog component import { fetchLogs, deleteLogs as apiDeleteLogs, resendEmails as apiResendEmails, } from '@api/logs'; // Import API functions import { formatDate, getSelectedDate, getPaginationRange, getStatusLabel, getStatusVariant, get_pending_status, } from '@utils/utils'; import Tooltip from '@components/tooltip/tooltip'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import TruncatedTooltipText from '@components/truncated-tooltip-text'; import Title from '@components/title/title'; const STATUS_FILTERS = [ { value: 'sent', label: __( 'Successful', 'suremails' ) }, { value: 'failed', label: __( 'Failed', 'suremails' ) }, { value: 'pending', label: __( 'In Progress', 'suremails' ) }, { value: 'blocked', label: __( 'Blocked', 'suremails' ) }, ]; const Logs = () => { // State Variables const [ page, setPage ] = useState( 1 ); const [ selectedDates, setSelectedDates ] = useState( { from: null, to: null, } ); const [ filter, setFilter ] = useState( '' ); const [ searchTerm, setSearchTerm ] = useState( '' ); // New search state const [ selectedLog, setSelectedLog ] = useState( null ); const [ isDrawerOpen, setIsDrawerOpen ] = useState( false ); const [ selectedLogs, setSelectedLogs ] = useState( [] ); const [ isDatePickerOpen, setIsDatePickerOpen ] = useState( false ); const logsPerPage = 10; // Dialog state for ConfirmationDialog const [ isDialogOpen, setIsDialogOpen ] = useState( false ); const [ dialogConfig, setDialogConfig ] = useState( { title: '', description: '', onConfirm: null, } ); const containerRef = useRef( null ); const useDebounce = ( value, delay = 500, callback ) => { const [ debouncedValue, setDebouncedValue ] = useState( value ); useEffect( () => { const handler = setTimeout( () => { setDebouncedValue( value ); callback(); }, delay ); return () => clearTimeout( handler ); }, [ value, delay ] ); return debouncedValue; }; /** * Debounced function to set the search term. * This prevents excessive API calls during rapid input. */ const debouncedSearchTerm = useDebounce( searchTerm, 500, () => setPage( 1 ) ); const queryClient = useQueryClient(); // Replace fetchAndSetLogs with React Query const { data: logsData, isLoading, error, } = useQuery( { queryKey: [ 'logs', page, selectedDates.from, selectedDates.to, filter, debouncedSearchTerm, ], queryFn: () => fetchLogs( { pageNumber: page, startDate: selectedDates.from, endDate: selectedDates.to, filter, searchTerm: debouncedSearchTerm, logsPerPage, } ), keepPreviousData: true, // Preserve previous page data while loading next page refetchInterval: 100000, // Refetch every 10 minutes refetchOnReconnect: true, } ); // Update the logs and totalPages from React Query data const logs = logsData?.logs || []; const totalPages = logsData?.total_count ? Math.ceil( logsData.total_count / logsPerPage ) : 1; // Add mutations for delete and resend operations const deleteMutation = useMutation( { mutationFn: apiDeleteLogs, onSuccess: ( response, variables ) => { if ( response.success ) { toast.success( __( 'Logs deleted successfully.', 'suremails' ) ); // Invalidate and refetch logs queryClient.invalidateQueries( { queryKey: [ 'logs' ], } ); // Refetch dashboard data queryClient.refetchQueries( { queryKey: [ 'dashboard-data' ], exact: true, } ); // Return to the previous page if required. if ( logsData.logs.length === variables.length && logsData.logs.length < logsPerPage && page > 1 ) { setPage( ( prev ) => Math.max( prev - 1, 1 ) ); } } }, onError: ( deleteError ) => { toast.error( __( 'Failed to delete logs.', 'suremails' ), { description: deleteError.message || __( 'There was an issue deleting logs.', 'suremails' ), } ); }, onSettled: () => { setIsDialogOpen( false ); setSelectedLogs( [] ); }, } ); const resendMutation = useMutation( { mutationFn: apiResendEmails, onSuccess: ( response ) => { if ( response.success ) { toast.success( __( 'Email(s) resent successfully.', 'suremails' ) ); // Invalidate and refetch logs queryClient.invalidateQueries( { queryKey: [ 'logs' ], } ); // Refetch dashboard data queryClient.refetchQueries( { queryKey: [ 'dashboard-data' ], exact: true, } ); } }, onError: ( resendError ) => { toast.error( __( 'Failed to resend the email(s).', 'suremails' ), { description: resendError.message || __( 'There was an issue resending emails.', 'suremails' ), } ); }, onSettled: () => { queryClient.invalidateQueries( { queryKey: [ 'logs' ], } ); setIsDialogOpen( false ); setSelectedLogs( [] ); }, } ); // Update the confirmation handlers to use mutations const confirmDeleteLogs = async ( logIds ) => { await deleteMutation.mutateAsync( logIds ); }; const confirmResendEmails = async ( logIds ) => { await resendMutation.mutateAsync( logIds ); }; // Handle error from React Query if ( error ) { toast.error( __( 'Failed to fetch logs.', 'suremails' ), { description: error.message || __( 'There was an issue fetching logs.', 'suremails' ), } ); } useEffect( () => { function handleClickOutside( event ) { if ( isDatePickerOpen && containerRef.current && ! containerRef.current.contains( event.target ) ) { setIsDatePickerOpen( false ); } } // Bind the event listener document.addEventListener( 'mousedown', handleClickOutside ); return () => { // Unbind the event listener on cleanup document.removeEventListener( 'mousedown', handleClickOutside ); }; }, [ isDatePickerOpen ] ); const handleViewDetails = ( log ) => { setSelectedLog( log ); setIsDrawerOpen( true ); }; const handleCloseDrawer = () => { setIsDrawerOpen( false ); setSelectedLog( null ); }; const handleSelectLog = ( logId ) => { setSelectedLogs( ( prevSelected ) => prevSelected.includes( logId ) ? prevSelected.filter( ( id ) => id !== logId ) : [ ...prevSelected, logId ] ); }; const handleSelectAll = () => { setSelectedLogs( selectedLogs.length === logs.length ? [] : logs.map( ( log ) => log.id ) ); }; // Determine if EmptyLogs condition is met const isEmptyLogs = ! isLoading && logs.length === 0 && ! selectedDates.from && ! selectedDates.to && ! filter && ! debouncedSearchTerm; // Include searchTerm // Conditional Rendering: If EmptyLogs condition is true, render only EmptyLogs if ( isEmptyLogs ) { return ( <div className="min-h-screen p-6 overflow-hidden bg-background-secondary"> <EmptyLogs /> </div> ); } // Define the handleResendSuccess callback const handleResendSuccess = () => { setIsDrawerOpen( false ); queryClient.invalidateQueries( { queryKey: [ 'logs' ], } ); }; // Determine the confirm button text based on the dialog title const getConfirmButtonText = () => { const titleLower = dialogConfig.title.toLowerCase(); if ( titleLower.includes( 'resend' ) ) { return __( 'Resend', 'suremails' ); } if ( titleLower.includes( 'deletion' ) ) { return __( 'Delete', 'suremails' ); } return __( 'Confirm', 'suremails' ); }; // Handler Functions for Pagination const handlePageChange = ( newPage ) => { setPage( newPage ); }; /** * Determine if the Resend button should be disabled based on the selected logs. * The button should be disabled if any of the selected logs are in pending status. */ const isResendDisabled = selectedLogs.some( ( id ) => { const log = logs.find( ( logItem ) => logItem.id === id ); return log && get_pending_status( log.status ); } ); // Conditional Rendering: Determine what to display based on loading and logs let content; if ( isLoading ) { content = <LogsSkeleton />; } else if ( logs.length === 0 && ( selectedDates.from || selectedDates.to || filter || debouncedSearchTerm ) ) { content = ( <NoFilteredLogs startDate={ selectedDates.from } endDate={ selectedDates.to } filter={ filter } searchTerm={ debouncedSearchTerm } // Pass searchTerm if needed setSelectedDates={ setSelectedDates } setFilter={ setFilter } setPage={ setPage } /> ); } else { content = ( <Table className="bg-background-primary" checkboxSelection> <Table.Head className="bg-background-secondary" onChangeSelection={ handleSelectAll } indeterminate={ selectedLogs.length > 0 && selectedLogs.length < logs.length } selected={ selectedLogs?.length > 0 } > <Table.HeadCell> { __( 'Subject', 'suremails' ) } </Table.HeadCell> <Table.HeadCell className="w-1/8"> { __( 'Status', 'suremails' ) } </Table.HeadCell> <Table.HeadCell className="w-1/6"> { __( 'Email To', 'suremails' ) } </Table.HeadCell> <Table.HeadCell> { __( 'Connection', 'suremails' ) } </Table.HeadCell> <Table.HeadCell className="w-1/6"> { __( 'Date & Time', 'suremails' ) } </Table.HeadCell> <Table.HeadCell className="w-12"> <span className="sr-only"> { __( 'Actions', 'suremails' ) } </span> </Table.HeadCell> </Table.Head> <Table.Body> { logs.map( ( log ) => ( <Table.Row key={ log.id } className="whitespace-nowrap" selected={ selectedLogs.includes( log.id ) } onChangeSelection={ () => handleSelectLog( log.id ) } > <Table.Cell> <TruncatedTooltipText text={ log.subject } className="max-w-[21.875rem]" /> </Table.Cell> <Table.Cell> <Badge className="max-w-fit" label={ getStatusLabel( log.status, log?.response ) } variant={ getStatusVariant( log.status, log?.response ) } size="sm" disableHover /> </Table.Cell> <Table.Cell> <TruncatedTooltipText text={ log.email_to } /> </Table.Cell> <Table.Cell> <Badge className="inline-block" label={ log.connection } variant="blue" size="sm" disableHover /> </Table.Cell> <Table.Cell> { formatDate( log.updated_at, { day: true, month: true, year: true, hour: true, minute: true, hour12: true, } ) } </Table.Cell> <Table.Cell> <div className="flex justify-end gap-2"> <Tooltip content={ __( 'Resend Email', 'suremails' ) } position="top" arrow > <Button className="text-icon-secondary hover:text-icon-primary" size="xs" onClick={ () => handleResend( [ log.id ] ) } icon={ <ResendIcon /> } variant="ghost" aria-label={ __( 'Resend', 'suremails' ) } disabled={ get_pending_status( log?.status ) } /> </Tooltip> <Tooltip content={ __( 'Delete Log', 'suremails' ) } position="top" arrow > <Button className="text-icon-secondary hover:text-icon-primary" size="xs" onClick={ () => handleDelete( [ log.id ] ) } icon={ <DeleteIcon /> } variant="ghost" aria-label={ __( 'Delete', 'suremails' ) } /> </Tooltip> <Tooltip content={ __( 'View Details', 'suremails' ) } position="top" arrow > <Button className="text-icon-secondary hover:text-icon-primary" size="xs" onClick={ () => handleViewDetails( log ) } icon={ <EyeIcon /> } variant="ghost" aria-label={ __( 'View Details', 'suremails' ) } disabled={ log.status === 'pending' } /> </Tooltip> </div> </Table.Cell> </Table.Row> ) ) } </Table.Body> <Table.Footer className="flex items-center justify-between"> { /* Pagination with Page Label */ } { /* Page Label - aligned to the right */ } <div className="text-sm font-normal text-text-secondary whitespace-nowrap"> { __( 'Page', 'suremails' ) } { page }{ ' ' } { __( 'out of', 'suremails' ) } { totalPages } </div> { /* Pagination Controls - aligned to the left */ } <div className="flex items-center space-x-2"> <Pagination size="sm"> <Pagination.Content className="[&>li]:m-0"> <Pagination.Previous tag="button" onClick={ () => setPage( ( prev ) => Math.max( prev - 1, 1 ) ) } disabled={ page === 1 } /> { getPaginationRange( page, totalPages, 1 ).map( ( item, index ) => { if ( item === 'ellipsis' ) { return ( <Pagination.Ellipsis key={ `ellipsis-${ index }` } /> ); } return ( <Pagination.Item key={ item } isActive={ page === item } onClick={ () => handlePageChange( item ) } > { item } </Pagination.Item> ); } ) } <Pagination.Next tag="button" onClick={ () => setPage( ( prev ) => Math.min( prev + 1, totalPages ) ) } disabled={ page === totalPages } /> </Pagination.Content> </Pagination> </div> </Table.Footer> </Table> ); } // Handler Functions for DatePicker const handleDateApply = ( dates ) => { const { from, to } = dates; if ( from && to ) { const fromDate = new Date( from ); const toDate = new Date( to ); if ( fromDate > toDate ) { // Swap the dates to ensure 'from' is earlier than 'to' setSelectedDates( { from: to, to: from } ); } else { setSelectedDates( dates ); } } else { setSelectedDates( { from, to: null } ); } setIsDatePickerOpen( false ); setPage( 1 ); }; const handleDateCancel = () => { setIsDatePickerOpen( false ); }; // Handler Functions for ConfirmationDialog const handleDelete = ( logIds ) => { setDialogConfig( { title: __( 'Confirm Deletion', 'suremails' ), description: __( 'Are you sure you want to delete the selected log(s)? This action cannot be undone.', 'suremails' ), onConfirm: () => confirmDeleteLogs( logIds ), destructiveConfirmButton: true, } ); setIsDialogOpen( true ); }; const handleResend = ( logIds ) => { setDialogConfig( { title: __( 'Confirm Resend', 'suremails' ), description: __( 'Are you sure you want to resend the selected email(s)?', 'suremails' ), onConfirm: () => confirmResendEmails( logIds ), destructiveConfirmButton: false, } ); setIsDialogOpen( true ); }; return ( <> <div className="min-h-screen px-8 py-8 bg-background-secondary"> <div className="p-4 space-y-2 border-0.5 border-solid shadow-sm bg-background-primary rounded-xl border-border-subtle"> <div> { /* Filters or Batch Actions */ } <div className="flex items-center justify-between p-1.25"> <Title title={ __( 'Email Logs', 'suremails' ) } tag="h1" /> <div className="flex space-x-4"> { selectedLogs.length > 0 ? ( // Batch Action Buttons <> <Button variant="primary" icon={ <ResendIcon /> } size="sm" onClick={ () => handleResend( selectedLogs ) } className="font-medium" disabled={ isResendDisabled } > { __( 'Resend Emails', 'suremails' ) } </Button> <Button variant="outline" icon={ <DeleteIcon /> } size="sm" onClick={ () => handleDelete( selectedLogs ) } destructive > { __( 'Delete', 'suremails' ) } </Button> </> ) : ( // Filter Controls <> { /* Conditionally Render Reset Filters Button */ } { ( selectedDates.from || selectedDates.to || filter || searchTerm ) && ( <Button variant="link" size="sm" icon={ <X /> } onClick={ () => { setSelectedDates( { from: null, to: null, } ); setFilter( '' ); setSearchTerm( '' ); setPage( 1 ); } } destructive className="leading-4 no-underline hover:no-underline min-w-fit focus:[box-shadow:none]" > { __( 'Clear Filters', 'suremails' ) } </Button> ) } <Input className="w-52" type="text" size="sm" onChange={ setSearchTerm } value={ searchTerm } placeholder={ __( 'Search…', 'suremails' ) } required prefix={ <Search className="text-icon-secondary" /> } /> { /* Status Filter */ } <Select value={ filter } onChange={ setFilter } size="sm" > <Select.Button className="w-52 h-[2rem] [&_div]:text-xs" placeholder={ __( 'Status', 'suremails' ) } > { ( { value: renderValue } ) => renderValue ? getStatusLabel( renderValue ) : __( 'Select Status', 'suremails' ) } </Select.Button> <Select.Portal id="suremails-root-app" className="z-999999" > <Select.Options> { STATUS_FILTERS.map( ( option ) => ( <Select.Option key={ option.value } value={ option.value } className="text-xs" > { option.label } </Select.Option> ) ) } </Select.Options> </Select.Portal> </Select> { /* Date Range Picker */ } <div className="relative" ref={ containerRef } > <Input type="text" size="sm" value={ getSelectedDate( selectedDates ) } suffix={ <Calendar className="text-icon-secondary" /> } onClick={ () => setIsDatePickerOpen( ! isDatePickerOpen ) } placeholder={ __( 'mm/dd/yyyy - mm/dd/yyyy', 'suremails' ) } className="cursor-pointer w-52" readOnly /> { isDatePickerOpen && ( <div className="absolute right-0 z-10 mt-2 rounded-lg shadow-lg"> <DatePicker applyButtonText={ __( 'Apply', 'suremails' ) } cancelButtonText={ __( 'Cancel', 'suremails' ) } selectionType="range" showOutsideDays={ false } variant="presets" onApply={ handleDateApply } onCancel={ handleDateCancel } selected={ selectedDates } /> </div> ) } </div> </> ) } </div> </div> </div> { /* Conditional Rendering Based on Loading and Logs */ } <div className="overflow-hidden bg-background-primary"> { content } </div> </div> </div> { /* Confirmation Dialog */ } <ConfirmationDialog isOpen={ isDialogOpen } title={ dialogConfig.title } description={ dialogConfig.description } onConfirm={ dialogConfig.onConfirm } onCancel={ () => setIsDialogOpen( false ) } confirmButtonText={ getConfirmButtonText() } cancelButtonText={ __( 'Cancel', 'suremails' ) } destructiveConfirmButton={ !! dialogConfig?.destructiveConfirmButton } /> { /* Email Log Drawer */ } <EmailLogDrawer isOpen={ selectedLog && isDrawerOpen } setOpen={ setIsDrawerOpen } log={ selectedLog } onClose={ handleCloseDrawer } onResendSuccess={ handleResendSuccess } // Pass the callback here /> </> ); }; export default Logs;
Copyright ©2021 || Defacer Indonesia