added Tickets tab to each installation and allowed to create ticket from there with pre-filled installation information and connected between them
This commit is contained in:
parent
bf47a82b25
commit
ac21c46c0e
|
|
@ -23,5 +23,6 @@
|
|||
"mainstats": "mainstats",
|
||||
"detailed_view": "detailed_view/",
|
||||
"report": "report",
|
||||
"installationTickets": "installationTickets",
|
||||
"tickets": "/tickets/"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' && (
|
||||
<Container
|
||||
maxWidth="xl"
|
||||
sx={{
|
||||
|
|
@ -550,6 +552,17 @@ function Installation(props: singleInstallationProps) {
|
|||
/>
|
||||
)}
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
<Route
|
||||
path={routes.installationTickets}
|
||||
element={
|
||||
<InstallationTicketsTab
|
||||
installationId={props.current_installation.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Route
|
||||
path={'*'}
|
||||
element={<Navigate to={routes.live}></Navigate>}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ function InstallationTabs(props: InstallationTabsProps) {
|
|||
'information',
|
||||
'configuration',
|
||||
'history',
|
||||
'pvview'
|
||||
'pvview',
|
||||
'installationTickets'
|
||||
];
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||
|
|
@ -165,6 +166,10 @@ function InstallationTabs(props: InstallationTabsProps) {
|
|||
{
|
||||
value: 'pvview',
|
||||
label: <FormattedMessage id="pvview" defaultMessage="Pv View" />
|
||||
},
|
||||
{
|
||||
value: 'installationTickets',
|
||||
label: <FormattedMessage id="tickets" defaultMessage="Tickets" />
|
||||
}
|
||||
]
|
||||
: currentUser.userType == UserType.partner
|
||||
|
|
@ -294,6 +299,10 @@ function InstallationTabs(props: InstallationTabsProps) {
|
|||
defaultMessage="History Of Actions"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'installationTickets',
|
||||
label: <FormattedMessage id="tickets" defaultMessage="Tickets" />
|
||||
}
|
||||
]
|
||||
: currentUser.userType == UserType.partner
|
||||
|
|
|
|||
|
|
@ -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' && (
|
||||
<Container
|
||||
maxWidth="xl"
|
||||
sx={{
|
||||
|
|
@ -428,6 +430,17 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
|||
/>
|
||||
)}
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
<Route
|
||||
path={routes.installationTickets}
|
||||
element={
|
||||
<InstallationTicketsTab
|
||||
installationId={props.current_installation.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Route
|
||||
path={'*'}
|
||||
element={<Navigate to={routes.batteryview}></Navigate>}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) {
|
|||
'manage',
|
||||
'overview',
|
||||
'log',
|
||||
'history'
|
||||
'history',
|
||||
'installationTickets'
|
||||
];
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||
|
|
@ -136,6 +137,10 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) {
|
|||
defaultMessage="History Of Actions"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'installationTickets',
|
||||
label: <FormattedMessage id="tickets" defaultMessage="Tickets" />
|
||||
}
|
||||
]
|
||||
: [
|
||||
|
|
@ -217,6 +222,10 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) {
|
|||
defaultMessage="History Of Actions"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'installationTickets',
|
||||
label: <FormattedMessage id="tickets" defaultMessage="Tickets" />
|
||||
}
|
||||
]
|
||||
: currentTab != 'list' &&
|
||||
|
|
|
|||
|
|
@ -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' && (
|
||||
<Container
|
||||
maxWidth="xl"
|
||||
sx={{
|
||||
|
|
@ -618,6 +620,17 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
/>
|
||||
)}
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
<Route
|
||||
path={routes.installationTickets}
|
||||
element={
|
||||
<InstallationTicketsTab
|
||||
installationId={props.current_installation.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Route
|
||||
path={'*'}
|
||||
element={<Navigate to={routes.live}></Navigate>}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
'log',
|
||||
'history',
|
||||
'configuration',
|
||||
'report'
|
||||
'report',
|
||||
'installationTickets'
|
||||
];
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||
|
|
@ -159,6 +160,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
defaultMessage="Report"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'installationTickets',
|
||||
label: <FormattedMessage id="tickets" defaultMessage="Tickets" />
|
||||
}
|
||||
]
|
||||
: currentUser.userType == UserType.partner
|
||||
|
|
@ -310,6 +315,10 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
|||
defaultMessage="Report"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'installationTickets',
|
||||
label: <FormattedMessage id="tickets" defaultMessage="Tickets" />
|
||||
}
|
||||
]
|
||||
: inInstallationView && currentUser.userType == UserType.partner
|
||||
|
|
|
|||
|
|
@ -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<number | ''>('');
|
||||
const [selectedDevice, setSelectedDevice] = useState<number | ''>('');
|
||||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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<Ticket[]>([]);
|
||||
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 (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
|
||||
<Typography variant="h4">
|
||||
<FormattedMessage id="tickets" defaultMessage="Tickets" />
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setCreateOpen(true)}
|
||||
>
|
||||
<FormattedMessage id="createTicket" defaultMessage="Create Ticket" />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" gap={1.5} mb={2} flexWrap="wrap">
|
||||
{statusCounts.map((s) => (
|
||||
<Chip
|
||||
key={s.status}
|
||||
label={`${intl.formatMessage({ id: s.id, defaultMessage: s.defaultMessage })}: ${s.count}`}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: s.count > 0 ? s.color : '#e0e0e0',
|
||||
color: s.count > 0 ? '#fff' : '#757575',
|
||||
fontWeight: 600
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{tickets.length === 0 && !error ? (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<FormattedMessage id="noTickets" defaultMessage="No tickets found." />
|
||||
</Typography>
|
||||
) : (
|
||||
<TableContainer>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="subject" defaultMessage="Subject" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="status" defaultMessage="Status" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="priority" defaultMessage="Priority" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="createdAt" defaultMessage="Created" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tickets
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.createdAt).getTime() -
|
||||
new Date(a.createdAt).getTime()
|
||||
)
|
||||
.map((ticket) => (
|
||||
<TableRow
|
||||
key={ticket.id}
|
||||
hover
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => navigate(`/tickets/${ticket.id}`)}
|
||||
>
|
||||
<TableCell>{ticket.id}</TableCell>
|
||||
<TableCell>{ticket.subject}</TableCell>
|
||||
<TableCell>
|
||||
<StatusChip status={ticket.status} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{intl.formatMessage({
|
||||
id: `priority${['Critical', 'High', 'Medium', 'Low'][ticket.priority]}`,
|
||||
defaultMessage: ['Critical', 'High', 'Medium', 'Low'][
|
||||
ticket.priority
|
||||
]
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(ticket.createdAt).toLocaleDateString()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
<CreateTicketModal
|
||||
open={createOpen}
|
||||
onClose={() => setCreateOpen(false)}
|
||||
onCreated={() => {
|
||||
setCreateOpen(false);
|
||||
fetchTickets();
|
||||
}}
|
||||
defaultInstallationId={installationId}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default InstallationTicketsTab;
|
||||
Loading…
Reference in New Issue