- Optimization and Edgecases
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -6,49 +6,31 @@ import {
|
|||||||
PauseCircleOutlined
|
PauseCircleOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Col, Row, Space, Tooltip } from "antd";
|
import { Card, Col, Row, Space, Tooltip } from "antd";
|
||||||
import React, { useMemo, useCallback } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
|
||||||
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
|
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
|
||||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||||
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
||||||
|
|
||||||
import "./production-board-card.styles.scss";
|
import "./production-board-card.styles.scss";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
|
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the color of the card based on the total hours
|
|
||||||
* @param ssbuckets
|
|
||||||
* @param totalHrs
|
|
||||||
* @returns {{r: number, b: number, g: number}}
|
|
||||||
*/
|
|
||||||
const cardColor = (ssbuckets, totalHrs) => {
|
const cardColor = (ssbuckets, totalHrs) => {
|
||||||
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
||||||
return bucket && bucket.color ? bucket.color.rgb || bucket.color : { r: 255, g: 255, b: 255 };
|
return bucket && bucket.color ? bucket.color.rgb || bucket.color : { r: 255, g: 255, b: 255 };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the contrast color based on the background color
|
|
||||||
* @param bgColor
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
const getContrastYIQ = (bgColor) =>
|
const getContrastYIQ = (bgColor) =>
|
||||||
(bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white";
|
(bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white";
|
||||||
|
|
||||||
/**
|
|
||||||
* Production Board Card component
|
|
||||||
* @param technician
|
|
||||||
* @param card
|
|
||||||
* @param bodyshop
|
|
||||||
* @param cardSettings
|
|
||||||
* @returns {Element}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) {
|
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { metadata } = card;
|
const { metadata } = card;
|
||||||
|
|
||||||
const employee_body = useMemo(
|
const employee_body = useMemo(
|
||||||
@@ -90,9 +72,8 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
|
|||||||
className="react-trello-card"
|
className="react-trello-card"
|
||||||
size="small"
|
size="small"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
|
||||||
cardSettings && cardSettings.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
|
color: cardSettings?.cardcolor && contrastYIQ,
|
||||||
color: cardSettings && cardSettings.cardcolor && contrastYIQ,
|
|
||||||
maxWidth: "250px",
|
maxWidth: "250px",
|
||||||
margin: "5px"
|
margin: "5px"
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useApolloClient } from "@apollo/client";
|
|||||||
import Board from "../../components/trello-board/index";
|
import Board from "../../components/trello-board/index";
|
||||||
import { Button, notification, Skeleton, Space, Statistic } from "antd";
|
import { Button, notification, Skeleton, Space, Statistic } from "antd";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState, useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -49,13 +49,11 @@ export function ProductionBoardKanbanComponent({
|
|||||||
associationSettings
|
associationSettings
|
||||||
}) {
|
}) {
|
||||||
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
|
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
|
||||||
|
|
||||||
const [filter, setFilter] = useState({ search: "", employeeId: null });
|
const [filter, setFilter] = useState({ search: "", employeeId: null });
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isMoving, setIsMoving] = useState(false);
|
const [isMoving, setIsMoving] = useState(false);
|
||||||
|
|
||||||
const orientation = associationSettings?.kanban_settings?.orientation ? "vertical" : "horizontal";
|
const orientation = associationSettings?.kanban_settings?.orientation ? "vertical" : "horizontal";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -65,13 +63,12 @@ export function ProductionBoardKanbanComponent({
|
|||||||
}, [associationSettings]);
|
}, [associationSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newBoardData = createFakeBoardData(
|
const newBoardData = createBoardData(
|
||||||
[...bodyshop.md_ro_statuses.production_statuses, ...(bodyshop.md_ro_statuses.additional_board_statuses || [])],
|
[...bodyshop.md_ro_statuses.production_statuses, ...(bodyshop.md_ro_statuses.additional_board_statuses || [])],
|
||||||
data,
|
data,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build Board Lanes Data
|
|
||||||
newBoardData.lanes = newBoardData.lanes.map((lane) => ({
|
newBoardData.lanes = newBoardData.lanes.map((lane) => ({
|
||||||
...lane,
|
...lane,
|
||||||
title: `${lane.title} (${lane.cards.length})`
|
title: `${lane.title} (${lane.cards.length})`
|
||||||
@@ -89,13 +86,7 @@ export function ProductionBoardKanbanComponent({
|
|||||||
|
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
/**
|
const getCardByID = useCallback((data, cardId) => {
|
||||||
* Get Card By ID
|
|
||||||
* @param data
|
|
||||||
* @param cardId
|
|
||||||
* @returns {*|any|null}
|
|
||||||
*/
|
|
||||||
const getCardByID = (data, cardId) => {
|
|
||||||
for (const lane of data.lanes) {
|
for (const lane of data.lanes) {
|
||||||
for (const card of lane.cards) {
|
for (const card of lane.cards) {
|
||||||
if (card.id === cardId) {
|
if (card.id === cardId) {
|
||||||
@@ -104,88 +95,90 @@ export function ProductionBoardKanbanComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const onDragEnd = async ({ type, source, destination, draggableId }) => {
|
const onDragEnd = useCallback(
|
||||||
logImEXEvent("kanban_drag_end");
|
async ({ type, source, destination, draggableId }) => {
|
||||||
|
logImEXEvent("kanban_drag_end");
|
||||||
|
|
||||||
// Early gate, also if the card is already moving bail
|
if (!type || type !== "lane" || !source || !destination || isMoving) return;
|
||||||
if (!type || type !== "lane" || !source || !destination || isMoving) return;
|
|
||||||
|
|
||||||
setIsMoving(true);
|
setIsMoving(true);
|
||||||
|
|
||||||
const targetLane = boardLanes.lanes.find((lane) => lane.id === destination.droppableId);
|
const targetLane = boardLanes.lanes.find((lane) => lane.id === destination.droppableId);
|
||||||
const sourceLane = boardLanes.lanes.find((lane) => lane.id === source.droppableId);
|
const sourceLane = boardLanes.lanes.find((lane) => lane.id === source.droppableId);
|
||||||
|
|
||||||
const sameColumnTransfer = source.droppableId === destination.droppableId;
|
const sameColumnTransfer = source.droppableId === destination.droppableId;
|
||||||
const sourceCard = getCardByID(boardLanes, draggableId);
|
const sourceCard = getCardByID(boardLanes, draggableId);
|
||||||
|
|
||||||
const movedCardWillBeFirst = destination.index === 0;
|
const movedCardWillBeFirst = destination.index === 0;
|
||||||
const movedCardWillBeLast = destination.index > targetLane.cards.length - 1;
|
const movedCardWillBeLast = destination.index > targetLane.cards.length - 1;
|
||||||
|
|
||||||
const lastCardInTargetLane = targetLane.cards[targetLane.cards.length - 1];
|
const lastCardInTargetLane = targetLane.cards[targetLane.cards.length - 1];
|
||||||
|
|
||||||
const oldChildCard = sourceLane.cards[destination.index + 1];
|
const oldChildCard = sourceLane.cards[destination.index + 1];
|
||||||
|
|
||||||
const newChildCard = movedCardWillBeLast
|
const newChildCard = movedCardWillBeLast
|
||||||
? null
|
? null
|
||||||
: targetLane.cards[
|
: targetLane.cards[
|
||||||
sameColumnTransfer
|
sameColumnTransfer
|
||||||
? destination.index - destination.index > 0
|
? destination.index - destination.index > 0
|
||||||
? destination.index
|
? destination.index
|
||||||
: destination.index + 1
|
: destination.index + 1
|
||||||
: destination.index
|
: destination.index
|
||||||
];
|
];
|
||||||
|
|
||||||
const oldChildCardNewParent = oldChildCard ? sourceCard.metadata.kanbanparent : null;
|
const oldChildCardNewParent = oldChildCard ? sourceCard.metadata.kanbanparent : null;
|
||||||
|
|
||||||
let movedCardNewKanbanParent;
|
let movedCardNewKanbanParent;
|
||||||
if (movedCardWillBeFirst) {
|
if (movedCardWillBeFirst) {
|
||||||
movedCardNewKanbanParent = "-1";
|
movedCardNewKanbanParent = "-1";
|
||||||
} else if (movedCardWillBeLast) {
|
} else if (movedCardWillBeLast) {
|
||||||
movedCardNewKanbanParent = lastCardInTargetLane.id;
|
movedCardNewKanbanParent = lastCardInTargetLane.id;
|
||||||
} else if (!!newChildCard) {
|
} else if (!!newChildCard) {
|
||||||
movedCardNewKanbanParent = newChildCard.metadata.kanbanparent;
|
movedCardNewKanbanParent = newChildCard.metadata.kanbanparent;
|
||||||
} else {
|
} else {
|
||||||
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
|
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
|
||||||
}
|
}
|
||||||
const newChildCardNewParent = newChildCard ? draggableId : null;
|
const newChildCardNewParent = newChildCard ? draggableId : null;
|
||||||
try {
|
try {
|
||||||
const update = await client.mutate({
|
const update = await client.mutate({
|
||||||
mutation: generate_UPDATE_JOB_KANBAN(
|
mutation: generate_UPDATE_JOB_KANBAN(
|
||||||
oldChildCard ? oldChildCard.id : null,
|
oldChildCard ? oldChildCard.id : null,
|
||||||
oldChildCardNewParent,
|
oldChildCardNewParent,
|
||||||
draggableId,
|
draggableId,
|
||||||
movedCardNewKanbanParent,
|
movedCardNewKanbanParent,
|
||||||
targetLane.id,
|
targetLane.id,
|
||||||
newChildCard ? newChildCard.id : null,
|
newChildCard ? newChildCard.id : null,
|
||||||
newChildCardNewParent
|
newChildCardNewParent
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: draggableId,
|
jobid: draggableId,
|
||||||
operation: AuditTrailMapping.jobstatuschange(targetLane.id),
|
operation: AuditTrailMapping.jobstatuschange(targetLane.id),
|
||||||
type: "jobstatuschange"
|
type: "jobstatuschange"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (update.errors) {
|
if (update.errors) {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("production.errors.boardupdate", {
|
||||||
|
message: JSON.stringify(update.errors)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("production.errors.boardupdate", {
|
message: t("production.errors.boardupdate", {
|
||||||
message: JSON.stringify(update.errors)
|
message: error.message
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setIsMoving(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
notification["error"]({
|
[boardLanes, client, getCardByID, insertAuditTrail, isMoving, t]
|
||||||
message: t("production.errors.boardupdate", {
|
);
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setIsMoving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const totalHrs = useMemo(
|
const totalHrs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -209,36 +202,45 @@ export function ProductionBoardKanbanComponent({
|
|||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
const Header = ({ title }) => (
|
const Header = useCallback(
|
||||||
<div className="react-trello-column-header" style={{ backgroundColor: "#e3e3e3" }}>
|
({ title }) => (
|
||||||
<UnorderedListOutlined style={{ marginRight: "5px" }} /> {title}
|
<div className="react-trello-column-header" style={{ backgroundColor: "#e3e3e3" }}>
|
||||||
</div>
|
<UnorderedListOutlined style={{ marginRight: "5px" }} /> {title}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cardSettings =
|
const cardSettings = useMemo(
|
||||||
associationSettings &&
|
() =>
|
||||||
associationSettings.kanban_settings &&
|
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
|
||||||
Object.keys(associationSettings.kanban_settings).length > 0
|
? associationSettings.kanban_settings
|
||||||
? associationSettings.kanban_settings
|
: {
|
||||||
: {
|
ats: true,
|
||||||
ats: true,
|
clm_no: true,
|
||||||
clm_no: true,
|
compact: false,
|
||||||
compact: false,
|
ownr_nm: true,
|
||||||
ownr_nm: true,
|
sublets: true,
|
||||||
sublets: true,
|
ins_co_nm: true,
|
||||||
ins_co_nm: true,
|
production_note: true,
|
||||||
production_note: true,
|
employeeassignments: true,
|
||||||
employeeassignments: true,
|
scheduled_completion: true,
|
||||||
scheduled_completion: true,
|
stickyheader: false,
|
||||||
stickyheader: false,
|
cardcolor: false,
|
||||||
cardcolor: false,
|
orientation: false
|
||||||
orientation: false
|
},
|
||||||
};
|
[associationSettings]
|
||||||
|
);
|
||||||
|
|
||||||
const components = {
|
const components = useMemo(
|
||||||
Card: (cardProps) => ProductionBoardCard({ card: cardProps, technician, bodyshop, cardSettings }),
|
() => ({
|
||||||
LaneHeader: Header
|
Card: (cardProps) => (
|
||||||
};
|
<ProductionBoardCard card={cardProps} technician={technician} bodyshop={bodyshop} cardSettings={cardSettings} />
|
||||||
|
),
|
||||||
|
LaneHeader: Header
|
||||||
|
}),
|
||||||
|
[Header, bodyshop, cardSettings, technician]
|
||||||
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Skeleton active />;
|
return <Skeleton active />;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Dropdown } from "antd";
|
import { Dropdown } from "antd";
|
||||||
import React from "react";
|
import React, { useCallback, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -21,9 +21,8 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
|||||||
|
|
||||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||||
|
|
||||||
const handleAlertToggle = (e) => {
|
const handleAlertToggle = useCallback(() => {
|
||||||
logImEXEvent("production_toggle_alert");
|
logImEXEvent("production_toggle_alert");
|
||||||
//e.stopPropagation();
|
|
||||||
updateAlert({
|
updateAlert({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: record.id,
|
jobId: record.id,
|
||||||
@@ -44,10 +43,10 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (record.refetch) record.refetch();
|
if (record.refetch) record.refetch();
|
||||||
});
|
});
|
||||||
};
|
}, [updateAlert, insertAuditTrail, record]);
|
||||||
|
|
||||||
const menu = {
|
const menuItems = useMemo(
|
||||||
items: [
|
() => [
|
||||||
{
|
{
|
||||||
key: "toggleAlert",
|
key: "toggleAlert",
|
||||||
label:
|
label:
|
||||||
@@ -56,17 +55,13 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
|||||||
: t("production.labels.alerton"),
|
: t("production.labels.alerton"),
|
||||||
onClick: handleAlertToggle
|
onClick: handleAlertToggle
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
};
|
[record.production_vars, t, handleAlertToggle]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={menu} trigger={["contextMenu"]}>
|
<Dropdown menu={{ items: menuItems }} trigger={["contextMenu"]}>
|
||||||
<div
|
<div style={{ height: "19px" }}>
|
||||||
style={{
|
|
||||||
//width: "100%",
|
|
||||||
height: "19px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{record.production_vars && record.production_vars.alert ? (
|
{record.production_vars && record.production_vars.alert ? (
|
||||||
<ExclamationCircleFilled className="production-alert" />
|
<ExclamationCircleFilled className="production-alert" />
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Input, Popover, Space } from "antd";
|
import { Button, Input, Popover, Space } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useCallback, useState, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FaRegStickyNote } from "react-icons/fa";
|
import { FaRegStickyNote } from "react-icons/fa";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
@@ -19,77 +19,81 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [note, setNote] = useState((record.production_vars && record.production_vars.note) || "");
|
const [note, setNote] = useState((record.production_vars && record.production_vars.note) || "");
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||||
|
|
||||||
const handleSaveNote = (e) => {
|
const handleSaveNote = useCallback(
|
||||||
logImEXEvent("production_add_note");
|
(e) => {
|
||||||
e.stopPropagation();
|
logImEXEvent("production_add_note");
|
||||||
setOpen(false);
|
e.stopPropagation();
|
||||||
updateAlert({
|
setOpen(false);
|
||||||
variables: {
|
updateAlert({
|
||||||
jobId: record.id,
|
variables: {
|
||||||
job: {
|
jobId: record.id,
|
||||||
production_vars: {
|
job: {
|
||||||
...record.production_vars,
|
production_vars: {
|
||||||
note: note
|
...record.production_vars,
|
||||||
|
note: note
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}).then(() => {
|
||||||
}).then(() => {
|
if (record.refetch) record.refetch();
|
||||||
if (record.refetch) record.refetch();
|
});
|
||||||
});
|
},
|
||||||
};
|
[updateAlert, record, note]
|
||||||
|
);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = useCallback((e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setNote(e.target.value);
|
setNote(e.target.value);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleOpenChange = (flag) => {
|
const handleOpenChange = useCallback(
|
||||||
setOpen(flag);
|
(flag) => {
|
||||||
if (flag) setNote((record.production_vars && record.production_vars.note) || "");
|
setOpen(flag);
|
||||||
};
|
if (flag) setNote((record.production_vars && record.production_vars.note) || "");
|
||||||
|
},
|
||||||
|
[record]
|
||||||
|
);
|
||||||
|
|
||||||
|
const popoverContent = useMemo(
|
||||||
|
() => (
|
||||||
|
<div style={{ width: "30em" }}>
|
||||||
|
<Input.TextArea
|
||||||
|
rows={5}
|
||||||
|
value={note}
|
||||||
|
onChange={handleChange}
|
||||||
|
autoFocus
|
||||||
|
allowClear
|
||||||
|
style={{ marginBottom: "1em" }}
|
||||||
|
/>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={handleSaveNote} type="primary">
|
||||||
|
{t("general.actions.save")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setNoteUpsertContext({
|
||||||
|
context: {
|
||||||
|
jobId: record.id,
|
||||||
|
text: note
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("notes.actions.savetojobnotes")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[note, handleSaveNote, handleChange, record, setNoteUpsertContext, t]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover onOpenChange={handleOpenChange} open={open} content={popoverContent} trigger={["click"]}>
|
||||||
onOpenChange={handleOpenChange}
|
|
||||||
open={open}
|
|
||||||
content={
|
|
||||||
<div style={{ width: "30em" }}>
|
|
||||||
<Input.TextArea
|
|
||||||
rows={5}
|
|
||||||
value={note}
|
|
||||||
onChange={handleChange}
|
|
||||||
// onPressEnter={handleSaveNote}
|
|
||||||
autoFocus
|
|
||||||
allowClear
|
|
||||||
style={{ marginBottom: "1em" }}
|
|
||||||
/>
|
|
||||||
<Space>
|
|
||||||
<Button onClick={handleSaveNote} type="primary">
|
|
||||||
{t("general.actions.save")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
setNoteUpsertContext({
|
|
||||||
context: {
|
|
||||||
jobId: record.id,
|
|
||||||
text: note
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("notes.actions.savetojobnotes")}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
trigger={["click"]}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CheckCircleFilled, EyeInvisibleFilled } from "@ant-design/icons";
|
import { CheckCircleFilled, EyeInvisibleFilled } from "@ant-design/icons";
|
||||||
import { Button, List, notification, Popover } from "antd";
|
import { Button, List, notification, Popover } from "antd";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState, useCallback } from "react";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_JOB_LINE_SUBLET } from "../../graphql/jobs-lines.queries";
|
import { UPDATE_JOB_LINE_SUBLET } from "../../graphql/jobs-lines.queries";
|
||||||
@@ -10,79 +10,86 @@ export default function ProductionSubletsManageComponent({ subletJobLines }) {
|
|||||||
const [updateJobLine] = useMutation(UPDATE_JOB_LINE_SUBLET);
|
const [updateJobLine] = useMutation(UPDATE_JOB_LINE_SUBLET);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const subletCount = useMemo(() => {
|
const subletCount = useMemo(
|
||||||
return {
|
() => ({
|
||||||
total: subletJobLines.filter((s) => !s.sublet_ignored).length,
|
total: subletJobLines.filter((s) => !s.sublet_ignored).length,
|
||||||
outstanding: subletJobLines.filter((s) => !s.sublet_ignored && !s.sublet_completed).length
|
outstanding: subletJobLines.filter((s) => !s.sublet_ignored && !s.sublet_completed).length
|
||||||
};
|
}),
|
||||||
}, [subletJobLines]);
|
[subletJobLines]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubletMark = async (sublet, action) => {
|
const handleSubletMark = useCallback(
|
||||||
setLoading(true);
|
async (sublet, action) => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
const result = await updateJobLine({
|
const result = await updateJobLine({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: sublet.jobid,
|
jobId: sublet.jobid,
|
||||||
now: new Date(),
|
now: new Date(),
|
||||||
lineId: sublet.id,
|
lineId: sublet.id,
|
||||||
line: {
|
line: {
|
||||||
sublet_completed: action === "complete" ? !sublet.sublet_completed : false,
|
sublet_completed: action === "complete" ? !sublet.sublet_completed : false,
|
||||||
sublet_ignored: action === "ignore" ? !sublet.sublet_ignored : false
|
sublet_ignored: action === "ignore" ? !sublet.sublet_ignored : false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.errors) {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("joblines.errors.updating", {
|
||||||
|
message: JSON.stringify(result.errors)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("joblines.successes.updated")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
setLoading(false);
|
||||||
|
},
|
||||||
|
[updateJobLine, t]
|
||||||
|
);
|
||||||
|
|
||||||
if (!!result.errors) {
|
const popContent = useMemo(
|
||||||
notification["error"]({
|
() => (
|
||||||
message: t("joblines.errors.updating", {
|
<div style={{ minWidth: "20rem" }}>
|
||||||
message: JSON.stringify(result.errors)
|
<List
|
||||||
})
|
onClick={(e) => e.stopPropagation()}
|
||||||
});
|
dataSource={subletJobLines}
|
||||||
} else {
|
renderItem={(s) => (
|
||||||
notification["success"]({
|
<List.Item
|
||||||
message: t("joblines.successes.updated")
|
actions={[
|
||||||
});
|
<Button
|
||||||
}
|
key="complete"
|
||||||
setLoading(false);
|
loading={loading}
|
||||||
};
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
const popContent = (
|
handleSubletMark(s, "complete");
|
||||||
<div style={{ minWidth: "20rem" }}>
|
}}
|
||||||
<List
|
type={s.sublet_completed ? "primary" : "ghost"}
|
||||||
onClick={(e) => e.stopPropagation()}
|
>
|
||||||
dataSource={subletJobLines}
|
<CheckCircleFilled style={{ color: s.sublet_completed ? "green" : undefined }} />
|
||||||
renderItem={(s) => (
|
</Button>,
|
||||||
<List.Item
|
<Button
|
||||||
actions={[
|
key="sublet"
|
||||||
<Button
|
loading={loading}
|
||||||
key="complete"
|
onClick={(e) => {
|
||||||
loading={loading}
|
e.stopPropagation();
|
||||||
onClick={(e) => {
|
handleSubletMark(s, "ignore");
|
||||||
e.stopPropagation();
|
}}
|
||||||
handleSubletMark(s, "complete");
|
type={s.sublet_ignored ? "primary" : "ghost"}
|
||||||
}}
|
>
|
||||||
type={s.sublet_completed ? "primary" : "ghost"}
|
<EyeInvisibleFilled style={{ color: s.sublet_ignored ? "tomato" : undefined }} />
|
||||||
>
|
</Button>
|
||||||
<CheckCircleFilled color={s.sublet_completed ? "green" : null} />
|
]}
|
||||||
</Button>,
|
>
|
||||||
<Button
|
<List.Item.Meta title={s.line_desc} />
|
||||||
key="sublet"
|
</List.Item>
|
||||||
loading={loading}
|
)}
|
||||||
onClick={(e) => {
|
/>
|
||||||
e.stopPropagation();
|
</div>
|
||||||
handleSubletMark(s, "ignore");
|
),
|
||||||
}}
|
[subletJobLines, loading, handleSubletMark]
|
||||||
type={s.sublet_ignored ? "primary" : "ghost"}
|
|
||||||
>
|
|
||||||
<EyeInvisibleFilled color={s.sublet_ignored ? "tomato" : null} />
|
|
||||||
</Button>
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<List.Item.Meta title={s.line_desc} />
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,9 +100,9 @@ export default function ProductionSubletsManageComponent({ subletJobLines }) {
|
|||||||
placement="bottom"
|
placement="bottom"
|
||||||
title={t("production.labels.sublets")}
|
title={t("production.labels.sublets")}
|
||||||
>
|
>
|
||||||
<span style={{ color: subletCount.outstanding > 0 ? "tomato" : "" }}>{`${
|
<span
|
||||||
subletCount.total - subletCount.outstanding
|
style={{ color: subletCount.outstanding > 0 ? "tomato" : undefined }}
|
||||||
} / ${subletCount.total}`}</span>
|
>{`${subletCount.total - subletCount.outstanding} / ${subletCount.total}`}</span>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,12 @@ import React, { useEffect, useState } from "react";
|
|||||||
|
|
||||||
const HeightPreservingItem = ({ children, ...props }) => {
|
const HeightPreservingItem = ({ children, ...props }) => {
|
||||||
const [size, setSize] = useState(0);
|
const [size, setSize] = useState(0);
|
||||||
const { "data-known-size": knownSize = 0 } = props;
|
const knownSize = props["data-known-size"];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (knownSize !== 0) {
|
setSize((prevSize) => {
|
||||||
setSize(knownSize);
|
return knownSize === 0 ? prevSize : knownSize;
|
||||||
}
|
});
|
||||||
}, [knownSize]);
|
}, [setSize, knownSize]);
|
||||||
//
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (knownSize !== 0 && knownSize !== size) {
|
|
||||||
// setSize(knownSize);
|
|
||||||
// }
|
|
||||||
// }, [knownSize, size]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { BoardContainer } from "../index";
|
import { BoardContainer } from "../index";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { v1 } from "uuid";
|
import { v1 } from "uuid";
|
||||||
|
|
||||||
const Board = ({ id, className, components, orientation, ...additionalProps }) => {
|
const Board = ({ id, className, components, orientation, ...additionalProps }) => {
|
||||||
const [storeId] = useState(id || v1());
|
const [storeId] = useState(id || v1());
|
||||||
|
|
||||||
const allClassNames = classNames("react-trello-board", className || "");
|
const allClassNames = useMemo(() => classNames("react-trello-board", className || ""), [className]);
|
||||||
const OrientationStyle = orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical;
|
const OrientationStyle = useMemo(
|
||||||
|
() => (orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical),
|
||||||
|
[orientation, components.StyleHorizontal, components.StyleVertical]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { DragDropContext, Droppable } from "../dnd/lib";
|
import { DragDropContext, Droppable } from "../dnd/lib";
|
||||||
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import pick from "lodash/pick";
|
import pick from "lodash/pick";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
@@ -35,7 +34,6 @@ import * as actions from "../../../redux/trello/trello.actions.js";
|
|||||||
* @param {boolean} props.editable - Whether the board is editable
|
* @param {boolean} props.editable - Whether the board is editable
|
||||||
* @param {boolean} props.canAddLanes - Whether lanes can be added to the board
|
* @param {boolean} props.canAddLanes - Whether lanes can be added to the board
|
||||||
* @param {Object} props.laneStyle - The CSS styles to apply to the lanes
|
* @param {Object} props.laneStyle - The CSS styles to apply to the lanes
|
||||||
* @param {Function} props.onCardMoveAcrossLanes - Callback function when a card is moved across lanes
|
|
||||||
* @param {string} props.orientation - The orientation of the board ("horizontal" or "vertical")
|
* @param {string} props.orientation - The orientation of the board ("horizontal" or "vertical")
|
||||||
* @param {Function} props.eventBusHandle - Function to handle events from the event bus
|
* @param {Function} props.eventBusHandle - Function to handle events from the event bus
|
||||||
* @param {Object} props.reducerData - The initial data for the Redux reducer
|
* @param {Object} props.reducerData - The initial data for the Redux reducer
|
||||||
@@ -64,7 +62,6 @@ const BoardContainer = ({
|
|||||||
editable = false,
|
editable = false,
|
||||||
canAddLanes = false,
|
canAddLanes = false,
|
||||||
laneStyle,
|
laneStyle,
|
||||||
onCardMoveAcrossLanes = () => {},
|
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
eventBusHandle,
|
eventBusHandle,
|
||||||
reducerData,
|
reducerData,
|
||||||
@@ -129,43 +126,15 @@ const BoardContainer = ({
|
|||||||
}
|
}
|
||||||
}, [currentReducerData, reducerData, onDataChange]);
|
}, [currentReducerData, reducerData, onDataChange]);
|
||||||
|
|
||||||
/**
|
const onDragUpdate = () => {};
|
||||||
* onDragUpdate
|
|
||||||
* @param draggableId
|
|
||||||
* @param type
|
|
||||||
* @param source
|
|
||||||
* @param mode
|
|
||||||
* @param combine
|
|
||||||
* @param destination
|
|
||||||
*/
|
|
||||||
const onDragUpdate = ({ draggableId, type, source, mode, combine, destination }) => {};
|
|
||||||
|
|
||||||
/**
|
const onDragStart = useCallback(() => {
|
||||||
* onDragStart
|
setIsDragging(true);
|
||||||
* @type {(function({draggableId: *, type: *, source: *, mode: *}): void)|*}
|
}, []);
|
||||||
*/
|
|
||||||
const onDragStart = useCallback(
|
|
||||||
({ draggableId, type, source, mode }) => {
|
|
||||||
setIsDragging(true);
|
|
||||||
},
|
|
||||||
[setIsDragging]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
const onBeforeDragStart = () => {};
|
||||||
* onBeforeDragStart
|
|
||||||
* @param draggableId
|
|
||||||
* @param type
|
|
||||||
* @param source
|
|
||||||
* @param mode
|
|
||||||
*/
|
|
||||||
const onBeforeDragStart = ({ draggableId, type, source, mode }) => {};
|
|
||||||
|
|
||||||
/**
|
const onBeforeCapture = () => {};
|
||||||
* onBeforeCapture
|
|
||||||
* @param draggableId
|
|
||||||
* @param mode
|
|
||||||
*/
|
|
||||||
const onBeforeCapture = ({ draggableId, mode }) => {};
|
|
||||||
|
|
||||||
const getCardDetails = useCallback(
|
const getCardDetails = useCallback(
|
||||||
(laneId, cardIndex) => {
|
(laneId, cardIndex) => {
|
||||||
@@ -174,22 +143,77 @@ const BoardContainer = ({
|
|||||||
[currentReducerData]
|
[currentReducerData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const hideEditableLane = () => {
|
const hideEditableLane = useCallback(() => {
|
||||||
setAddLaneMode(false);
|
setAddLaneMode(false);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const showEditableLane = () => {
|
const showEditableLane = useCallback(() => {
|
||||||
setAddLaneMode(true);
|
setAddLaneMode(true);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const addNewLane = (params) => {
|
const addNewLane = useCallback(
|
||||||
hideEditableLane();
|
(params) => {
|
||||||
dispatch(actions.addLane(params));
|
hideEditableLane();
|
||||||
onLaneAdd(params);
|
dispatch(actions.addLane(params));
|
||||||
};
|
onLaneAdd(params);
|
||||||
|
},
|
||||||
|
[dispatch, hideEditableLane, onLaneAdd]
|
||||||
|
);
|
||||||
|
|
||||||
const passThroughProps = pick(
|
const passThroughProps = useMemo(
|
||||||
{
|
() =>
|
||||||
|
pick(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
components,
|
||||||
|
data,
|
||||||
|
draggable,
|
||||||
|
style,
|
||||||
|
onDataChange,
|
||||||
|
onCardAdd,
|
||||||
|
onCardUpdate,
|
||||||
|
onCardClick,
|
||||||
|
onBeforeCardDelete,
|
||||||
|
onCardDelete,
|
||||||
|
onLaneScroll,
|
||||||
|
onLaneClick,
|
||||||
|
onLaneAdd,
|
||||||
|
onLaneDelete,
|
||||||
|
onLaneUpdate,
|
||||||
|
editable,
|
||||||
|
canAddLanes,
|
||||||
|
laneStyle,
|
||||||
|
orientation,
|
||||||
|
eventBusHandle,
|
||||||
|
reducerData,
|
||||||
|
cardStyle,
|
||||||
|
...otherProps
|
||||||
|
},
|
||||||
|
[
|
||||||
|
"onLaneScroll",
|
||||||
|
"onLaneDelete",
|
||||||
|
"onLaneUpdate",
|
||||||
|
"onCardClick",
|
||||||
|
"onBeforeCardDelete",
|
||||||
|
"onCardDelete",
|
||||||
|
"onCardAdd",
|
||||||
|
"onCardUpdate",
|
||||||
|
"onLaneClick",
|
||||||
|
"laneSortFunction",
|
||||||
|
"draggable",
|
||||||
|
"cardDraggable",
|
||||||
|
"collapsibleLanes",
|
||||||
|
"canAddLanes",
|
||||||
|
"hideCardDeleteIcon",
|
||||||
|
"tagStyle",
|
||||||
|
"handleDragStart",
|
||||||
|
"handleDragEnd",
|
||||||
|
"cardDragClass",
|
||||||
|
"editLaneTitle",
|
||||||
|
"orientation"
|
||||||
|
]
|
||||||
|
),
|
||||||
|
[
|
||||||
id,
|
id,
|
||||||
components,
|
components,
|
||||||
data,
|
data,
|
||||||
@@ -209,66 +233,44 @@ const BoardContainer = ({
|
|||||||
editable,
|
editable,
|
||||||
canAddLanes,
|
canAddLanes,
|
||||||
laneStyle,
|
laneStyle,
|
||||||
onCardMoveAcrossLanes,
|
|
||||||
orientation,
|
orientation,
|
||||||
eventBusHandle,
|
eventBusHandle,
|
||||||
reducerData,
|
reducerData,
|
||||||
cardStyle,
|
cardStyle,
|
||||||
...otherProps
|
otherProps
|
||||||
},
|
|
||||||
[
|
|
||||||
// "onCardMoveAcrossLanes",
|
|
||||||
"onLaneScroll",
|
|
||||||
"onLaneDelete",
|
|
||||||
"onLaneUpdate",
|
|
||||||
"onCardClick",
|
|
||||||
"onBeforeCardDelete",
|
|
||||||
"onCardDelete",
|
|
||||||
"onCardAdd",
|
|
||||||
"onCardUpdate",
|
|
||||||
"onLaneClick",
|
|
||||||
"laneSortFunction",
|
|
||||||
"draggable",
|
|
||||||
"cardDraggable",
|
|
||||||
"collapsibleLanes",
|
|
||||||
"canAddLanes",
|
|
||||||
"hideCardDeleteIcon",
|
|
||||||
"tagStyle",
|
|
||||||
"handleDragStart",
|
|
||||||
"handleDragEnd",
|
|
||||||
"cardDragClass",
|
|
||||||
"editLaneTitle",
|
|
||||||
"orientation"
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLaneDrag = async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
const onLaneDrag = useCallback(
|
||||||
setIsDragging(false);
|
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
||||||
|
setIsDragging(false);
|
||||||
|
|
||||||
if (!type || type !== "lane" || !source || !destination) return;
|
if (!type || type !== "lane" || !source || !destination) return;
|
||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
actions.moveCardAcrossLanes({
|
actions.moveCardAcrossLanes({
|
||||||
fromLaneId: source.droppableId,
|
fromLaneId: source.droppableId,
|
||||||
toLaneId: destination.droppableId,
|
toLaneId: destination.droppableId,
|
||||||
cardId: draggableId,
|
cardId: draggableId,
|
||||||
index: destination.index
|
index: destination.index
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
onDragEnd({ draggableId, type, source, reason, mode, destination, combine })
|
try {
|
||||||
.catch((err) => {
|
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
|
||||||
|
} catch (err) {
|
||||||
console.error("Error in onLaneDrag", err);
|
console.error("Error in onLaneDrag", err);
|
||||||
})
|
} finally {
|
||||||
.finally(() => {
|
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
});
|
}
|
||||||
};
|
},
|
||||||
|
[dispatch, onDragEnd]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<components.BoardWrapper style={style} orientation={orientation} draggable={false}>
|
<components.BoardWrapper style={style} orientation={orientation}>
|
||||||
<PopoverWrapper>
|
<PopoverWrapper>
|
||||||
<DragDropContext
|
<DragDropContext
|
||||||
onDragEnd={onLaneDrag}
|
onDragEnd={onLaneDrag}
|
||||||
@@ -346,7 +348,6 @@ BoardContainer.propTypes = {
|
|||||||
tagStyle: PropTypes.object,
|
tagStyle: PropTypes.object,
|
||||||
cardDraggable: PropTypes.bool,
|
cardDraggable: PropTypes.bool,
|
||||||
cardDragClass: PropTypes.string,
|
cardDragClass: PropTypes.string,
|
||||||
onCardMoveAcrossLanes: PropTypes.func,
|
|
||||||
orientation: PropTypes.string,
|
orientation: PropTypes.string,
|
||||||
cardStyle: PropTypes.object
|
cardStyle: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
import React, { forwardRef, useCallback, useMemo, useState, useEffect } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { bindActionCreators } from "redux";
|
import { bindActionCreators } from "redux";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import isEqual from "lodash/isEqual";
|
|
||||||
import { v1 } from "uuid";
|
import { v1 } from "uuid";
|
||||||
|
|
||||||
import * as actions from "../../../redux/trello/trello.actions.js";
|
import * as actions from "../../../redux/trello/trello.actions.js";
|
||||||
@@ -17,7 +16,6 @@ const Lane = ({
|
|||||||
boardId,
|
boardId,
|
||||||
title,
|
title,
|
||||||
index,
|
index,
|
||||||
isDragging,
|
|
||||||
isProcessing,
|
isProcessing,
|
||||||
laneSortFunction,
|
laneSortFunction,
|
||||||
style = {},
|
style = {},
|
||||||
@@ -54,52 +52,15 @@ const Lane = ({
|
|||||||
currentPage,
|
currentPage,
|
||||||
...otherProps
|
...otherProps
|
||||||
}) => {
|
}) => {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [currentPageFinal, setCurrentPageFinal] = useState(currentPage);
|
|
||||||
const [addCardMode, setAddCardMode] = useState(false);
|
const [addCardMode, setAddCardMode] = useState(false);
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
const laneRef = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEqual(cards, currentPageFinal)) {
|
setIsVisible(false);
|
||||||
setCurrentPageFinal(currentPage);
|
const timer = setTimeout(() => setIsVisible(true), 0);
|
||||||
}
|
return () => clearTimeout(timer);
|
||||||
}, [cards, currentPage, currentPageFinal]);
|
}, [cards.length]);
|
||||||
|
|
||||||
const handleScroll = useCallback(
|
|
||||||
(evt) => {
|
|
||||||
const node = evt.target;
|
|
||||||
const elemScrollPosition = node.scrollHeight - node.scrollTop - node.clientHeight;
|
|
||||||
if (elemScrollPosition < 1 && onLaneScroll && !loading) {
|
|
||||||
const nextPage = currentPageFinal + 1;
|
|
||||||
setLoading(true);
|
|
||||||
onLaneScroll(nextPage, id).then((moreCards) => {
|
|
||||||
if ((moreCards || []).length > 0) {
|
|
||||||
actions.paginateLane({
|
|
||||||
laneId: id,
|
|
||||||
newCards: moreCards,
|
|
||||||
nextPage: nextPage
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentPageFinal, loading, onLaneScroll, id, actions]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const node = laneRef.current;
|
|
||||||
if (node) {
|
|
||||||
node.addEventListener("scroll", handleScroll);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (node) {
|
|
||||||
node.removeEventListener("scroll", handleScroll);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [handleScroll]);
|
|
||||||
|
|
||||||
const sortCards = useCallback((cards, sortFunction) => {
|
const sortCards = useCallback((cards, sortFunction) => {
|
||||||
if (!cards) return [];
|
if (!cards) return [];
|
||||||
@@ -177,7 +138,6 @@ const Lane = ({
|
|||||||
|
|
||||||
const Card = React.memo(({ provided, item: card, isDragging }) => {
|
const Card = React.memo(({ provided, item: card, isDragging }) => {
|
||||||
const onDeleteCard = () => removeCard(card.id);
|
const onDeleteCard = () => removeCard(card.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
@@ -204,12 +164,6 @@ const Lane = ({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the draggable component
|
|
||||||
* @param index
|
|
||||||
* @param item
|
|
||||||
* @returns {React.JSX.Element}
|
|
||||||
*/
|
|
||||||
const renderDraggable = (index, item) => {
|
const renderDraggable = (index, item) => {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
console.log("null Item");
|
console.log("null Item");
|
||||||
@@ -217,18 +171,18 @@ const Lane = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable draggableId={item.id} index={index} key={item.id}>
|
<Draggable draggableId={item.id} index={index} key={item.id} isDragDisabled={isProcessing}>
|
||||||
{(provided, snapshot) => <Card provided={provided} item={item} isDragging={snapshot.isDragging} />}
|
{(provided, snapshot) => <Card provided={provided} item={item} isDragging={snapshot.isDragging} />}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderAddCardLink = useCallback(
|
const renderAddCardLink = useMemo(
|
||||||
() => editable && !addCardMode && <components.AddCardLink onClick={showEditableCard} laneId={id} />,
|
() => editable && !addCardMode && <components.AddCardLink onClick={showEditableCard} laneId={id} />,
|
||||||
[editable, addCardMode, showEditableCard, id]
|
[editable, addCardMode, showEditableCard, id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderNewCardForm = useCallback(
|
const renderNewCardForm = useMemo(
|
||||||
() => addCardMode && <components.NewCardForm onCancel={hideEditableCard} laneId={id} onAdd={addNewCard} />,
|
() => addCardMode && <components.NewCardForm onCancel={hideEditableCard} laneId={id} onAdd={addNewCard} />,
|
||||||
[addCardMode, hideEditableCard, addNewCard, id]
|
[addCardMode, hideEditableCard, addNewCard, id]
|
||||||
);
|
);
|
||||||
@@ -246,12 +200,6 @@ const Lane = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the droppable component with the provided cards and the provided props from react-beautiful-dnd
|
|
||||||
* @param provided
|
|
||||||
* @param renderedCards
|
|
||||||
* @returns {Element}
|
|
||||||
*/
|
|
||||||
const renderDroppable = (provided, renderedCards) => {
|
const renderDroppable = (provided, renderedCards) => {
|
||||||
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
|
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
|
||||||
const FinalComponent = collapsed ? "div" : Component;
|
const FinalComponent = collapsed ? "div" : Component;
|
||||||
@@ -286,11 +234,8 @@ const Lane = ({
|
|||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
style={{
|
style={{
|
||||||
width: 152, // This is required and is pegged to .react-trello-card (Vertical)=
|
width: 152,
|
||||||
display: "flex",
|
display: "flex"
|
||||||
flex: "none",
|
|
||||||
alignContent: "stretch",
|
|
||||||
boxSizing: "border-box"
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -306,23 +251,22 @@ const Lane = ({
|
|||||||
reverse: 22
|
reverse: 22
|
||||||
},
|
},
|
||||||
components: { Item: HeightPreservingItem },
|
components: { Item: HeightPreservingItem },
|
||||||
itemContent: (index, item) => renderDraggable(index, item),
|
itemContent: (index, item) => renderDraggable(index, item)
|
||||||
scrollerRef: provided.innerRef
|
|
||||||
};
|
};
|
||||||
const finalComponentProps = collapsed ? {} : componentProps;
|
const finalComponentProps = collapsed ? {} : componentProps;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ height: "100%" }}>
|
||||||
<div
|
<div
|
||||||
{...provided.droppableProps}
|
{...provided.droppableProps}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
className={allClassNames}
|
className={allClassNames}
|
||||||
style={{ ...provided.droppableProps.style }}
|
style={{ ...provided.droppableProps.style }}
|
||||||
>
|
>
|
||||||
<FinalComponent {...finalComponentProps} />
|
{isVisible && <FinalComponent {...finalComponentProps} />}
|
||||||
{/*{provided.placeholder}*/}
|
{(orientation === "horizontal" || renderedCards.length === 0 || collapsed) && provided.placeholder}
|
||||||
</div>
|
</div>
|
||||||
{renderAddCardLink()}
|
{renderAddCardLink}
|
||||||
{renderNewCardForm()}
|
{renderNewCardForm}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -358,7 +302,6 @@ const Lane = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const allClassNames = classNames("react-trello-lane", collapsed ? "lane-collapsed" : "", className || "");
|
const allClassNames = classNames("react-trello-lane", collapsed ? "lane-collapsed" : "", className || "");
|
||||||
const showFooter = collapsibleLanes && cards.length > 0;
|
|
||||||
|
|
||||||
const passedProps = {
|
const passedProps = {
|
||||||
actions,
|
actions,
|
||||||
@@ -392,7 +335,6 @@ const Lane = ({
|
|||||||
currentPage,
|
currentPage,
|
||||||
...otherProps
|
...otherProps
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<components.Section
|
<components.Section
|
||||||
key={id}
|
key={id}
|
||||||
@@ -402,8 +344,7 @@ const Lane = ({
|
|||||||
>
|
>
|
||||||
{renderHeader({ id, cards, ...passedProps })}
|
{renderHeader({ id, cards, ...passedProps })}
|
||||||
{renderDragContainer()}
|
{renderDragContainer()}
|
||||||
{loading && <components.Loader />}
|
{collapsibleLanes && <components.LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />}
|
||||||
{showFooter && <components.LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />}
|
|
||||||
</components.Section>
|
</components.Section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const getSectionStyles = (props) => {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow-y: none;
|
// overflow-y: none;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return `
|
return `
|
||||||
@@ -75,7 +75,6 @@ export const StyleHorizontal = styled.div`
|
|||||||
.react-trello-lane {
|
.react-trello-lane {
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
min-height: 25px;
|
min-height: 25px;
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-trello-lane.lane-collapsed {
|
.react-trello-lane.lane-collapsed {
|
||||||
@@ -84,6 +83,9 @@ export const StyleHorizontal = styled.div`
|
|||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
.react-trello-card {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyleVertical = styled.div`
|
export const StyleVertical = styled.div`
|
||||||
@@ -167,6 +169,7 @@ export const CustomPopoverContent = styled(PopoverContent)`
|
|||||||
|
|
||||||
export const BoardWrapper = styled.div`
|
export const BoardWrapper = styled.div`
|
||||||
color: #393939;
|
color: #393939;
|
||||||
|
height: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
${getBoardWrapperStyles};
|
${getBoardWrapperStyles};
|
||||||
@@ -183,7 +186,7 @@ export const Section = styled.section`
|
|||||||
background-color: #e3e3e3;
|
background-color: #e3e3e3;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin: 2px 2px;
|
margin: 2px 2px;
|
||||||
padding: 2px;
|
height: 100%;
|
||||||
${getSectionStyles};
|
${getSectionStyles};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -319,7 +322,7 @@ export const LaneSection = styled.section`
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: auto;
|
height: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user