Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Autocomplete, Button, Card, Chip, IconButton, Link, MenuItem, Select, Stack, TextField, Typography } from "@mui/material";
import { DELETE_EQUIPMENT_INSTANCE, EquipmentInstance, InstanceStatus, UPDATE_INSTANCE, UPDATE_INSTANCE_CONTROLLER_ASSIGNMENT, UPDATE_INSTANCE_HOBBS_TIME } from "../../../queries/equipmentInstanceQueries";
import { DELETE_EQUIPMENT_INSTANCE, EquipmentInstance, formatHobbsTime, InstanceStatus, UPDATE_INSTANCE, UPDATE_INSTANCE_CONTROLLER_ASSIGNMENT, UPDATE_INSTANCE_HOBBS_TIME } from "../../../queries/equipmentInstanceQueries";
import ActionButton from "../../../common/ActionButton";
import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutline';
import { useMutation, useQuery } from "@apollo/client/react";
Expand Down Expand Up @@ -100,19 +100,6 @@ export default function EquipmentInstanceCard(props: EquipmentInstanceCardProps)
/>;
}

function formatSeconds(allSeconds: number){
const hours = Math.floor(allSeconds / 3600);
if (hours > 0){
return (allSeconds / 3600).toFixed(2) + " hrs"
}
const minutes = Math.floor((allSeconds % 3600) / 60);
const seconds = allSeconds % 60;
if (minutes > 0){
return (minutes + seconds / 50).toFixed(2) + " min"
} else {
return Math.floor(allSeconds) + "sec"
}
}

function activeUserDisplay() {
if (!currentAccessController) {
Expand Down Expand Up @@ -165,7 +152,7 @@ export default function EquipmentInstanceCard(props: EquipmentInstanceCardProps)
<Stack alignItems={"center"} justifyContent={"space-between"}>
{
!allowEdit
? formatSeconds(props.instance.hobbsTime)
? formatHobbsTime(props.instance.hobbsTime)
: hobbsTimeSetField()
}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Autocomplete, Button, Card, Chip, IconButton, Link, MenuItem, Select, Stack, TextField, Typography } from "@mui/material";
import { DELETE_EQUIPMENT_INSTANCE, EquipmentInstance, InstanceStatus, UPDATE_INSTANCE, UPDATE_INSTANCE_CONTROLLER_ASSIGNMENT, UPDATE_INSTANCE_HOBBS_TIME } from "../../../queries/equipmentInstanceQueries";
import { DELETE_EQUIPMENT_INSTANCE, EquipmentInstance, formatHobbsTime, InstanceStatus, UPDATE_INSTANCE, UPDATE_INSTANCE_CONTROLLER_ASSIGNMENT, UPDATE_INSTANCE_HOBBS_TIME } from "../../../queries/equipmentInstanceQueries";
import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutline';
import { useMutation, useQuery } from "@apollo/client/react";
import { useState } from "react";
Expand Down Expand Up @@ -99,20 +99,6 @@ export default function EquipmentInstanceRow(props: EquipmentInstanceRowProps) {
/>;
}

function formatSeconds(allSeconds: number){
const hours = Math.floor(allSeconds / 3600);
if (hours > 0){
return (allSeconds / 3600).toFixed(2) + " hrs"
}
const minutes = Math.floor((allSeconds % 3600) / 60);
const seconds = allSeconds % 60;
if (minutes > 0){
return (minutes + seconds / 50).toFixed(2) + " min"
} else {
return Math.floor(allSeconds) + "sec"
}
}

function activeUserDisplay() {
if (!currentAccessController) {
return <Typography>No User</Typography>;
Expand Down Expand Up @@ -162,7 +148,7 @@ export default function EquipmentInstanceRow(props: EquipmentInstanceRowProps) {
<Stack alignItems={"center"} direction={"row"} width={"200px"}>
{
!allowEdit
? formatSeconds(props.instance.hobbsTime)
? formatHobbsTime(props.instance.hobbsTime)
: hobbsTimeSetField()
}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,16 @@ export default function MaintenancePage() {
// const containsOperator = getGridStringOperators().filter((operator) => operator.value === "contains");

const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 10, filterable: false, resizable: false, hideable: false },
{ field: "id", headerName: "ID", width: 25, filterable: false, resizable: false, hideable: false },
{ field: "equipment", headerName: "Equipment", width: 350, sortable: false, filterable: false, resizable: false },
{ field: "instance", headerName: "Instance", width: 300, sortable: false, filterable: false, resizable: false },
{ field: "type", headerName: "Type", width: 140, sortable: false, filterable: false, resizable: false },
{ field: "status", headerName: "Status", width: 140, filterable: false, resizable: false },
{ field: "severity", headerName: "Severity", width: 140, filterable: false, resizable: false },
{ field: "creator", headerName: "Creator", width: 140, sortable: false, filterable: false, resizable: false },
{ field: "assigned", headerName: "Assigned", width: 140, sortable: false, filterable: false, resizable: false },
{ field: "dateCreated", headerName: "Created", width: 180, filterable: false, resizable: false },
{ field: "severity", headerName: "Severity", width: 120, filterable: false, resizable: false },
{ field: "creator", headerName: "Creator", width: 120, sortable: false, filterable: false, resizable: false },
{ field: "assigned", headerName: "Assigned", width: 120, sortable: false, filterable: false, resizable: false },
{ field: "timeUnit", headerName: "Time Unit", width: 140, sortable: false, filterable: false, resizable: false },
{ field: "dateCreated", headerName: "Create Date", width: 180, filterable: false, resizable: false },
{
field: "manage", headerName: "Manage", width: 140, filterable: false, sortable: false, renderCell: (params: GridRenderCellParams<any, MaintenanceTicket>) => (
<MaintenanceTicketButtonCell ticket={params.value} />
Expand Down Expand Up @@ -96,6 +97,7 @@ export default function MaintenancePage() {
severity: ticket.severity,
creator: ticket.type === MaintenanceTicketType.REPORTED ? ticket.creator?.ritUsername ?? "" : "SERVER",
assigned: ticket.assigned?.ritUsername ?? "UNASSIGNED",
timeUnit: ticket.timeUnit,
dateCreated: formatter.format(new Date(Number(ticket.dateCreated))),
manage: ticket
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Stack } from "@mui/system";
import PrettyModal from "../../../common/PrettyModal";
import { ASSIGN_MAINTENANCE_TICKET, DELETE_MAINTENACE_TICKET, MaintenanceTicket, MaintenanceTicketSeverity, MaintenanceTicketStatus, MaintenanceTicketType, MODIFY_MAINTENANCE_TICKET_STATUS, UPDATE_MAINTENACE_TICKET } from "../../../queries/maintenanceTicketQueries";
import { ASSIGN_MAINTENANCE_TICKET, DELETE_MAINTENACE_TICKET, MaintenanceTicket, MaintenanceTicketSeverity, MaintenanceTicketStatus, MaintenanceTicketTimeUnit, MaintenanceTicketType, MODIFY_MAINTENANCE_TICKET_STATUS, UPDATE_MAINTENACE_TICKET } from "../../../queries/maintenanceTicketQueries";
import { Autocomplete, Button, Chip, IconButton, TextField, Typography } from "@mui/material";
import { useState } from "react";
import CloseIcon from '@mui/icons-material/Close';
Expand All @@ -17,6 +17,7 @@ import { useParams } from "react-router-dom";
import { GET_VALID_STAFF } from "../../../queries/makerspaceQueries";
import { CurrentUser, useCurrentUser } from "../../../common/CurrentUserProvider";
import { isManager } from "../../../common/PrivilegeUtils";
import { formatHobbsTime } from "../../../queries/equipmentInstanceQueries";

interface TicketModalProps {
open: boolean,
Expand Down Expand Up @@ -194,6 +195,19 @@ export default function MaintenanceTicketModal(props: TicketModalProps) {
}
}

function createTimeLabel(){
const after = `Create After: ${props.ticket.intervalHours} hours`
const difference = props.ticket.instance.hobbsTime - props.ticket.hobbsTimeAtCreate
if (difference < 0){
return `${after} (${formatHobbsTime(Math.abs(difference))} to go)`
} else {
return `${after} (${formatHobbsTime(difference)} ago)`
}
}

const shouldShowHobbsAtOpen= props.ticket.timeUnit == MaintenanceTicketTimeUnit.USAGE || props.ticket.status != MaintenanceTicketStatus.UPCOMING;
const shouldShowHobbsAtClose= props.ticket.status == MaintenanceTicketStatus.CLOSED;

return (
<PrettyModal open={props.open} onClose={handleClose} width={"600px"}>
<Stack spacing={2}>
Expand All @@ -214,7 +228,10 @@ export default function MaintenanceTicketModal(props: TicketModalProps) {
</Stack>
<Stack direction={"row"} justifyContent={"space-between"}>
<Typography variant="subtitle1">{`Ticket #${props.ticket.id}`}</Typography>
<Typography variant="subtitle1">{`Created: ${formatter.format(Number(props.ticket.dateCreated))}`}</Typography>
{ props.ticket.timeUnit=== MaintenanceTicketTimeUnit.CALENDAR ?
<Typography variant="subtitle1">{`Create Date: ${formatter.format(Number(props.ticket.dateCreated))}`}</Typography>
: <Typography variant="subtitle1">{createTimeLabel()}</Typography>
}
</Stack>
<Stack direction={"row"} justifyContent={"space-between"} alignItems={"center"}>
<Typography variant="body1">{`Reported by: ${props.ticket.type === MaintenanceTicketType.REPORTED ? props.ticket.creator?.ritUsername ?? "" : "SERVER"}`}</Typography>
Expand Down Expand Up @@ -317,6 +334,10 @@ export default function MaintenanceTicketModal(props: TicketModalProps) {
</Stack>
}
</Stack>
<Stack justifyContent={"space-between"} direction={"row"}>
{shouldShowHobbsAtOpen ? <Typography variant="body1">Usage at Open: {formatHobbsTime(props.ticket.hobbsTimeAtCreate)}</Typography> : undefined}
{shouldShowHobbsAtClose ? <Typography variant="body1">Usage at Close: {props.ticket.hobbsTimeAtClose ? formatHobbsTime(props.ticket.hobbsTimeAtClose) : "N/A"}</Typography> : undefined}
</Stack>
{
editing
? <TextField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FullMakerspace } from "../../../queries/makerspaceQueries";
import { useMemo, useState } from "react";
import { EquipmentInstance, GET_EQUIPMENT_INSTANCES } from "../../../queries/equipmentInstanceQueries";
import { useMutation, useQuery } from "@apollo/client/react";
import { CREATE_INTERVAL_MAINTENANCE_TICKET, MaintenanceTicketSeverity } from "../../../queries/maintenanceTicketQueries";
import { CREATE_INTERVAL_MAINTENANCE_TICKET, GET_MAINTENANCE_TICKETS, MaintenanceTicketSeverity, PAGINATED_MAINTENANCE_TICKETS } from "../../../queries/maintenanceTicketQueries";
import { toast } from "react-toastify";
import FileUploadButton from "../../../common/FileUploadButton";
import styled from "styled-components";
Expand All @@ -32,7 +32,7 @@ export default function NewIntervalTicketModal(props: NewTicketModalProps) {
const [imageUrl, setImageUrl] = useState<string>();

const equipmentInstancesResult = useQuery(GET_EQUIPMENT_INSTANCES, { variables: { equipmentID: equipment?.id ?? -1 } });
const [createTicket] = useMutation(CREATE_INTERVAL_MAINTENANCE_TICKET, { refetchQueries: ["PaginatedMaintenanceTickets", "MaintenanceTickets"] });
const [createTicket] = useMutation(CREATE_INTERVAL_MAINTENANCE_TICKET, { refetchQueries: [{query: PAGINATED_MAINTENANCE_TICKETS}, {query: GET_MAINTENANCE_TICKETS}] });

const EMPTY_ARRAY: EquipmentInstance[] = [];
const instances: EquipmentInstance[] = equipmentInstancesResult.data?.equipmentInstances ?? EMPTY_ARRAY;
Expand All @@ -50,14 +50,20 @@ export default function NewIntervalTicketModal(props: NewTicketModalProps) {
const makerspace_equipments = makerspace_equipments_2?.flat(1);

const [startDate, setStartDate] = useState(new Date());
const [scale, setScale] = useState("days");
const [timeUnit, setTimeUnit] = useState(""); // USAGE | CALENDAR
const [scale, setScale] = useState("days"); // hours | days | weeks
const [interval, setInterval] = useState("1");

function handleChangeScale(_event: React.MouseEvent<HTMLElement>, value: string) {
if (value !== null && value !== scale) {
setScale(value);
}
}
function handleChangeTimeUnit(_event: React.MouseEvent<HTMLElement>, value: string) {
if (value !== null && value !== timeUnit) {
setTimeUnit(value);
}
}

function handleClose() {
setEquipment(undefined);
Expand All @@ -67,25 +73,29 @@ export default function NewIntervalTicketModal(props: NewTicketModalProps) {
setImageUrl(undefined);
setStartDate(new Date());
setScale("days");
setTimeUnit("CALENDAR")
setInterval("1");

props.onClose();
}

async function handleCreateTicket() {
if (!(equipment && reportedInstance && severity && !Number.isNaN(Number(interval)))) {
toast.error("A required field is empty!");
if (!(equipment && reportedInstance && severity && !Number.isNaN(Number(interval)) && (timeUnit == "USAGE" || timeUnit == "CALENDAR") ) ) {
toast.error("A required field is empty or invalid!");
return;
}
try {
const intervalHours = scale === "days" ? Math.floor(Number(interval) * 24) : (scale == "weeks") ? Math.floor(Number(interval) * 168) : Math.floor(Number(interval))

await createTicket({
variables: {
severity: severity,
instanceID: Number(reportedInstance.id),
description: description,
startDate: startDate.toISOString(),
imageUrl: imageUrl,
intervalHours: scale === "days" ? Number(interval) * 24 : Number(interval) * 168
intervalHours: intervalHours,
timeUnit: timeUnit,
}
})
} catch (e) {
Expand Down Expand Up @@ -127,6 +137,7 @@ export default function NewIntervalTicketModal(props: NewTicketModalProps) {
{
equipment
? <Autocomplete
fullWidth
key={instances.length === 1 ? "auto-selected" : "manual-select"}
renderInput={
(params) => (
Expand Down Expand Up @@ -159,21 +170,40 @@ export default function NewIntervalTicketModal(props: NewTicketModalProps) {
disabled
/>
}
<Autocomplete
renderInput={
(params) => (
<TextField
{...params}
label="Severity"
placeholder="Select Severity..."
required
/>
)
}
options={[MaintenanceTicketSeverity.HIGH, MaintenanceTicketSeverity.MEDIUM, MaintenanceTicketSeverity.LOW]}
value={severity}
onChange={(event, newValue) => setSeverity(newValue ?? undefined)}
/>
<Stack direction={"row"} spacing={1}>
<Autocomplete
renderInput={
(params) => (
<TextField
{...params}
label="Severity"
placeholder="Select Severity..."
required
/>
)
}
options={[MaintenanceTicketSeverity.HIGH, MaintenanceTicketSeverity.MEDIUM, MaintenanceTicketSeverity.LOW]}
value={severity}
onChange={(event, newValue) => setSeverity(newValue ?? undefined)}
sx={{flexGrow: 1}}
/>

<ToggleButtonGroup
exclusive
value={timeUnit}
onChange={handleChangeTimeUnit}
>
<ToggleButton value="USAGE">
Usage
</ToggleButton>
<ToggleButton value="CALENDAR">
Calendar
</ToggleButton>
</ToggleButtonGroup>


</Stack>

<Stack direction={"row"} justifyContent={"space-between"}>
<DatePicker
value={startDate}
Expand All @@ -196,11 +226,15 @@ export default function NewIntervalTicketModal(props: NewTicketModalProps) {
width: "120px"
}}
/>

<ToggleButtonGroup
exclusive
value={scale}
onChange={handleChangeScale}
>
<ToggleButton value="hours">
Hours
</ToggleButton>
<ToggleButton value="days">
Days
</ToggleButton>
Expand Down
15 changes: 15 additions & 0 deletions client/src/queries/equipmentInstanceQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ export interface EquipmentInstance {
accessController: AccessController | undefined;
hobbsTime: number
}
export function formatHobbsTime(allSeconds: number){
const hours = Math.floor(allSeconds / 3600);
if (hours > 0){
return (allSeconds / 3600).toFixed(2) + " hrs"
}
const minutes = Math.floor((allSeconds % 3600) / 60);
const seconds = allSeconds % 60;
if (minutes > 0){
return (minutes + seconds / 50).toFixed(2) + " min"
} else {
return Math.floor(allSeconds) + "sec"
}
}



export const GET_EQUIPMENT_INSTANCES = gql`
query EquipmentInstances($equipmentID: ID!) {
Expand Down
Loading
Loading