Files
bodyshop/client/src/components/production-board-kanban/production-board-kanban.component.jsx
2024-03-13 11:38:19 -04:00

319 lines
11 KiB
JavaScript

import {SyncOutlined} from "@ant-design/icons";
import {useApolloClient} from "@apollo/client";
import Board, {moveCard} from "@asseinfo/react-kanban";
import {Button, Grid, notification, Space, Statistic} from "antd";
import {PageHeader} from "@ant-design/pro-layout";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Sticky, StickyContainer} from "react-sticky";
import {createStructuredSelector} from "reselect";
import styled from "styled-components";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {generate_UPDATE_JOB_KANBAN} from "../../graphql/jobs.queries";
import {insertAuditTrail} from "../../redux/application/application.actions";
import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
//import "@asseinfo/react-kanban/dist/styles.css";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
import "./production-board-kanban.styles.scss";
import {createBoardData} from "./production-board-kanban.utils.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({jobid, operation, type}) =>
dispatch(insertAuditTrail({jobid, operation, type })),
});
export function ProductionBoardKanbanComponent({
data,
bodyshop,
refetch,
technician,
insertAuditTrail,
associationSettings,
}) {
const [boardLanes, setBoardLanes] = useState({
columns: [{id: "Loading...", title: "Loading...", cards: []}],
});
const [filter, setFilter] = useState({search: "", employeeId: null});
const [isMoving, setIsMoving] = useState(false);
const {t} = useTranslation();
useEffect(() => {
const boardData = createBoardData(
[
...bodyshop.md_ro_statuses.production_statuses,
...(bodyshop.md_ro_statuses.additional_board_statuses || []),
],
data,
filter
);
boardData.columns = boardData.columns.map((d) => {
return {...d, title: `${d.title} (${d.cards.length})`};
});
setBoardLanes(boardData);
setIsMoving(false);
}, [data, setBoardLanes, setIsMoving, bodyshop.md_ro_statuses, filter]);
const client = useApolloClient();
const handleDragEnd = async (card, source, destination) => {
logImEXEvent("kanban_drag_end");
setIsMoving(true);
setBoardLanes(moveCard(boardLanes, source, destination));
const sameColumnTransfer = source.fromColumnId === destination.toColumnId;
const sourceColumn = boardLanes.columns.find(
(x) => x.id === source.fromColumnId
);
const destinationColumn = boardLanes.columns.find(
(x) => x.id === destination.toColumnId
);
const movedCardWillBeFirst = destination.toPosition === 0;
const movedCardWillBeLast =
destinationColumn.cards.length - destination.toPosition < 1;
const lastCardInDestinationColumn =
destinationColumn.cards[destinationColumn.cards.length - 1];
const oldChildCard = sourceColumn.cards[source.fromPosition + 1];
const newChildCard = movedCardWillBeLast
? null
: destinationColumn.cards[
sameColumnTransfer
? source.fromPosition - destination.toPosition > 0
? destination.toPosition
: destination.toPosition + 1
: destination.toPosition
];
const oldChildCardNewParent = oldChildCard ? card.kanbanparent : null;
let movedCardNewKanbanParent;
if (movedCardWillBeFirst) {
//console.log("==> New Card is first.");
movedCardNewKanbanParent = "-1";
} else if (movedCardWillBeLast) {
// console.log("==> New Card is last.");
movedCardNewKanbanParent = lastCardInDestinationColumn.id;
} else if (!!newChildCard) {
// console.log("==> New Card is somewhere in the middle");
movedCardNewKanbanParent = newChildCard.kanbanparent;
} else {
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
}
const newChildCardNewParent = newChildCard ? card.id : null;
const update = await client.mutate({
mutation: generate_UPDATE_JOB_KANBAN(
oldChildCard ? oldChildCard.id : null,
oldChildCardNewParent,
card.id,
movedCardNewKanbanParent,
destination.toColumnId,
newChildCard ? newChildCard.id : null,
newChildCardNewParent
),
});
insertAuditTrail({
jobid: card.id,
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
type: "jobstatuschange",
});
if (update.errors) {
notification["error"]({
message: t("production.errors.boardupdate", {
message: JSON.stringify(update.errors),
}),
});
}
};
const totalHrs = data
.reduce(
(acc, val) =>
acc +
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAB = data
.reduce(
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAR = data
.reduce(
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const standardSizes = {
xs: "250",
sm: "250",
md: "250",
lg: "250",
xl: "250",
xxl: "250",
};
const compactSizes = {
xs: "150",
sm: "150",
md: "150",
lg: "150",
xl: "155",
xxl: "155",
};
const width = selectedBreakpoint
? associationSettings &&
associationSettings.kanban_settings &&
associationSettings.kanban_settings.compact
? compactSizes[selectedBreakpoint[0]]
: standardSizes[selectedBreakpoint[0]]
: "250";
const stickyHeader = {
renderColumnHeader: ({title}) => (
<Sticky>
{({
style,
// the following are also available but unused in this example
isSticky,
wasSticky,
distanceFromTop,
distanceFromBottom,
calculatedHeight,
}) => (
<div
className="react-kanban-column-header"
style={{...style, zIndex: "99", backgroundColor: "#ddd"}}
>
{title}
</div>
)}
</Sticky>
),
};
const cardSettings =
associationSettings &&
associationSettings.kanban_settings &&
Object.keys(associationSettings.kanban_settings).length > 0
? associationSettings.kanban_settings
: {
ats: true,
clm_no: true,
compact: false,
ownr_nm: true,
sublets: true,
ins_co_nm: true,
production_note: true,
employeeassignments: true,
scheduled_completion: true,
stickyheader: false,
cardcolor: false,
};
return (
<Container width={width}>
<IndefiniteLoading loading={isMoving}/>
<PageHeader
title={
<Space>
<Statistic
title={t("dashboard.titles.productionhours")}
value={totalHrs}
/>
<Statistic
title={t("dashboard.titles.labhours")}
value={totalLAB}
/>
<Statistic
title={t("dashboard.titles.larhours")}
value={totalLAR}
/>
<Statistic
title={t("appointments.labels.inproduction")}
value={data && data.length}
/>
</Space>
}
extra={
<Space wrap>
<Button onClick={() => refetch && refetch()}>
<SyncOutlined/>
</Button>
<ProductionBoardFilters
filter={filter}
setFilter={setFilter}
loading={isMoving}
/>
<ProductionBoardKanbanCardSettings
associationSettings={associationSettings}
/>
</Space>
}
/>
{cardSettings.cardcolor && (
<CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop}/>
)}
<ProductionListDetailComponent jobs={data}/>
<StickyContainer>
<Board
style={{height: "100%"}}
children={boardLanes}
disableCardDrag={isMoving}
{...(cardSettings.stickyheader && stickyHeader)}
renderCard={(card) =>
ProductionBoardCard(technician, card, bodyshop, cardSettings)
}
onCardDragEnd={handleDragEnd}
/>
</StickyContainer>
</Container>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionBoardKanbanComponent);
const Container = styled.div`
.react-kanban-card-skeleton,
.react-kanban-card,
.react-kanban-card-adder-form {
box-sizing: border-box;
max-width: ${(props) => props.width}px;
min-width: ${(props) => props.width}px;
}
`;