diff --git a/typescript/frontend-marios2/src/Resources/routes.json b/typescript/frontend-marios2/src/Resources/routes.json index e7f8541bd..6daa7f353 100644 --- a/typescript/frontend-marios2/src/Resources/routes.json +++ b/typescript/frontend-marios2/src/Resources/routes.json @@ -23,5 +23,6 @@ "mainstats": "mainstats", "detailed_view": "detailed_view/", "report": "report", + "installationTickets": "installationTickets", "tickets": "/tickets/" } diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx index 377f869bf..d13f4e139 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx @@ -28,6 +28,7 @@ import Topology from '../Topology/Topology'; import BatteryView from '../BatteryView/BatteryView'; import Configuration from '../Configuration/Configuration'; import PvView from '../PvView/PvView'; +import InstallationTicketsTab from '../Tickets/InstallationTicketsTab'; interface singleInstallationProps { current_installation?: I_Installation; @@ -381,7 +382,8 @@ function Installation(props: singleInstallationProps) { currentTab != 'information' && currentTab != 'history' && currentTab != 'manage' && - currentTab != 'log' && ( + currentTab != 'log' && + currentTab != 'installationTickets' && ( )} + {currentUser.userType == UserType.admin && ( + + } + /> + )} + } diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx index 81121533a..8db02e68a 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx @@ -32,7 +32,8 @@ function InstallationTabs(props: InstallationTabsProps) { 'information', 'configuration', 'history', - 'pvview' + 'pvview', + 'installationTickets' ]; const [currentTab, setCurrentTab] = useState(undefined); @@ -165,6 +166,10 @@ function InstallationTabs(props: InstallationTabsProps) { { value: 'pvview', label: + }, + { + value: 'installationTickets', + label: } ] : currentUser.userType == UserType.partner @@ -294,6 +299,10 @@ function InstallationTabs(props: InstallationTabsProps) { defaultMessage="History Of Actions" /> ) + }, + { + value: 'installationTickets', + label: } ] : currentUser.userType == UserType.partner diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx index c0f432645..b8a678512 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx @@ -23,6 +23,7 @@ import SalidomoOverview from '../Overview/salidomoOverview'; import { UserType } from '../../../interfaces/UserTypes'; import HistoryOfActions from '../History/History'; import BuildIcon from '@mui/icons-material/Build'; +import InstallationTicketsTab from '../Tickets/InstallationTicketsTab'; import AccessContextProvider from '../../../contexts/AccessContextProvider'; import Access from '../ManageAccess/Access'; @@ -324,7 +325,8 @@ function SalidomoInstallation(props: singleInstallationProps) { currentTab != 'information' && currentTab != 'manage' && currentTab != 'history' && - currentTab != 'log' && ( + currentTab != 'log' && + currentTab != 'installationTickets' && ( )} + {currentUser.userType == UserType.admin && ( + + } + /> + )} + } diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx index e84691bac..1a0f619ac 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx @@ -28,7 +28,8 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) { 'manage', 'overview', 'log', - 'history' + 'history', + 'installationTickets' ]; const [currentTab, setCurrentTab] = useState(undefined); @@ -136,6 +137,10 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) { defaultMessage="History Of Actions" /> ) + }, + { + value: 'installationTickets', + label: } ] : [ @@ -217,6 +222,10 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) { defaultMessage="History Of Actions" /> ) + }, + { + value: 'installationTickets', + label: } ] : currentTab != 'list' && diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index 6c8089500..91c3bc9d3 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -28,6 +28,7 @@ import SodistoreHomeConfiguration from './SodistoreHomeConfiguration'; import TopologySodistoreHome from '../Topology/TopologySodistoreHome'; import Overview from '../Overview/overview'; import WeeklyReport from './WeeklyReport'; +import InstallationTicketsTab from '../Tickets/InstallationTicketsTab'; interface singleInstallationProps { current_installation?: I_Installation; @@ -473,7 +474,8 @@ function SodioHomeInstallation(props: singleInstallationProps) { currentTab != 'manage' && currentTab != 'history' && currentTab != 'log' && - currentTab != 'report' && ( + currentTab != 'report' && + currentTab != 'installationTickets' && ( )} + {currentUser.userType == UserType.admin && ( + + } + /> + )} + } diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx index cc9a4197a..398959ea3 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx @@ -31,7 +31,8 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { 'log', 'history', 'configuration', - 'report' + 'report', + 'installationTickets' ]; const [currentTab, setCurrentTab] = useState(undefined); @@ -159,6 +160,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { defaultMessage="Report" /> ) + }, + { + value: 'installationTickets', + label: } ] : currentUser.userType == UserType.partner @@ -310,6 +315,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { defaultMessage="Report" /> ) + }, + { + value: 'installationTickets', + label: } ] : inInstallationView && currentUser.userType == UserType.partner diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/CreateTicketModal.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/CreateTicketModal.tsx index 446f6c5d8..39f803775 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tickets/CreateTicketModal.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/CreateTicketModal.tsx @@ -61,9 +61,10 @@ interface Props { open: boolean; onClose: () => void; onCreated: () => void; + defaultInstallationId?: number; } -function CreateTicketModal({ open, onClose, onCreated }: Props) { +function CreateTicketModal({ open, onClose, onCreated, defaultInstallationId }: Props) { const [subject, setSubject] = useState(''); const [selectedProduct, setSelectedProduct] = useState(''); const [selectedDevice, setSelectedDevice] = useState(''); @@ -100,13 +101,19 @@ function CreateTicketModal({ open, onClose, onCreated }: Props) { .then((res) => { const data = res.data; if (Array.isArray(data)) { - setAllInstallations( - data.map((item: any) => ({ - id: item.id, - name: item.name, - device: item.device - })) - ); + const mapped = data.map((item: any) => ({ + id: item.id, + name: item.name, + device: item.device + })); + setAllInstallations(mapped); + if (defaultInstallationId != null) { + const match = mapped.find((inst: Installation) => inst.id === defaultInstallationId); + if (match) { + setSelectedInstallation(match); + setSelectedDevice(match.device ?? ''); + } + } } }) .catch(() => setAllInstallations([])) @@ -114,7 +121,20 @@ function CreateTicketModal({ open, onClose, onCreated }: Props) { }, [selectedProduct]); useEffect(() => { - setSelectedInstallation(null); + if (defaultInstallationId == null || !open) return; + axiosConfig + .get('/GetInstallationById', { params: { id: defaultInstallationId } }) + .then((res) => { + const inst = res.data; + if (inst) { + setSelectedProduct(inst.product); + } + }) + .catch(() => {}); + }, [defaultInstallationId, open]); + + useEffect(() => { + if (defaultInstallationId == null) setSelectedInstallation(null); }, [selectedDevice]); useEffect(() => { diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/InstallationTicketsTab.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/InstallationTicketsTab.tsx new file mode 100644 index 000000000..51de69fa7 --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/InstallationTicketsTab.tsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Alert, + Box, + Button, + Chip, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import { FormattedMessage, useIntl } from 'react-intl'; +import axiosConfig from 'src/Resources/axiosConfig'; +import { Ticket, TicketStatus } from 'src/interfaces/TicketTypes'; +import StatusChip from './StatusChip'; +import CreateTicketModal from './CreateTicketModal'; + +interface Props { + installationId: number; +} + +const statusCountKeys: { + status: number; + id: string; + defaultMessage: string; + color: string; +}[] = [ + { status: TicketStatus.Open, id: 'statusOpen', defaultMessage: 'Open', color: '#d32f2f' }, + { status: TicketStatus.InProgress, id: 'statusInProgress', defaultMessage: 'In Progress', color: '#ed6c02' }, + { status: TicketStatus.Escalated, id: 'statusEscalated', defaultMessage: 'Escalated', color: '#9c27b0' }, + { status: TicketStatus.Resolved, id: 'statusResolved', defaultMessage: 'Resolved', color: '#2e7d32' }, + { status: TicketStatus.Closed, id: 'statusClosed', defaultMessage: 'Closed', color: '#757575' } +]; + +function InstallationTicketsTab({ installationId }: Props) { + const navigate = useNavigate(); + const intl = useIntl(); + const [tickets, setTickets] = useState([]); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(true); + const [createOpen, setCreateOpen] = useState(false); + + const fetchTickets = () => { + axiosConfig + .get('/GetTicketsForInstallation', { params: { installationId } }) + .then((res) => { + setTickets(res.data); + setError(''); + }) + .catch(() => setError('Failed to load tickets.')) + .finally(() => setLoading(false)); + }; + + useEffect(() => { + fetchTickets(); + }, [installationId]); + + const statusCounts = statusCountKeys.map((s) => ({ + ...s, + count: tickets.filter((t) => t.status === s.status).length + })); + + if (loading) return null; + + return ( + + + + + + + + + + {statusCounts.map((s) => ( + 0 ? s.color : '#e0e0e0', + color: s.count > 0 ? '#fff' : '#757575', + fontWeight: 600 + }} + /> + ))} + + + {error && ( + + {error} + + )} + + {tickets.length === 0 && !error ? ( + + + + ) : ( + + + + + # + + + + + + + + + + + + + + + + {tickets + .sort( + (a, b) => + new Date(b.createdAt).getTime() - + new Date(a.createdAt).getTime() + ) + .map((ticket) => ( + navigate(`/tickets/${ticket.id}`)} + > + {ticket.id} + {ticket.subject} + + + + + {intl.formatMessage({ + id: `priority${['Critical', 'High', 'Medium', 'Low'][ticket.priority]}`, + defaultMessage: ['Critical', 'High', 'Medium', 'Low'][ + ticket.priority + ] + })} + + + {new Date(ticket.createdAt).toLocaleDateString()} + + + ))} + +
+
+ )} + + setCreateOpen(false)} + onCreated={() => { + setCreateOpen(false); + fetchTickets(); + }} + defaultInstallationId={installationId} + /> +
+ ); +} + +export default InstallationTicketsTab;