@@ -23,11 +23,16 @@ import {
2323 IconButton ,
2424 Tooltip ,
2525 Snackbar ,
26+ Dialog ,
27+ DialogActions ,
28+ DialogContent ,
29+ DialogContentText ,
30+ DialogTitle ,
2631} from '@mui/material' ;
27- import {
28- Refresh as RefreshIcon ,
29- Download as DownloadIcon ,
30- ContentCopy as ContentCopyIcon ,
32+ import {
33+ Refresh as RefreshIcon ,
34+ Download as DownloadIcon ,
35+ ContentCopy as ContentCopyIcon ,
3136 PlayArrow as TestIcon ,
3237 Inbox as InboxIcon ,
3338 Phone as PhoneIcon ,
@@ -37,6 +42,8 @@ import {
3742 Error as ErrorIcon ,
3843 Warning as WarningIcon ,
3944 Info as InfoIcon ,
45+ Delete as DeleteIcon ,
46+ Visibility as VisibilityIcon ,
4047} from '@mui/icons-material' ;
4148import AdminAPIClient from '../api/client' ;
4249import { useTraits } from '../hooks/useTraits' ;
@@ -56,6 +63,11 @@ function Inbound({ client, docsBase }: InboundProps) {
5663 const [ callbacks , setCallbacks ] = useState < any | null > ( null ) ;
5764 const [ simulating , setSimulating ] = useState ( false ) ;
5865 const [ copySnackbar , setCopySnackbar ] = useState < string > ( '' ) ;
66+ const [ deleteDialogOpen , setDeleteDialogOpen ] = useState ( false ) ;
67+ const [ faxToDelete , setFaxToDelete ] = useState < string | null > ( null ) ;
68+ const [ deleting , setDeleting ] = useState ( false ) ;
69+ const [ previewDialogOpen , setPreviewDialogOpen ] = useState ( false ) ;
70+ const [ previewPdfUrl , setPreviewPdfUrl ] = useState < string | null > ( null ) ;
5971 // Precise help anchors (lightweight resolver for inbound failures)
6072 const base = docsBase || 'https://dmontgomery40.github.io/Faxbot' ;
6173 const anchors : Record < string , string > = {
@@ -103,6 +115,59 @@ function Inbound({ client, docsBase }: InboundProps) {
103115 }
104116 } ;
105117
118+ const previewPdf = async ( id : string ) => {
119+ try {
120+ // Clean up previous URL if exists
121+ if ( previewPdfUrl ) {
122+ URL . revokeObjectURL ( previewPdfUrl ) ;
123+ }
124+ const blob = await client . downloadInboundPdf ( id ) ;
125+ const url = URL . createObjectURL ( blob ) ;
126+ setPreviewPdfUrl ( url ) ;
127+ setPreviewDialogOpen ( true ) ;
128+ } catch ( err ) {
129+ alert ( `Preview failed: ${ err instanceof Error ? err . message : 'Unknown error' } ` ) ;
130+ }
131+ } ;
132+
133+ const handlePreviewClose = ( ) => {
134+ setPreviewDialogOpen ( false ) ;
135+ // Clean up blob URL after a delay
136+ setTimeout ( ( ) => {
137+ if ( previewPdfUrl ) {
138+ URL . revokeObjectURL ( previewPdfUrl ) ;
139+ setPreviewPdfUrl ( null ) ;
140+ }
141+ } , 100 ) ;
142+ } ;
143+
144+ const handleDeleteClick = ( id : string ) => {
145+ setFaxToDelete ( id ) ;
146+ setDeleteDialogOpen ( true ) ;
147+ } ;
148+
149+ const handleDeleteConfirm = async ( ) => {
150+ if ( ! faxToDelete ) return ;
151+
152+ try {
153+ setDeleting ( true ) ;
154+ await client . deleteInbound ( faxToDelete ) ;
155+ setFaxes ( faxes . filter ( f => f . id !== faxToDelete ) ) ;
156+ setCopySnackbar ( 'Fax deleted successfully' ) ;
157+ } catch ( err ) {
158+ setError ( `Delete failed: ${ err instanceof Error ? err . message : 'Unknown error' } ` ) ;
159+ } finally {
160+ setDeleting ( false ) ;
161+ setDeleteDialogOpen ( false ) ;
162+ setFaxToDelete ( null ) ;
163+ }
164+ } ;
165+
166+ const handleDeleteCancel = ( ) => {
167+ setDeleteDialogOpen ( false ) ;
168+ setFaxToDelete ( null ) ;
169+ } ;
170+
106171 useEffect ( ( ) => {
107172 fetchInbound ( ) ;
108173 ( async ( ) => {
@@ -154,10 +219,49 @@ function Inbound({ client, docsBase }: InboundProps) {
154219 if ( ! dateString ) return '-' ;
155220 try {
156221 const date = new Date ( dateString ) ;
157- if ( isSmallMobile ) {
158- return date . toLocaleDateString ( ) ;
222+ const now = new Date ( ) ;
223+ const diffMs = now . getTime ( ) - date . getTime ( ) ;
224+ const diffMins = Math . floor ( diffMs / 60000 ) ;
225+ const diffHours = Math . floor ( diffMs / 3600000 ) ;
226+ const diffDays = Math . floor ( diffMs / 86400000 ) ;
227+
228+ // Format time
229+ const timeStr = date . toLocaleTimeString ( 'en-US' , {
230+ hour : 'numeric' ,
231+ minute : '2-digit' ,
232+ hour12 : true
233+ } ) ;
234+
235+ // Check if it's today
236+ const isToday = date . toDateString ( ) === now . toDateString ( ) ;
237+ if ( isToday ) {
238+ if ( diffMins < 1 ) return 'Just now' ;
239+ if ( diffMins < 60 ) return `${ diffMins } m ago` ;
240+ return `Today at ${ timeStr } ` ;
241+ }
242+
243+ // Check if it's yesterday
244+ const yesterday = new Date ( now ) ;
245+ yesterday . setDate ( yesterday . getDate ( ) - 1 ) ;
246+ if ( date . toDateString ( ) === yesterday . toDateString ( ) ) {
247+ return `Yesterday at ${ timeStr } ` ;
248+ }
249+
250+ // Check if it's this week
251+ if ( diffDays < 7 ) {
252+ const dayName = date . toLocaleDateString ( 'en-US' , { weekday : 'long' } ) ;
253+ return `${ dayName } at ${ timeStr } ` ;
254+ }
255+
256+ // Check if it's this year
257+ if ( date . getFullYear ( ) === now . getFullYear ( ) ) {
258+ const monthDay = date . toLocaleDateString ( 'en-US' , { month : 'short' , day : 'numeric' } ) ;
259+ return `${ monthDay } at ${ timeStr } ` ;
159260 }
160- return date . toLocaleString ( ) ;
261+
262+ // Older dates
263+ const fullDate = date . toLocaleDateString ( 'en-US' , { month : 'short' , day : 'numeric' , year : 'numeric' } ) ;
264+ return `${ fullDate } at ${ timeStr } ` ;
161265 } catch {
162266 return dateString ;
163267 }
@@ -248,15 +352,37 @@ function Inbound({ client, docsBase }: InboundProps) {
248352 </ Stack >
249353
250354 { /* Actions */ }
251- < Button
252- variant = "contained"
253- startIcon = { < DownloadIcon /> }
254- onClick = { ( ) => downloadPdf ( fax . id ) }
255- fullWidth
256- sx = { { borderRadius : 2 } }
257- >
258- Download PDF
259- </ Button >
355+ < Stack spacing = { 1 } >
356+ < Box sx = { { display : 'flex' , gap : 1 } } >
357+ < Button
358+ variant = "contained"
359+ onClick = { ( ) => previewPdf ( fax . id ) }
360+ fullWidth
361+ sx = { { borderRadius : 2 } }
362+ >
363+ View
364+ </ Button >
365+ < Button
366+ variant = "outlined"
367+ startIcon = { < DownloadIcon /> }
368+ onClick = { ( ) => downloadPdf ( fax . id ) }
369+ fullWidth
370+ sx = { { borderRadius : 2 } }
371+ >
372+ Download
373+ </ Button >
374+ </ Box >
375+ < Button
376+ variant = "outlined"
377+ color = "error"
378+ startIcon = { < DeleteIcon /> }
379+ onClick = { ( ) => handleDeleteClick ( fax . id ) }
380+ fullWidth
381+ sx = { { borderRadius : 2 } }
382+ >
383+ Delete
384+ </ Button >
385+ </ Stack >
260386 </ Stack >
261387 </ CardContent >
262388 </ Card >
@@ -598,15 +724,36 @@ same => n,System(curl -s -X POST -H "Content-Type: application/json" -H "X-Inter
598724 </ Typography >
599725 </ TableCell >
600726 < TableCell align = "right" >
601- < Tooltip title = "Download PDF" >
602- < IconButton
603- size = "small"
604- onClick = { ( ) => downloadPdf ( fax . id ) }
605- disabled = { ! fax . id }
606- >
607- < DownloadIcon />
608- </ IconButton >
609- </ Tooltip >
727+ < Box sx = { { display : 'flex' , gap : 0.5 , justifyContent : 'flex-end' } } >
728+ < Tooltip title = "View PDF" >
729+ < IconButton
730+ size = "small"
731+ onClick = { ( ) => previewPdf ( fax . id ) }
732+ disabled = { ! fax . id }
733+ >
734+ < VisibilityIcon />
735+ </ IconButton >
736+ </ Tooltip >
737+ < Tooltip title = "Download PDF" >
738+ < IconButton
739+ size = "small"
740+ onClick = { ( ) => downloadPdf ( fax . id ) }
741+ disabled = { ! fax . id }
742+ >
743+ < DownloadIcon />
744+ </ IconButton >
745+ </ Tooltip >
746+ < Tooltip title = "Delete" >
747+ < IconButton
748+ size = "small"
749+ onClick = { ( ) => handleDeleteClick ( fax . id ) }
750+ disabled = { ! fax . id }
751+ color = "error"
752+ >
753+ < DeleteIcon />
754+ </ IconButton >
755+ </ Tooltip >
756+ </ Box >
610757 </ TableCell >
611758 </ TableRow >
612759 ) ) }
@@ -649,6 +796,69 @@ same => n,System(curl -s -X POST -H "Content-Type: application/json" -H "X-Inter
649796 onClose = { ( ) => setCopySnackbar ( '' ) }
650797 message = { copySnackbar }
651798 />
799+
800+ { /* Delete Confirmation Dialog */ }
801+ < Dialog
802+ open = { deleteDialogOpen }
803+ onClose = { handleDeleteCancel }
804+ >
805+ < DialogTitle > Delete Inbound Fax?</ DialogTitle >
806+ < DialogContent >
807+ < DialogContentText >
808+ Are you sure you want to delete this fax? This will permanently remove the record and associated PDF file. This action cannot be undone.
809+ </ DialogContentText >
810+ </ DialogContent >
811+ < DialogActions >
812+ < Button onClick = { handleDeleteCancel } disabled = { deleting } >
813+ Cancel
814+ </ Button >
815+ < Button onClick = { handleDeleteConfirm } color = "error" variant = "contained" disabled = { deleting } >
816+ { deleting ? 'Deleting...' : 'Delete' }
817+ </ Button >
818+ </ DialogActions >
819+ </ Dialog >
820+
821+ { /* PDF Preview Dialog */ }
822+ < Dialog
823+ open = { previewDialogOpen }
824+ onClose = { handlePreviewClose }
825+ maxWidth = "lg"
826+ fullWidth
827+ PaperProps = { {
828+ sx : {
829+ height : '90vh' ,
830+ maxHeight : '90vh' ,
831+ }
832+ } }
833+ >
834+ < DialogTitle >
835+ Fax Preview
836+ < IconButton
837+ onClick = { handlePreviewClose }
838+ sx = { {
839+ position : 'absolute' ,
840+ right : 8 ,
841+ top : 8 ,
842+ } }
843+ >
844+ < Box component = "span" sx = { { fontSize : '1.5rem' } } > ×</ Box >
845+ </ IconButton >
846+ </ DialogTitle >
847+ < DialogContent sx = { { p : 0 , display : 'flex' , flexDirection : 'column' } } >
848+ { previewPdfUrl && (
849+ < Box
850+ component = "iframe"
851+ src = { previewPdfUrl }
852+ sx = { {
853+ width : '100%' ,
854+ height : '100%' ,
855+ border : 'none' ,
856+ flexGrow : 1 ,
857+ } }
858+ />
859+ ) }
860+ </ DialogContent >
861+ </ Dialog >
652862 </ Box >
653863 ) ;
654864}
0 commit comments