234 lines
7.1 KiB
React
234 lines
7.1 KiB
React
import { useState } from 'react'
|
|
import {
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Button,
|
|
TextField,
|
|
Box,
|
|
Typography,
|
|
Alert,
|
|
IconButton,
|
|
Divider,
|
|
CircularProgress,
|
|
} from '@mui/material'
|
|
import EditIcon from '@mui/icons-material/Edit'
|
|
import DeleteIcon from '@mui/icons-material/Delete'
|
|
import { LocalizationProvider, DateTimePicker } from '@mui/x-date-pickers'
|
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
|
import dayjs from 'dayjs'
|
|
import {
|
|
useScheduledMessages,
|
|
useCreateScheduledMessage,
|
|
useUpdateScheduledMessage,
|
|
useDeleteScheduledMessage,
|
|
} from './api'
|
|
|
|
function formatDate(d) {
|
|
try {
|
|
return new Date(d).toLocaleString()
|
|
} catch {
|
|
return d
|
|
}
|
|
}
|
|
|
|
export default function ScheduleMessageModal({ session, onClose }) {
|
|
const [message, setMessage] = useState('')
|
|
const [scheduledAt, setScheduledAt] = useState(() => dayjs().add(1, 'hour'))
|
|
const [editingId, setEditingId] = useState(null)
|
|
const [error, setError] = useState('')
|
|
const [success, setSuccess] = useState('')
|
|
|
|
const { data: scheduledMessages, isLoading } = useScheduledMessages(session?.id)
|
|
const createMutation = useCreateScheduledMessage()
|
|
const updateMutation = useUpdateScheduledMessage()
|
|
const deleteMutation = useDeleteScheduledMessage()
|
|
|
|
const handleClose = () => {
|
|
setMessage('')
|
|
setScheduledAt(dayjs().add(1, 'hour'))
|
|
setEditingId(null)
|
|
setError('')
|
|
setSuccess('')
|
|
onClose()
|
|
}
|
|
|
|
const handleEdit = (msg) => {
|
|
setMessage(msg.message)
|
|
setScheduledAt(dayjs(msg.scheduled_at))
|
|
setEditingId(msg.id)
|
|
setError('')
|
|
setSuccess('')
|
|
}
|
|
|
|
const handleCancelEdit = () => {
|
|
setMessage('')
|
|
setScheduledAt(dayjs().add(1, 'hour'))
|
|
setEditingId(null)
|
|
setError('')
|
|
setSuccess('')
|
|
}
|
|
|
|
const handleSave = async () => {
|
|
setError('')
|
|
setSuccess('')
|
|
if (!message.trim()) {
|
|
setError('El mensaje no puede estar vacío')
|
|
return
|
|
}
|
|
if (!session) return
|
|
const isoDate = scheduledAt.toISOString()
|
|
try {
|
|
if (editingId) {
|
|
await updateMutation.mutateAsync({
|
|
id: editingId,
|
|
sessionId: session.id,
|
|
message: message.trim(),
|
|
scheduledAt: isoDate,
|
|
})
|
|
setSuccess('Mensaje programado actualizado')
|
|
} else {
|
|
await createMutation.mutateAsync({
|
|
sessionId: session.id,
|
|
message: message.trim(),
|
|
scheduledAt: isoDate,
|
|
})
|
|
setSuccess('Mensaje programado creado')
|
|
}
|
|
setMessage('')
|
|
setScheduledAt(dayjs().add(1, 'hour'))
|
|
setEditingId(null)
|
|
} catch (err) {
|
|
setError(err.message)
|
|
}
|
|
}
|
|
|
|
const handleDelete = async (id) => {
|
|
if (!session) return
|
|
if (!window.confirm('¿Eliminar este mensaje programado?')) return
|
|
setError('')
|
|
setSuccess('')
|
|
try {
|
|
await deleteMutation.mutateAsync({ id, sessionId: session.id })
|
|
setSuccess('Mensaje programado eliminado')
|
|
} catch (err) {
|
|
setError(err.message)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={!!session} onClose={handleClose} maxWidth="sm" fullWidth>
|
|
<DialogTitle>
|
|
Programar mensaje: {session?.name || session?.phone || session?.id}
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
|
|
<DateTimePicker
|
|
label="Fecha y hora"
|
|
value={scheduledAt}
|
|
onChange={(v) => setScheduledAt(v)}
|
|
ampm={false}
|
|
slotProps={{
|
|
textField: {
|
|
fullWidth: true,
|
|
size: 'small',
|
|
readOnly: true,
|
|
},
|
|
}}
|
|
disablePast
|
|
/>
|
|
<TextField
|
|
fullWidth
|
|
size="small"
|
|
label="Mensaje"
|
|
multiline
|
|
minRows={3}
|
|
maxRows={6}
|
|
value={message}
|
|
onChange={(e) => setMessage(e.target.value)}
|
|
placeholder="Escribe el mensaje a programar..."
|
|
/>
|
|
{error && <Alert severity="error">{error}</Alert>}
|
|
{success && <Alert severity="success">{success}</Alert>}
|
|
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'flex-end' }}>
|
|
{editingId && (
|
|
<Button onClick={handleCancelEdit} color="inherit">
|
|
Cancelar edición
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleSave}
|
|
disabled={!message.trim() || createMutation.isPending || updateMutation.isPending}
|
|
>
|
|
{createMutation.isPending || updateMutation.isPending ? (
|
|
<CircularProgress size={20} color="inherit" />
|
|
) : editingId ? (
|
|
'Guardar'
|
|
) : (
|
|
'Programar'
|
|
)}
|
|
</Button>
|
|
</Box>
|
|
</Box>
|
|
</LocalizationProvider>
|
|
|
|
<Divider sx={{ my: 2 }} />
|
|
|
|
<Typography variant="subtitle2" gutterBottom>
|
|
Mensajes programados pendientes
|
|
</Typography>
|
|
{isLoading ? (
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
|
|
<CircularProgress size={24} />
|
|
</Box>
|
|
) : scheduledMessages?.length === 0 ? (
|
|
<Typography variant="body2" color="text.secondary">
|
|
No hay mensajes programados pendientes.
|
|
</Typography>
|
|
) : (
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
{scheduledMessages?.map((msg) => (
|
|
<Box
|
|
key={msg.id}
|
|
sx={{
|
|
p: 1.5,
|
|
border: '1px solid',
|
|
borderColor: 'divider',
|
|
borderRadius: 1,
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'flex-start',
|
|
gap: 1,
|
|
}}
|
|
>
|
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
<Typography variant="body2" sx={{ wordBreak: 'break-word' }}>
|
|
{msg.message}
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
{formatDate(msg.scheduled_at)}
|
|
</Typography>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
|
<IconButton size="small" onClick={() => handleEdit(msg)} disabled={deleteMutation.isPending}>
|
|
<EditIcon fontSize="small" />
|
|
</IconButton>
|
|
<IconButton size="small" onClick={() => handleDelete(msg.id)} disabled={deleteMutation.isPending}>
|
|
<DeleteIcon fontSize="small" color="error" />
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
)}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={handleClose}>Cerrar</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
)
|
|
}
|