diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index 55b8a17e0..c0740a606 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -2088,14 +2088,68 @@ public class Controller : ControllerBase var ticket = Db.GetTicketById(id); if (ticket is null) return NotFound(); + var installation = Db.GetInstallationById(ticket.InstallationId); + var creator = Db.GetUserById(ticket.CreatedByUserId); + var assignee = ticket.AssigneeId.HasValue ? Db.GetUserById(ticket.AssigneeId) : null; + return new { ticket, - comments = Db.GetCommentsForTicket(id), - diagnosis = Db.GetDiagnosisForTicket(id), - timeline = Db.GetTimelineForTicket(id) + comments = Db.GetCommentsForTicket(id), + diagnosis = Db.GetDiagnosisForTicket(id), + timeline = Db.GetTimelineForTicket(id), + installationName = installation?.InstallationName ?? $"#{ticket.InstallationId}", + creatorName = creator?.Name ?? $"User #{ticket.CreatedByUserId}", + assigneeName = assignee?.Name }; } + [HttpGet(nameof(GetTicketSummaries))] + public ActionResult GetTicketSummaries(Token authToken) + { + var user = Db.GetSession(authToken)?.User; + if (user is null || user.UserType != 2) return Unauthorized(); + + var tickets = Db.GetAllTickets(); + var summaries = tickets.Select(t => + { + var installation = Db.GetInstallationById(t.InstallationId); + return new + { + t.Id, t.Subject, t.Status, t.Priority, t.Category, t.SubCategory, + t.InstallationId, t.CreatedAt, t.UpdatedAt, + installationName = installation?.InstallationName ?? $"#{t.InstallationId}" + }; + }); + + return Ok(summaries); + } + + [HttpGet(nameof(GetAdminUsers))] + public ActionResult> GetAdminUsers(Token authToken) + { + var user = Db.GetSession(authToken)?.User; + if (user is null || user.UserType != 2) return Unauthorized(); + + return Ok(Db.Users + .Where(u => u.UserType == 2) + .Select(u => new { u.Id, u.Name }) + .ToList()); + } + + [HttpPost(nameof(SubmitDiagnosisFeedback))] + public ActionResult SubmitDiagnosisFeedback(Int64 ticketId, Int32 feedback, String? overrideText, Token authToken) + { + var user = Db.GetSession(authToken)?.User; + if (user is null || user.UserType != 2) return Unauthorized(); + + var diagnosis = Db.GetDiagnosisForTicket(ticketId); + if (diagnosis is null) return NotFound(); + + diagnosis.Feedback = feedback; + diagnosis.OverrideText = overrideText; + + return Db.Update(diagnosis) ? Ok() : StatusCode(500, "Failed to save feedback."); + } } diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/AiDiagnosisPanel.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/AiDiagnosisPanel.tsx new file mode 100644 index 000000000..80f50bab4 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/AiDiagnosisPanel.tsx @@ -0,0 +1,276 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { + Alert, + Box, + Button, + Card, + CardContent, + CardHeader, + Chip, + CircularProgress, + Divider, + List, + ListItem, + ListItemIcon, + ListItemText, + Typography +} from '@mui/material'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import ThumbUpIcon from '@mui/icons-material/ThumbUp'; +import ThumbDownIcon from '@mui/icons-material/ThumbDown'; +import { FormattedMessage } from 'react-intl'; +import axiosConfig from 'src/Resources/axiosConfig'; +import { + DiagnosisStatus, + DiagnosisFeedback, + TicketAiDiagnosis +} from 'src/interfaces/TicketTypes'; + +interface AiDiagnosisPanelProps { + diagnosis: TicketAiDiagnosis | null; + onRefresh: () => void; +} + +function getConfidenceColor( + confidence: number +): 'success' | 'warning' | 'error' { + if (confidence >= 0.7) return 'success'; + if (confidence >= 0.4) return 'warning'; + return 'error'; +} + +function parseActions(actionsJson: string | null): string[] { + if (!actionsJson) return []; + try { + const parsed = JSON.parse(actionsJson); + return Array.isArray(parsed) ? parsed : []; + } catch { + return actionsJson.split('\n').filter((s) => s.trim()); + } +} + +const feedbackLabels: Record = { + [DiagnosisFeedback.Accepted]: 'Accepted', + [DiagnosisFeedback.Rejected]: 'Rejected', + [DiagnosisFeedback.Overridden]: 'Overridden' +}; + +function AiDiagnosisPanel({ diagnosis, onRefresh }: AiDiagnosisPanelProps) { + const intervalRef = useRef | null>(null); + const [submittingFeedback, setSubmittingFeedback] = useState(false); + + const isPending = + diagnosis && + (diagnosis.status === DiagnosisStatus.Pending || + diagnosis.status === DiagnosisStatus.Analyzing); + + useEffect(() => { + if (isPending) { + intervalRef.current = setInterval(onRefresh, 5000); + } + return () => { + if (intervalRef.current) clearInterval(intervalRef.current); + }; + }, [isPending, onRefresh]); + + const handleFeedback = (feedback: DiagnosisFeedback) => { + if (!diagnosis) return; + setSubmittingFeedback(true); + axiosConfig + .post('/SubmitDiagnosisFeedback', null, { + params: { + ticketId: diagnosis.ticketId, + feedback + } + }) + .then(() => onRefresh()) + .finally(() => setSubmittingFeedback(false)); + }; + + if (!diagnosis) { + return ( + + + } + /> + + + + + + + + ); + } + + if (isPending) { + return ( + + + } + /> + + + + + + + + + + + ); + } + + if (diagnosis.status === DiagnosisStatus.Failed) { + return ( + + + } + /> + + + + + + + + ); + } + + const actions = parseActions(diagnosis.recommendedActions); + const hasFeedback = diagnosis.feedback != null; + + return ( + + + } + /> + + + + + + + + {diagnosis.rootCause ?? '-'} + + + + + + + + {diagnosis.confidence != null ? ( + + ) : ( + - + )} + + + {actions.length > 0 && ( + + + + + + {actions.map((action, i) => ( + + + + + + + ))} + + + )} + + + + {hasFeedback ? ( + + + + + + ) : ( + + + + + + + + )} + + + ); +} + +export default AiDiagnosisPanel; diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/CommentThread.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/CommentThread.tsx new file mode 100644 index 000000000..6e0f4695c --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/CommentThread.tsx @@ -0,0 +1,133 @@ +import React, { useState } from 'react'; +import { + Avatar, + Box, + Button, + Card, + CardContent, + CardHeader, + Divider, + TextField, + Typography +} from '@mui/material'; +import PersonIcon from '@mui/icons-material/Person'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import { FormattedMessage } from 'react-intl'; +import axiosConfig from 'src/Resources/axiosConfig'; +import { CommentAuthorType, TicketComment } from 'src/interfaces/TicketTypes'; + +interface CommentThreadProps { + ticketId: number; + comments: TicketComment[]; + onCommentAdded: () => void; +} + +function CommentThread({ + ticketId, + comments, + onCommentAdded +}: CommentThreadProps) { + const [body, setBody] = useState(''); + const [submitting, setSubmitting] = useState(false); + + const sorted = [...comments].sort( + (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() + ); + + const handleSubmit = () => { + if (!body.trim()) return; + setSubmitting(true); + axiosConfig + .post('/AddTicketComment', { ticketId, body }) + .then(() => { + setBody(''); + onCommentAdded(); + }) + .finally(() => setSubmitting(false)); + }; + + return ( + + + } + /> + + + {sorted.length === 0 && ( + + + + )} + + {sorted.map((comment) => { + const isAi = comment.authorType === CommentAuthorType.AiAgent; + return ( + + + {isAi ? ( + + ) : ( + + )} + + + + + {isAi ? 'AI' : `User #${comment.authorId ?? '?'}`} + + + {new Date(comment.createdAt).toLocaleString()} + + + + {comment.body} + + + + ); + })} + + + + + setBody(e.target.value)} + /> + + + + + ); +} + +export default CommentThread; diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/StatusChip.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/StatusChip.tsx new file mode 100644 index 000000000..7ced43d04 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/StatusChip.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Chip } from '@mui/material'; +import { TicketStatus } from 'src/interfaces/TicketTypes'; + +const statusLabels: Record = { + [TicketStatus.Open]: 'Open', + [TicketStatus.InProgress]: 'In Progress', + [TicketStatus.Escalated]: 'Escalated', + [TicketStatus.Resolved]: 'Resolved', + [TicketStatus.Closed]: 'Closed' +}; + +const statusColors: Record< + number, + 'error' | 'warning' | 'info' | 'success' | 'default' +> = { + [TicketStatus.Open]: 'error', + [TicketStatus.InProgress]: 'warning', + [TicketStatus.Escalated]: 'error', + [TicketStatus.Resolved]: 'success', + [TicketStatus.Closed]: 'default' +}; + +interface StatusChipProps { + status: number; + size?: 'small' | 'medium'; +} + +function StatusChip({ status, size = 'small' }: StatusChipProps) { + return ( + + ); +} + +export default StatusChip; \ No newline at end of file diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketDetail.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketDetail.tsx new file mode 100644 index 000000000..124e07b5b --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketDetail.tsx @@ -0,0 +1,432 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { + Alert, + Box, + Button, + Card, + CardContent, + CardHeader, + CircularProgress, + Container, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Divider, + FormControl, + Grid, + InputLabel, + MenuItem, + Select, + Typography +} from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { FormattedMessage } from 'react-intl'; +import axiosConfig from 'src/Resources/axiosConfig'; +import { + TicketDetail as TicketDetailType, + TicketStatus, + TicketPriority, + TicketCategory, + TicketSubCategory, + AdminUser, + subCategoryLabels +} from 'src/interfaces/TicketTypes'; +import Footer from 'src/components/Footer'; +import StatusChip from './StatusChip'; +import AiDiagnosisPanel from './AiDiagnosisPanel'; +import CommentThread from './CommentThread'; +import TimelinePanel from './TimelinePanel'; + +const priorityLabels: Record = { + [TicketPriority.Critical]: 'Critical', + [TicketPriority.High]: 'High', + [TicketPriority.Medium]: 'Medium', + [TicketPriority.Low]: 'Low' +}; + +const categoryLabels: Record = { + [TicketCategory.Hardware]: 'Hardware', + [TicketCategory.Software]: 'Software', + [TicketCategory.Network]: 'Network', + [TicketCategory.UserAccess]: 'User Access', + [TicketCategory.Firmware]: 'Firmware' +}; + +const statusOptions = [ + { value: TicketStatus.Open, label: 'Open' }, + { value: TicketStatus.InProgress, label: 'In Progress' }, + { value: TicketStatus.Escalated, label: 'Escalated' }, + { value: TicketStatus.Resolved, label: 'Resolved' }, + { value: TicketStatus.Closed, label: 'Closed' } +]; + +function TicketDetailPage() { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const [detail, setDetail] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [updatingStatus, setUpdatingStatus] = useState(false); + const [adminUsers, setAdminUsers] = useState([]); + const [updatingAssignee, setUpdatingAssignee] = useState(false); + const [deleteOpen, setDeleteOpen] = useState(false); + const [deleting, setDeleting] = useState(false); + + const fetchDetail = useCallback(() => { + if (!id) return; + axiosConfig + .get('/GetTicketDetail', { params: { id: Number(id) } }) + .then((res) => { + setDetail(res.data); + setError(''); + }) + .catch(() => setError('Failed to load ticket details.')) + .finally(() => setLoading(false)); + }, [id]); + + useEffect(() => { + fetchDetail(); + axiosConfig + .get('/GetAdminUsers') + .then((res) => setAdminUsers(res.data)) + .catch(() => {}); + }, [fetchDetail]); + + const handleStatusChange = (newStatus: number) => { + if (!detail) return; + setUpdatingStatus(true); + axiosConfig + .put('/UpdateTicket', { ...detail.ticket, status: newStatus }) + .then(() => fetchDetail()) + .catch(() => setError('Failed to update status.')) + .finally(() => setUpdatingStatus(false)); + }; + + const handleAssigneeChange = (assigneeId: number | '') => { + if (!detail) return; + setUpdatingAssignee(true); + axiosConfig + .put('/UpdateTicket', { + ...detail.ticket, + assigneeId: assigneeId === '' ? null : assigneeId + }) + .then(() => fetchDetail()) + .catch(() => setError('Failed to update assignee.')) + .finally(() => setUpdatingAssignee(false)); + }; + + const handleDelete = () => { + if (!id) return; + setDeleting(true); + axiosConfig + .delete('/DeleteTicket', { params: { id: Number(id) } }) + .then(() => navigate('/tickets')) + .catch(() => { + setError('Failed to delete ticket.'); + setDeleting(false); + setDeleteOpen(false); + }); + }; + + if (loading) { + return ( + + + + ); + } + + if (error || !detail) { + return ( + + {error || 'Ticket not found.'} + + + ); + } + + const { ticket, comments, diagnosis, timeline } = detail; + + return ( +
+ + + + + + + + + #{ticket.id} — {ticket.subject} + + + + + {priorityLabels[ticket.priority] ?? '-'} ·{' '} + {categoryLabels[ticket.category] ?? '-'} + {ticket.subCategory !== TicketSubCategory.General && + ` · ${subCategoryLabels[ticket.subCategory] ?? ''}`} + + + + + + {/* Left column: Description, AI Diagnosis, Comments */} + + + + } + /> + + + + {ticket.description || ( + + + + )} + + + + + + + + + + + + {/* Right column: Status, Assignee, Details, Timeline */} + + + + } + /> + + + + + + + + + + + + + + + + + + + + + } + /> + + + + + + + + {detail.installationName} + + + + + + + + {detail.creatorName} + + + {detail.assigneeName && ( + + + + + + {detail.assigneeName} + + + )} + + + + + + {new Date(ticket.createdAt).toLocaleString()} + + + + + + + + {new Date(ticket.updatedAt).toLocaleString()} + + + {ticket.resolvedAt && ( + + + + + + {new Date(ticket.resolvedAt).toLocaleString()} + + + )} + + + + + + + + {/* Delete confirmation dialog */} + setDeleteOpen(false)}> + + + + + + + + + + + + + + +
+
+ ); +} + +export default TicketDetailPage; diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketList.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketList.tsx index 1c4a85361..430fe4599 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketList.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketList.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Alert, Box, Button, - Chip, Container, FormControl, InputLabel, @@ -22,7 +22,7 @@ import AddIcon from '@mui/icons-material/Add'; import { FormattedMessage } from 'react-intl'; import axiosConfig from 'src/Resources/axiosConfig'; import { - Ticket, + TicketSummary, TicketStatus, TicketPriority, TicketCategory, @@ -31,6 +31,7 @@ import { } from 'src/interfaces/TicketTypes'; import Footer from 'src/components/Footer'; import CreateTicketModal from './CreateTicketModal'; +import StatusChip from './StatusChip'; const statusLabels: Record = { [TicketStatus.Open]: 'Open', @@ -40,14 +41,6 @@ const statusLabels: Record = { [TicketStatus.Closed]: 'Closed' }; -const statusColors: Record = { - [TicketStatus.Open]: 'error', - [TicketStatus.InProgress]: 'warning', - [TicketStatus.Escalated]: 'error', - [TicketStatus.Resolved]: 'success', - [TicketStatus.Closed]: 'default' -}; - const priorityLabels: Record = { [TicketPriority.Critical]: 'Critical', [TicketPriority.High]: 'High', @@ -64,7 +57,8 @@ const categoryLabels: Record = { }; function TicketList() { - const [tickets, setTickets] = useState([]); + const navigate = useNavigate(); + const [tickets, setTickets] = useState([]); const [search, setSearch] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [createOpen, setCreateOpen] = useState(false); @@ -72,7 +66,7 @@ function TicketList() { const fetchTickets = () => { axiosConfig - .get('/GetAllTickets') + .get('/GetTicketSummaries') .then((res) => setTickets(res.data)) .catch(() => setError('Failed to load tickets')); }; @@ -85,7 +79,7 @@ function TicketList() { const matchesSearch = search === '' || t.subject.toLowerCase().includes(search.toLowerCase()) || - t.description.toLowerCase().includes(search.toLowerCase()); + t.installationName.toLowerCase().includes(search.toLowerCase()); const matchesStatus = statusFilter === '' || t.status === statusFilter; return matchesSearch && matchesStatus; }); @@ -191,6 +185,12 @@ function TicketList() { defaultMessage="Category" /> + + + {filtered.map((ticket) => ( - + navigate(`/tickets/${ticket.id}`)}> {ticket.id} {ticket.subject} - + {priorityLabels[ticket.priority] ?? '-'} @@ -217,6 +213,7 @@ function TicketList() { {ticket.subCategory !== TicketSubCategory.General && ` — ${subCategoryLabels[ticket.subCategory] ?? ''}`} + {ticket.installationName} {new Date(ticket.createdAt).toLocaleDateString()} diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/TimelinePanel.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/TimelinePanel.tsx new file mode 100644 index 000000000..d482f6a0b --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/TimelinePanel.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { + Box, + Card, + CardContent, + CardHeader, + Divider, + Typography +} from '@mui/material'; +import { FormattedMessage } from 'react-intl'; +import { + TicketTimelineEvent, + TimelineEventType +} from 'src/interfaces/TicketTypes'; + +const eventTypeLabels: Record = { + [TimelineEventType.Created]: 'Created', + [TimelineEventType.StatusChanged]: 'Status Changed', + [TimelineEventType.Assigned]: 'Assigned', + [TimelineEventType.CommentAdded]: 'Comment Added', + [TimelineEventType.AiDiagnosisAttached]: 'AI Diagnosis', + [TimelineEventType.Escalated]: 'Escalated' +}; + +const eventTypeColors: Record = { + [TimelineEventType.Created]: '#1976d2', + [TimelineEventType.StatusChanged]: '#ed6c02', + [TimelineEventType.Assigned]: '#9c27b0', + [TimelineEventType.CommentAdded]: '#2e7d32', + [TimelineEventType.AiDiagnosisAttached]: '#0288d1', + [TimelineEventType.Escalated]: '#d32f2f' +}; + +interface TimelinePanelProps { + events: TicketTimelineEvent[]; +} + +function TimelinePanel({ events }: TimelinePanelProps) { + const sorted = [...events].sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); + + return ( + + + } + /> + + + {sorted.length === 0 ? ( + + + + ) : ( + + {sorted.map((event) => ( + + + + + {eventTypeLabels[event.eventType] ?? 'Event'} + + + {event.description} + + + {new Date(event.createdAt).toLocaleString()} + + + + ))} + + )} + + + ); +} + +export default TimelinePanel; \ No newline at end of file diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/index.tsx index 57d325748..abad62db3 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tickets/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/index.tsx @@ -1,11 +1,13 @@ import React from 'react'; import { Route, Routes } from 'react-router-dom'; import TicketList from './TicketList'; +import TicketDetailPage from './TicketDetail'; function Tickets() { return ( } /> + } /> ); } diff --git a/typescript/frontend-marios2/src/interfaces/TicketTypes.tsx b/typescript/frontend-marios2/src/interfaces/TicketTypes.tsx index 8107d2ba8..67f710f06 100644 --- a/typescript/frontend-marios2/src/interfaces/TicketTypes.tsx +++ b/typescript/frontend-marios2/src/interfaces/TicketTypes.tsx @@ -146,4 +146,31 @@ export type TicketDetail = { comments: TicketComment[]; diagnosis: TicketAiDiagnosis | null; timeline: TicketTimelineEvent[]; + installationName: string; + creatorName: string; + assigneeName: string | null; }; + +export type TicketSummary = { + id: number; + subject: string; + status: number; + priority: number; + category: number; + subCategory: number; + installationId: number; + createdAt: string; + updatedAt: string; + installationName: string; +}; + +export type AdminUser = { + id: number; + name: string; +}; + +export enum DiagnosisFeedback { + Accepted = 0, + Rejected = 1, + Overridden = 2 +} diff --git a/typescript/frontend-marios2/src/lang/de.json b/typescript/frontend-marios2/src/lang/de.json index de571333b..110fb1a42 100644 --- a/typescript/frontend-marios2/src/lang/de.json +++ b/typescript/frontend-marios2/src/lang/de.json @@ -524,5 +524,33 @@ "category": "Kategorie", "allStatuses": "Alle Status", "createdAt": "Erstellt", - "noTickets": "Keine Tickets gefunden." + "noTickets": "Keine Tickets gefunden.", + "backToTickets": "Zurück zu Tickets", + "aiDiagnosis": "KI-Diagnose", + "rootCause": "Ursache", + "confidence": "Zuverlässigkeit", + "recommendedActions": "Empfohlene Massnahmen", + "diagnosisAnalyzing": "KI analysiert dieses Ticket...", + "diagnosisFailed": "KI-Diagnose fehlgeschlagen. Bitte versuchen Sie es später erneut.", + "noDiagnosis": "Keine KI-Diagnose verfügbar.", + "comments": "Kommentare", + "noComments": "Noch keine Kommentare.", + "addComment": "Hinzufügen", + "timeline": "Zeitverlauf", + "noTimelineEvents": "Noch keine Ereignisse.", + "updateStatus": "Status aktualisieren", + "details": "Details", + "createdBy": "Erstellt von", + "updatedAt": "Aktualisiert", + "resolvedAt": "Gelöst", + "noDescription": "Keine Beschreibung vorhanden.", + "assignee": "Zuständig", + "unassigned": "Nicht zugewiesen", + "deleteTicket": "Löschen", + "confirmDeleteTicket": "Ticket löschen?", + "confirmDeleteTicketMessage": "Dieses Ticket wird mit allen Kommentaren, KI-Diagnosen und dem Zeitverlauf dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", + "diagnosisFeedbackLabel": "War das hilfreich?", + "feedbackSubmitted": "Feedback: {feedback}", + "accept": "Akzeptieren", + "reject": "Ablehnen" } \ No newline at end of file diff --git a/typescript/frontend-marios2/src/lang/en.json b/typescript/frontend-marios2/src/lang/en.json index 52cd08915..29991b3ba 100644 --- a/typescript/frontend-marios2/src/lang/en.json +++ b/typescript/frontend-marios2/src/lang/en.json @@ -272,5 +272,33 @@ "category": "Category", "allStatuses": "All Statuses", "createdAt": "Created", - "noTickets": "No tickets found." + "noTickets": "No tickets found.", + "backToTickets": "Back to Tickets", + "aiDiagnosis": "AI Diagnosis", + "rootCause": "Root Cause", + "confidence": "Confidence", + "recommendedActions": "Recommended Actions", + "diagnosisAnalyzing": "AI is analyzing this ticket...", + "diagnosisFailed": "AI diagnosis failed. Please try again later.", + "noDiagnosis": "No AI diagnosis available.", + "comments": "Comments", + "noComments": "No comments yet.", + "addComment": "Add", + "timeline": "Timeline", + "noTimelineEvents": "No events yet.", + "updateStatus": "Update Status", + "details": "Details", + "createdBy": "Created By", + "updatedAt": "Updated", + "resolvedAt": "Resolved", + "noDescription": "No description provided.", + "assignee": "Assignee", + "unassigned": "Unassigned", + "deleteTicket": "Delete", + "confirmDeleteTicket": "Delete Ticket?", + "confirmDeleteTicketMessage": "This will permanently delete this ticket, its comments, AI diagnosis, and timeline. This action cannot be undone.", + "diagnosisFeedbackLabel": "Was this helpful?", + "feedbackSubmitted": "Feedback: {feedback}", + "accept": "Accept", + "reject": "Reject" } diff --git a/typescript/frontend-marios2/src/lang/fr.json b/typescript/frontend-marios2/src/lang/fr.json index b84d6c028..f970dc332 100644 --- a/typescript/frontend-marios2/src/lang/fr.json +++ b/typescript/frontend-marios2/src/lang/fr.json @@ -524,5 +524,33 @@ "category": "Catégorie", "allStatuses": "Tous les statuts", "createdAt": "Créé", - "noTickets": "Aucun ticket trouvé." + "noTickets": "Aucun ticket trouvé.", + "backToTickets": "Retour aux tickets", + "aiDiagnosis": "Diagnostic IA", + "rootCause": "Cause principale", + "confidence": "Confiance", + "recommendedActions": "Actions recommandées", + "diagnosisAnalyzing": "L'IA analyse ce ticket...", + "diagnosisFailed": "Le diagnostic IA a échoué. Veuillez réessayer plus tard.", + "noDiagnosis": "Aucun diagnostic IA disponible.", + "comments": "Commentaires", + "noComments": "Aucun commentaire pour le moment.", + "addComment": "Ajouter", + "timeline": "Chronologie", + "noTimelineEvents": "Aucun événement pour le moment.", + "updateStatus": "Mettre à jour le statut", + "details": "Détails", + "createdBy": "Créé par", + "updatedAt": "Mis à jour", + "resolvedAt": "Résolu", + "noDescription": "Aucune description fournie.", + "assignee": "Responsable", + "unassigned": "Non assigné", + "deleteTicket": "Supprimer", + "confirmDeleteTicket": "Supprimer le ticket ?", + "confirmDeleteTicketMessage": "Ce ticket sera définitivement supprimé avec tous ses commentaires, diagnostics IA et sa chronologie. Cette action est irréversible.", + "diagnosisFeedbackLabel": "Était-ce utile ?", + "feedbackSubmitted": "Retour : {feedback}", + "accept": "Accepter", + "reject": "Rejeter" } \ No newline at end of file diff --git a/typescript/frontend-marios2/src/lang/it.json b/typescript/frontend-marios2/src/lang/it.json index d4bca6997..50a5cd159 100644 --- a/typescript/frontend-marios2/src/lang/it.json +++ b/typescript/frontend-marios2/src/lang/it.json @@ -524,5 +524,33 @@ "category": "Categoria", "allStatuses": "Tutti gli stati", "createdAt": "Creato", - "noTickets": "Nessun ticket trovato." + "noTickets": "Nessun ticket trovato.", + "backToTickets": "Torna ai ticket", + "aiDiagnosis": "Diagnosi IA", + "rootCause": "Causa principale", + "confidence": "Affidabilità", + "recommendedActions": "Azioni consigliate", + "diagnosisAnalyzing": "L'IA sta analizzando questo ticket...", + "diagnosisFailed": "Diagnosi IA fallita. Riprovare più tardi.", + "noDiagnosis": "Nessuna diagnosi IA disponibile.", + "comments": "Commenti", + "noComments": "Nessun commento ancora.", + "addComment": "Aggiungi", + "timeline": "Cronologia", + "noTimelineEvents": "Nessun evento ancora.", + "updateStatus": "Aggiorna stato", + "details": "Dettagli", + "createdBy": "Creato da", + "updatedAt": "Aggiornato", + "resolvedAt": "Risolto", + "noDescription": "Nessuna descrizione fornita.", + "assignee": "Assegnatario", + "unassigned": "Non assegnato", + "deleteTicket": "Elimina", + "confirmDeleteTicket": "Eliminare il ticket?", + "confirmDeleteTicketMessage": "Questo ticket verrà eliminato definitivamente con tutti i commenti, la diagnosi IA e la cronologia. Questa azione non può essere annullata.", + "diagnosisFeedbackLabel": "È stato utile?", + "feedbackSubmitted": "Feedback: {feedback}", + "accept": "Accetta", + "reject": "Rifiuta" }