allow to edit comment
This commit is contained in:
parent
5666191a6b
commit
a7c3a8f5a8
|
|
@ -2398,6 +2398,40 @@ public class Controller : ControllerBase
|
|||
return comment;
|
||||
}
|
||||
|
||||
public class UpdateTicketCommentRequest
|
||||
{
|
||||
public Int64 Id { get; set; }
|
||||
public String Body { get; set; } = "";
|
||||
}
|
||||
|
||||
[HttpPost(nameof(UpdateTicketComment))]
|
||||
public ActionResult<TicketComment> UpdateTicketComment([FromBody] UpdateTicketCommentRequest req, Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
if (user is null || user.UserType != 2) return Unauthorized();
|
||||
|
||||
var comment = Db.TicketComments.FirstOrDefault(c => c.Id == req.Id);
|
||||
if (comment is null) return NotFound();
|
||||
|
||||
if (comment.AuthorType != (Int32)CommentAuthorType.Human) return Forbid();
|
||||
if (comment.AuthorId != user.Id) return Forbid();
|
||||
|
||||
if (String.IsNullOrWhiteSpace(req.Body)) return BadRequest("Body required.");
|
||||
|
||||
comment.Body = req.Body;
|
||||
comment.EditedAt = DateTime.UtcNow;
|
||||
if (!Db.Update(comment)) return StatusCode(500, "Failed to update comment.");
|
||||
|
||||
var ticket = Db.GetTicketById(comment.TicketId);
|
||||
if (ticket is not null)
|
||||
{
|
||||
ticket.UpdatedAt = DateTime.UtcNow;
|
||||
Db.Update(ticket);
|
||||
}
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetTicketDetail))]
|
||||
public ActionResult<Object> GetTicketDetail(Int64 id, Token authToken)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public class TicketComment
|
|||
public Int64? AuthorId { get; set; }
|
||||
public String Body { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? EditedAt { get; set; }
|
||||
|
||||
[Ignore] public List<Int64> MentionedUserIds { get; set; } = new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,4 +72,5 @@ public static partial class Db
|
|||
// Ticket system
|
||||
public static Boolean Update(Ticket ticket) => Update(obj: ticket);
|
||||
public static Boolean Update(TicketAiDiagnosis diagnosis) => Update(obj: diagnosis);
|
||||
public static Boolean Update(TicketComment comment) => Update(obj: comment);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import React, { useContext, useRef, useState } from 'react';
|
||||
import { UserContext } from 'src/contexts/userContext';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
|
|
@ -39,8 +40,13 @@ function CommentThread({
|
|||
adminUsers = []
|
||||
}: CommentThreadProps) {
|
||||
const intl = useIntl();
|
||||
const userCtx = useContext(UserContext);
|
||||
const currentUserId = userCtx?.currentUser?.id;
|
||||
const [body, setBody] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [editingId, setEditingId] = useState<number | null>(null);
|
||||
const [editBody, setEditBody] = useState('');
|
||||
const [savingEdit, setSavingEdit] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
|
@ -125,6 +131,32 @@ function CommentThread({
|
|||
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
||||
);
|
||||
|
||||
const startEdit = (comment: TicketComment) => {
|
||||
setEditingId(comment.id);
|
||||
setEditBody(comment.body);
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
setEditingId(null);
|
||||
setEditBody('');
|
||||
};
|
||||
|
||||
const saveEdit = async (commentId: number) => {
|
||||
if (!editBody.trim()) return;
|
||||
setSavingEdit(true);
|
||||
try {
|
||||
await axiosConfig.post('/UpdateTicketComment', {
|
||||
id: commentId,
|
||||
body: editBody
|
||||
});
|
||||
setEditingId(null);
|
||||
setEditBody('');
|
||||
onCommentAdded();
|
||||
} finally {
|
||||
setSavingEdit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!body.trim() && selectedFiles.length === 0) return;
|
||||
setSubmitting(true);
|
||||
|
|
@ -196,6 +228,8 @@ function CommentThread({
|
|||
|
||||
{sorted.map((comment) => {
|
||||
const isAi = comment.authorType === CommentAuthorType.AiAgent;
|
||||
const canEdit = !isAi && currentUserId != null && comment.authorId === currentUserId;
|
||||
const isEditing = editingId === comment.id;
|
||||
return (
|
||||
<Box
|
||||
key={comment.id}
|
||||
|
|
@ -216,7 +250,7 @@ function CommentThread({
|
|||
</Avatar>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Box
|
||||
sx={{ display: 'flex', alignItems: 'baseline', gap: 1 }}
|
||||
sx={{ display: 'flex', alignItems: 'baseline', gap: 1, flexWrap: 'wrap' }}
|
||||
>
|
||||
<Typography variant="subtitle2">
|
||||
{isAi ? 'AI' : (adminUsers.find(u => u.id === comment.authorId)?.name ?? `User #${comment.authorId ?? '?'}`)}
|
||||
|
|
@ -224,10 +258,59 @@ function CommentThread({
|
|||
<Typography variant="caption" color="text.disabled">
|
||||
{new Date(comment.createdAt).toLocaleString()}
|
||||
</Typography>
|
||||
{comment.editedAt && (
|
||||
<Typography variant="caption" color="text.disabled" sx={{ fontStyle: 'italic' }}>
|
||||
<FormattedMessage
|
||||
id="commentEdited"
|
||||
defaultMessage="(edited {time})"
|
||||
values={{ time: new Date(comment.editedAt).toLocaleString() }}
|
||||
/>
|
||||
</Typography>
|
||||
)}
|
||||
{canEdit && !isEditing && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
sx={{ minWidth: 0, p: 0, ml: 'auto', textTransform: 'none' }}
|
||||
onClick={() => startEdit(comment)}
|
||||
>
|
||||
<FormattedMessage id="edit" defaultMessage="Edit" />
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{comment.body}
|
||||
</Typography>
|
||||
{isEditing ? (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 0.5 }}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
value={editBody}
|
||||
onChange={(e) => setEditBody(e.target.value)}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={cancelEdit}
|
||||
disabled={savingEdit}
|
||||
>
|
||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
onClick={() => saveEdit(comment.id)}
|
||||
disabled={savingEdit || !editBody.trim()}
|
||||
>
|
||||
<FormattedMessage id="save" defaultMessage="Save" />
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{comment.body}
|
||||
</Typography>
|
||||
)}
|
||||
<DocumentList ticketCommentId={comment.id} refreshKey={refreshKey} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ export type TicketComment = {
|
|||
authorId: number | null;
|
||||
body: string;
|
||||
createdAt: string;
|
||||
editedAt?: string | null;
|
||||
};
|
||||
|
||||
export type TicketAiDiagnosis = {
|
||||
|
|
|
|||
|
|
@ -567,6 +567,7 @@
|
|||
"noDiagnosis": "Keine KI-Diagnose verfügbar.",
|
||||
"comments": "Kommentare",
|
||||
"noComments": "Noch keine Kommentare.",
|
||||
"commentEdited": "(bearbeitet {time})",
|
||||
"addComment": "Hinzufügen",
|
||||
"timeline": "Zeitverlauf",
|
||||
"noTimelineEvents": "Noch keine Ereignisse.",
|
||||
|
|
|
|||
|
|
@ -315,6 +315,7 @@
|
|||
"noDiagnosis": "No AI diagnosis available.",
|
||||
"comments": "Comments",
|
||||
"noComments": "No comments yet.",
|
||||
"commentEdited": "(edited {time})",
|
||||
"addComment": "Add",
|
||||
"timeline": "Timeline",
|
||||
"noTimelineEvents": "No events yet.",
|
||||
|
|
|
|||
|
|
@ -567,6 +567,7 @@
|
|||
"noDiagnosis": "Aucun diagnostic IA disponible.",
|
||||
"comments": "Commentaires",
|
||||
"noComments": "Aucun commentaire pour le moment.",
|
||||
"commentEdited": "(modifié {time})",
|
||||
"addComment": "Ajouter",
|
||||
"timeline": "Chronologie",
|
||||
"noTimelineEvents": "Aucun événement pour le moment.",
|
||||
|
|
|
|||
|
|
@ -567,6 +567,7 @@
|
|||
"noDiagnosis": "Nessuna diagnosi IA disponibile.",
|
||||
"comments": "Commenti",
|
||||
"noComments": "Nessun commento ancora.",
|
||||
"commentEdited": "(modificato {time})",
|
||||
"addComment": "Aggiungi",
|
||||
"timeline": "Cronologia",
|
||||
"noTimelineEvents": "Nessun evento ancora.",
|
||||
|
|
|
|||
Loading…
Reference in New Issue