feat: implement attachment browser and viewer modals, enhance chore attachment handling
This commit is contained in:
125
src/views/Modals/Inputs/AttachmentBrowserModal.jsx
Normal file
125
src/views/Modals/Inputs/AttachmentBrowserModal.jsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import { AttachFile, Close, Image } from '@mui/icons-material'
|
||||
import { Box, Button, CircularProgress, List, ListItem, ListItemButton, Typography } from '@mui/joy'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
|
||||
import { GetChoreAttachments } from '../../../utils/Fetcher'
|
||||
import { resolvePhotoURL } from '../../../utils/Helpers'
|
||||
import AttachmentViewerModal from './AttachmentViewerModal'
|
||||
|
||||
const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']
|
||||
|
||||
const isImageFile = fileName => {
|
||||
if (!fileName) return false
|
||||
const ext = fileName.split('.').pop().toLowerCase()
|
||||
return IMAGE_EXTENSIONS.includes(ext)
|
||||
}
|
||||
|
||||
const downloadFile = (url, fileName) => {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = fileName || 'attachment'
|
||||
a.rel = 'noopener'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
|
||||
function AttachmentBrowserModal({ choreId, isOpen, onClose }) {
|
||||
const { ResponsiveModal } = useResponsiveModal()
|
||||
const [attachments, setAttachments] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [viewerConfig, setViewerConfig] = useState({ isOpen: false })
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || !choreId) return
|
||||
setIsLoading(true)
|
||||
GetChoreAttachments(choreId)
|
||||
.then(res => res.json())
|
||||
.then(data => setAttachments(Array.isArray(data) ? data : []))
|
||||
.finally(() => setIsLoading(false))
|
||||
}, [isOpen, choreId])
|
||||
|
||||
const handleClose = () => {
|
||||
setAttachments([])
|
||||
onClose?.()
|
||||
}
|
||||
|
||||
const handleAttachmentClick = attachment => {
|
||||
const url = resolvePhotoURL(attachment.sign)
|
||||
if (isImageFile(attachment.file_name)) {
|
||||
setViewerConfig({
|
||||
isOpen: true,
|
||||
url,
|
||||
fileName: attachment.file_name,
|
||||
onClose: () => setViewerConfig({ isOpen: false }),
|
||||
})
|
||||
} else {
|
||||
downloadFile(url, attachment.file_name)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResponsiveModal
|
||||
open={!!isOpen}
|
||||
onClose={handleClose}
|
||||
title='Attachments'
|
||||
footer={
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
variant='plain'
|
||||
color='neutral'
|
||||
startDecorator={<Close />}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||
<CircularProgress size='md' />
|
||||
</Box>
|
||||
) : attachments.length === 0 ? (
|
||||
<Typography
|
||||
level='body-sm'
|
||||
sx={{ color: 'text.secondary', py: 2, textAlign: 'center' }}
|
||||
>
|
||||
No attachments found.
|
||||
</Typography>
|
||||
) : (
|
||||
<List sx={{ '--ListItem-paddingX': '0px' }}>
|
||||
{attachments.map((attachment, index) => (
|
||||
<ListItem key={index} sx={{ p: 0 }}>
|
||||
<ListItemButton
|
||||
onClick={() => handleAttachmentClick(attachment)}
|
||||
sx={{ borderRadius: 'sm', gap: 1.5, py: 1 }}
|
||||
>
|
||||
{isImageFile(attachment.file_name) ? (
|
||||
<Image fontSize='small' />
|
||||
) : (
|
||||
<AttachFile fontSize='small' />
|
||||
)}
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Typography level='body-sm' noWrap>
|
||||
{attachment.file_name || `File ${index + 1}`}
|
||||
</Typography>
|
||||
{attachment.size_bytes > 0 && (
|
||||
<Typography level='body-xs' sx={{ color: 'text.tertiary' }}>
|
||||
{(attachment.size_bytes / 1024).toFixed(1)} KB
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</ResponsiveModal>
|
||||
<AttachmentViewerModal config={viewerConfig} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AttachmentBrowserModal
|
||||
Reference in New Issue
Block a user