schedule message and qr added
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user