Skip to content

Commit

Permalink
feat: overhaul WO item view
Browse files Browse the repository at this point in the history
  • Loading branch information
trevor-anderson committed Mar 18, 2023
1 parent 5452e07 commit 853083c
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const StyledChecklistContainer = styled("div")<{ isExpanded: boolean }>(
padding: "1rem",
alignItems: "center",
justifyContent: "center",
"&: hover": {
"&:hover": {
cursor: "pointer",
backgroundColor: theme.palette.action.hover
}
Expand Down
15 changes: 15 additions & 0 deletions src/pages/WorkOrders/ItemView/ItemView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ export const WorkOrderItemView = () => {
headerComponents={
<WorkOrderItemViewHeader workOrder={MOCK_workOrder} isItemOwnedByUser={isItemOwnedByUser} />
}
sx={(theme) => ({
"& .core-content-view-header-container": {
...(theme.variables.isMobilePageLayout && {
boxShadow: theme.palette.mode === "dark" ? `0 -3px 8px 8px rgba(0,0,0,0.35)` : 3,
zIndex: 10 // <-- ensures the box-shadow appears above other elements
})
},
"& .core-content-view-section-divider": {
display: "none"
},
"& .core-content-view-children-container": {
padding: 0,
overflowY: "auto"
}
})}
>
<WorkOrderItemViewContent workOrder={MOCK_workOrder} isItemOwnedByUser={isItemOwnedByUser} />
</CoreItemView>
Expand Down
237 changes: 173 additions & 64 deletions src/pages/WorkOrders/ItemView/ItemViewContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { styled } from "@mui/material/styles";
import InfoIcon from "@mui/icons-material/Info";
import { useState } from "react";
import { styled, alpha } from "@mui/material/styles";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import CalendarIcon from "@mui/icons-material/CalendarToday";
import PersonIcon from "@mui/icons-material/Person";
import { ContactAvatar, WorkOrderCategoryChip, WorkOrderStatusChip } from "@components";
import { ItemDetails } from "@layouts/CoreItemView";
import { TabPanel, ContactAvatar, XscrollContainer } from "@components";
import { WorkOrderCategoryChip, WorkOrderStatusChip } from "@components/Chips";
import { ItemDetails, ItemDetailsGroup } from "@layouts/CoreItemView";
import { getDate, prettifyStr } from "@utils";
import { WO_ITEM_VIEW_TABS } from "./tabConfigs";
import { LocationDetails } from "./LocationDetails";
import { WorkOrderTimeline } from "./WorkOrderTimeline";
import { Checklist } from "./Checklist";
Expand All @@ -17,82 +21,187 @@ export const WorkOrderItemViewContent = ({
workOrder: WorkOrder;
isItemOwnedByUser: boolean;
}) => {
const [activeTabIndex, setActiveTabIndex] = useState<number>(0);

const handleChangeTab = (event: React.SyntheticEvent, newValue: number) => {
setActiveTabIndex(newValue);
};

// prettier-ignore
const { createdBy, assignedTo, status, priority, createdAt, location, category,
description, checklist, entryContact, entryContactPhone, contractorNotes } = workOrder;

// TODO On mobile, use Mui TABS to make the layout better

return (
<WorkOrderItemViewContentContainer id="work-order-item-view-content-container">
{/* TOP LEFT - "main" */}

<ItemDetails
gridArea="main"
header={
<>
<LocationDetails location={location} />
<ItemDetails label="Status" style={{ marginLeft: "auto" }}>
<WorkOrderItemViewContentContainer>
<Tabs
value={activeTabIndex}
onChange={handleChangeTab}
{...WO_ITEM_VIEW_TABS.A11Y_PROPS.tabsWrapperProps}
>
{WO_ITEM_VIEW_TABS.LABELS.map((tabLabel) => (
<Tab
key={`tab:${tabLabel.replace(/\s/g, "")}`}
label={tabLabel}
{...WO_ITEM_VIEW_TABS.A11Y_PROPS.tabProps[tabLabel]}
/>
))}
</Tabs>

{/* TAB PANEL: "Work Order" */}

<TabPanel
tab="Work Order"
isActive={activeTabIndex === WO_ITEM_VIEW_TABS.ACTIVE_INDICES["Work Order"]}
>
<XscrollContainer>
<div className="wo-item-view-workorder-tabpanel-top-section-grid-container">
<LocationDetails gridArea="location" location={location} />
<ItemDetails gridArea="createdAt" label="Created">
{getDate(createdAt)}
</ItemDetails>
<ItemDetails gridArea="status" label="Status">
<WorkOrderStatusChip status={status} />
</ItemDetails>
</>
}
<ItemDetails gridArea="createdBy" label="Created By">
<ContactAvatar contact={createdBy} viewContactOnClick={!isItemOwnedByUser} />
</ItemDetails>
<ItemDetails gridArea="priority" label="Priority">
{`${priority}${priority === "HIGH" && status !== "COMPLETE" ? ` ⚠️` : ""}`}
</ItemDetails>
<ItemDetails gridArea="assignedTo" label="Assigned To">
{assignedTo && (
<ContactAvatar contact={assignedTo} viewContactOnClick={isItemOwnedByUser} />
)}
</ItemDetails>
<ItemDetails gridArea="category" label="Category">
{category && <WorkOrderCategoryChip category={category} />}
</ItemDetails>
</div>
</XscrollContainer>
<ItemDetailsGroup gridArea="timeline" label="Timeline" labelIcon={<CalendarIcon />}>
<XscrollContainer>
<WorkOrderTimeline workOrder={workOrder} isItemOwnedByUser={isItemOwnedByUser} />
</XscrollContainer>
</ItemDetailsGroup>
</TabPanel>

{/* TAB PANEL: "Description" */}

<TabPanel
tab="Description"
isActive={activeTabIndex === WO_ITEM_VIEW_TABS.ACTIVE_INDICES["Description"]}
>
<ItemDetails label="Description">{description}</ItemDetails>
{
// the <Checklist /> comp has a built-in "label", hence this ternary:
checklist ? <Checklist checklist={checklist} /> : <ItemDetails label="Checklist" />
}
<ItemDetails label="Notes">{contractorNotes}</ItemDetails>
</ItemDetails>

{/* TOP RIGHT - "overview" */}

<ItemDetails gridArea="overview" label="Overview" labelIcon={<InfoIcon />}>
<ItemDetails label="Priority">
{`${priority}${(priority === "HIGH" && status !== "COMPLETE" && ` ⚠️`) || ""}`}
</ItemDetails>
<ItemDetails label="Category">
{category && <WorkOrderCategoryChip category={category} />}
</ItemDetails>
<ItemDetails label="Assigned To">
{assignedTo && (
<ContactAvatar contact={assignedTo} viewContactOnClick={isItemOwnedByUser} />
)}
</ItemDetails>
<ItemDetails label="Created By">
<ContactAvatar contact={createdBy} viewContactOnClick={!isItemOwnedByUser} />
</ItemDetails>
<ItemDetails label="Created">{getDate(createdAt)}</ItemDetails>
</ItemDetails>

{/* BOTTOM LEFT - "timeline" */}

<ItemDetails gridArea="timeline" label="Timeline" labelIcon={<CalendarIcon />}>
<WorkOrderTimeline workOrder={workOrder} isItemOwnedByUser={isItemOwnedByUser} />
</ItemDetails>

{/* BOTTOM RIGHT - "contact" */}

<ItemDetails gridArea="contact" label="Entry Contact" labelIcon={<PersonIcon />}>
<ItemDetails label="Name">{entryContact}</ItemDetails>
<ItemDetails label="Phone">{prettifyStr.phone(entryContactPhone ?? "")}</ItemDetails>
</ItemDetails>
<div className="wo-item-view-description-tabpanel-bottom-layout">
<ItemDetails label="Notes">{contractorNotes}</ItemDetails>
<ItemDetailsGroup label="Entry Contact" labelIcon={<PersonIcon />}>
<ItemDetails label="Name">{entryContact}</ItemDetails>
<ItemDetails label="Phone">{prettifyStr.phone(entryContactPhone ?? "")}</ItemDetails>
</ItemDetailsGroup>
</div>
</TabPanel>
</WorkOrderItemViewContentContainer>
);
};

const WorkOrderItemViewContentContainer = styled("div")(({ theme }) => ({
alignSelf: "center",
width: "100%",
display: "grid",
gridGap: "1rem",
gridTemplateAreas: `
'main overview'
'timeline contact'
`,
gridTemplateRows: "1fr auto",
gridTemplateColumns: "1fr auto",
// TODO Until tabs are impl'd, allow x-scroll on mobile
...(theme.variables.isMobilePageLayout && { overflowX: "auto" })
"& .MuiTabs-root": {
margin: "0 2rem",
borderWidth: "0 0 1px 0",
borderStyle: "solid",
borderColor: theme.palette.divider,

...(theme.variables.isMobilePageLayout && {
width: "calc( 100% - 2rem )",
margin: "0 1rem",

"& .MuiTabs-flexContainer": {
justifyContent: "space-around",

"& > .MuiTab-root": {
width: "45%",
padding: "0.3rem 0 0 0"
}
}
})
},

// TabPanel containers:

"& > .tab-panel": {
padding: "1.5rem 0",
display: "flex",
flexDirection: "column",
gap: "2rem",

// TAB: Summary
"&[aria-labelledby=WorkOrder-tab]": {
// X-scroll container:
"& > .x-scroll-container": {
"&::before, &::after": {
width: theme.variables.isMobilePageLayout ? "1rem" : "2rem"
},
// top-section grid layout container:
"& > .wo-item-view-workorder-tabpanel-top-section-grid-container": {
display: "grid",
gap: theme.variables.isMobilePageLayout ? "1rem 1.5rem" : "1.5rem",
gridTemplateColumns: "1fr min-content",
// For viewports under 600px wide:
gridTemplateRows: "auto repeat( 3, min-content )",
gridTemplateAreas: `
"location location"
"createdAt status"
"createdBy priority"
"assignedTo category"`,
// For viewports over 600px wide:
[theme.breakpoints.up("sm")]: {
gridTemplateAreas: `
"location createdAt"
"location status"
"createdBy priority"
"assignedTo category"`
}
}
},
// timeline ItemDetailsGroup
"& > .item-details-group:last-of-type": {
padding: theme.variables.isMobilePageLayout ? "0 1rem" : "0 2rem",

"& > .item-details-group-content": {
padding: "0.1rem 0",
backgroundColor: theme.palette.background.paper,
"& *": {
borderColor: alpha(theme.palette.divider, 0.05)
}
}
}
},

// TAB: Description
"&[aria-labelledby=Description-tab]": {
padding: theme.variables.isMobilePageLayout ? "1.5rem 1rem" : "1.5rem 2rem",

"& > div": {
"&.wo-item-view-description-tabpanel-bottom-layout": {
display: "flex",
gap: "2rem",
flexDirection: theme.variables.isMobilePageLayout ? "column" : "row",

"& > div": {
...(!theme.variables.isMobilePageLayout && {
width: "50%"
})
},

"& > .item-details-group": {
backgroundColor: theme.palette.background.paper
}
}
}
}
}
}));
19 changes: 16 additions & 3 deletions src/pages/WorkOrders/ItemView/ItemViewHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useNavigate } from "react-router-dom";
import Button from "@mui/material/Button";
import { usePageLayoutContext } from "@app";
import { PenToSquareIcon, FileInvoiceDollarIcon } from "@components";
import type { WorkOrder } from "@types";

Expand All @@ -11,6 +12,7 @@ export const WorkOrderItemViewHeader = ({
isItemOwnedByUser: boolean;
}) => {
const nav = useNavigate();
const { isMobilePageLayout } = usePageLayoutContext();

const {
onClick,
Expand All @@ -20,7 +22,7 @@ export const WorkOrderItemViewHeader = ({
? {
onClick: () => nav("/home/workorders/form", { state: { workOrder } }),
startIcon: <PenToSquareIcon />,
buttonText: "Update Work Order"
buttonText: isMobilePageLayout ? "Edit" : "Update Work Order"
}
: {
onClick: () => nav("/home/invoices/form", { state: { workOrderToInvoice: workOrder } }),
Expand All @@ -29,6 +31,13 @@ export const WorkOrderItemViewHeader = ({
};

/*
TODO Maybe convert the button/buttons into a single "Actions" button.
- If there's only 1 action, show that action as a standalone button
- If +1, show "Actions", which opens a modal with opts
TODO See https://mui.com/material-ui/react-button-group/#split-button
for a great example of an idea for an "Actions" btn.
TODO Add WO ItemView buttons for assignee:
- `Update Status`
- `Update Checklist`
Expand All @@ -41,11 +50,15 @@ export const WorkOrderItemViewHeader = ({
className="wo-item-view-header-mutation-button"
onClick={onClick}
startIcon={startIcon}
sx={{
style={{
height: "2rem",
width: "14rem",
paddingBottom: "0.16rem",
borderRadius: "1.5rem",
...(isMobilePageLayout && {
fontSize: "1rem"
})
}}
sx={{
"& svg": {
marginRight: "0.2rem",
marginBottom: "0.15rem"
Expand Down
Loading

0 comments on commit 853083c

Please sign in to comment.