383 lines
11 KiB
JavaScript
383 lines
11 KiB
JavaScript
import { Button, Card, Space, Switch, Table } from "antd";
|
|
import queryString from "query-string";
|
|
import React, { useCallback, useEffect } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
|
import { pageLimit } from "../../utils/config";
|
|
import dayjs from "../../utils/day";
|
|
import {
|
|
CheckCircleFilled,
|
|
CheckCircleOutlined,
|
|
DeleteFilled,
|
|
DeleteOutlined,
|
|
EditFilled,
|
|
ExclamationCircleFilled,
|
|
PlusCircleFilled,
|
|
SyncOutlined
|
|
} from "@ant-design/icons";
|
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
|
import { connect } from "react-redux";
|
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
|
|
|
/**
|
|
* Task List Component
|
|
* @param dueDate
|
|
* @returns {Element}
|
|
* @constructor
|
|
*/
|
|
const DueDateRecord = ({ dueDate }) => {
|
|
if (!dueDate) return <></>;
|
|
|
|
const dueDateDayjs = dayjs(dueDate);
|
|
const relativeDueDate = dueDateDayjs.fromNow();
|
|
const isBeforeToday = dueDateDayjs.isBefore(dayjs());
|
|
|
|
return (
|
|
<div title={relativeDueDate} style={isBeforeToday ? { color: "red" } : {}}>
|
|
<DateFormatter>{dueDate}</DateFormatter>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const RemindAtRecord = ({ remindAt }) => {
|
|
if (!remindAt) return <></>;
|
|
|
|
const remindAtDayjs = dayjs(remindAt);
|
|
const relativeRemindAtDate = remindAtDayjs.fromNow();
|
|
const isBeforeToday = remindAtDayjs.isBefore(dayjs());
|
|
|
|
return (
|
|
<div title={relativeRemindAtDate} style={isBeforeToday ? { color: "red" } : {}}>
|
|
<DateTimeFormatter>{remindAt}</DateTimeFormatter>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Priority Label Component
|
|
* @param priority
|
|
* @returns {Element}
|
|
* @constructor
|
|
*/
|
|
const PriorityLabel = ({ priority }) => {
|
|
switch (priority) {
|
|
case 1:
|
|
return (
|
|
<div>
|
|
High <ExclamationCircleFilled style={{ marginLeft: "5px", color: "red" }} />
|
|
</div>
|
|
);
|
|
case 2:
|
|
return (
|
|
<div>
|
|
Medium <ExclamationCircleFilled style={{ marginLeft: "5px", color: "yellow" }} />
|
|
</div>
|
|
);
|
|
case 3:
|
|
return (
|
|
<div>
|
|
Low <ExclamationCircleFilled style={{ marginLeft: "5px", color: "green" }} />
|
|
</div>
|
|
);
|
|
default:
|
|
return (
|
|
<div>
|
|
None <ExclamationCircleFilled style={{ marginLeft: "5px" }} />
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
const mapDispatchToProps = (dispatch) => ({
|
|
// Existing dispatch props...
|
|
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
|
});
|
|
|
|
const mapStateToProps = (state) => ({});
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
|
|
|
|
function TaskListComponent({
|
|
bodyshop,
|
|
loading,
|
|
tasks,
|
|
total,
|
|
titleTranslation,
|
|
refetch,
|
|
toggleCompletedStatus,
|
|
setTaskUpsertContext,
|
|
toggleDeletedStatus,
|
|
relationshipType,
|
|
relationshipId,
|
|
onlyMine,
|
|
parentJobId,
|
|
query,
|
|
showRo = true
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const location = useLocation();
|
|
|
|
const search = queryString.parse(useLocation().search);
|
|
|
|
// Extract Query Params
|
|
const { page, sortcolumn, sortorder, deleted, completed, mine } = search;
|
|
|
|
const history = useNavigate();
|
|
const columns = [];
|
|
|
|
useEffect(() => {
|
|
// This is a hack to force the page to change if the query params change (partssublet for example)
|
|
}, [location]);
|
|
|
|
columns.push({
|
|
title: t("tasks.fields.created_at"),
|
|
dataIndex: "created_at",
|
|
key: "created_at",
|
|
width: "8%",
|
|
defaultSortOrder: "descend",
|
|
sorter: true,
|
|
sortOrder: sortcolumn === "created_at" && sortorder,
|
|
render: (text, record) => <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
|
});
|
|
|
|
if (!onlyMine) {
|
|
columns.push({
|
|
title: t("tasks.fields.assigned_to"),
|
|
dataIndex: "assigned_to",
|
|
key: "assigned_to",
|
|
width: "8%",
|
|
sorter: true,
|
|
sortOrder: sortcolumn === "assigned_to" && sortorder,
|
|
render: (text, record) => {
|
|
const employee = bodyshop?.employees?.find((e) => e.user_email === record.assigned_to);
|
|
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
|
|
}
|
|
});
|
|
}
|
|
|
|
if (showRo) {
|
|
columns.push({
|
|
title: t("tasks.fields.job.ro_number"),
|
|
dataIndex: ["job", "ro_number"],
|
|
key: "job.ro_number",
|
|
width: "8%",
|
|
render: (text, record) =>
|
|
record.job ? (
|
|
<Link to={`/manage/jobs/${record.job.id}`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
|
) : (
|
|
t("general.labels.na")
|
|
)
|
|
});
|
|
}
|
|
|
|
columns.push(
|
|
{
|
|
title: t("tasks.fields.jobline"),
|
|
dataIndex: ["jobline", "id"],
|
|
key: "jobline.id",
|
|
width: "8%",
|
|
render: (text, record) => record?.jobline?.line_desc || ""
|
|
},
|
|
{
|
|
title: t("tasks.fields.parts_order"),
|
|
dataIndex: ["parts_order", "id"],
|
|
key: "part_order.id",
|
|
width: "8%",
|
|
render: (text, record) =>
|
|
record.parts_order ? (
|
|
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
|
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
|
|
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
|
|
: t("general.labels.na")}
|
|
</Link>
|
|
) : (
|
|
""
|
|
)
|
|
},
|
|
{
|
|
title: t("tasks.fields.bill"),
|
|
dataIndex: ["bill", "id"],
|
|
key: "bill.id",
|
|
width: "8%",
|
|
render: (text, record) =>
|
|
record.bill ? (
|
|
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
|
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
|
|
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
|
|
: t("general.labels.na")}
|
|
</Link>
|
|
) : (
|
|
""
|
|
)
|
|
},
|
|
{
|
|
title: t("tasks.fields.title"),
|
|
dataIndex: "title",
|
|
key: "title",
|
|
sorter: true,
|
|
sortOrder: sortcolumn === "title" && sortorder
|
|
},
|
|
{
|
|
title: t("tasks.fields.due_date"),
|
|
dataIndex: "due_date",
|
|
key: "due_date",
|
|
sorter: true,
|
|
sortOrder: sortcolumn === "due_date" && sortorder,
|
|
width: "8%",
|
|
render: (text, record) => <DueDateRecord dueDate={record.due_date} />
|
|
},
|
|
{
|
|
title: t("tasks.fields.remind_at"),
|
|
dataIndex: "remind_at",
|
|
key: "remind_at",
|
|
sorter: true,
|
|
sortOrder: sortcolumn === "remind_at" && sortorder,
|
|
width: "8%",
|
|
render: (text, record) => <RemindAtRecord remindAt={record.remind_at} />
|
|
},
|
|
{
|
|
title: t("tasks.fields.priority"),
|
|
dataIndex: "priority",
|
|
key: "priority",
|
|
sorter: true,
|
|
sortOrder: sortcolumn === "priority" && sortorder,
|
|
width: "8%",
|
|
render: (text, record) => <PriorityLabel priority={record.priority} />
|
|
},
|
|
{
|
|
title: t("tasks.fields.actions"),
|
|
key: "toggleCompleted",
|
|
width: "5%",
|
|
render: (text, record) => (
|
|
<Space direction="horizontal">
|
|
<Button
|
|
title={t("tasks.buttons.edit")}
|
|
onClick={() => {
|
|
setTaskUpsertContext({
|
|
context: {
|
|
existingTask: record,
|
|
query
|
|
}
|
|
});
|
|
}}
|
|
>
|
|
<EditFilled />
|
|
</Button>
|
|
<Button
|
|
title={t("tasks.buttons.complete")}
|
|
onClick={() => toggleCompletedStatus(record.id, record.completed)}
|
|
>
|
|
{record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
|
|
</Button>
|
|
<Button title={t("tasks.buttons.delete")} onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
|
|
{record.deleted ? <DeleteFilled /> : <DeleteOutlined />}
|
|
</Button>
|
|
</Space>
|
|
)
|
|
}
|
|
);
|
|
|
|
const handleCreateTask = useCallback(() => {
|
|
setTaskUpsertContext({
|
|
actions: {},
|
|
context: {
|
|
jobid: parentJobId,
|
|
[relationshipType]: relationshipId,
|
|
query
|
|
}
|
|
});
|
|
}, [parentJobId, relationshipId, relationshipType, setTaskUpsertContext, query]);
|
|
|
|
const handleTableChange = (pagination, filters, sorter) => {
|
|
search.page = pagination.current;
|
|
search.sortcolumn = sorter.columnKey;
|
|
search.sortorder = sorter.order;
|
|
history({ search: queryString.stringify(search) });
|
|
};
|
|
|
|
const handleSwitchChange = useCallback(
|
|
(param, value) => {
|
|
if (value) {
|
|
search[param] = "true";
|
|
} else {
|
|
delete search[param];
|
|
}
|
|
history({ search: queryString.stringify(search) });
|
|
},
|
|
[history, search]
|
|
);
|
|
|
|
const expandableRow = (record) => {
|
|
return (
|
|
<Card title={t("tasks.fields.description")} size="small">
|
|
{record.description}
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Extra actions for the tasks
|
|
* @type {Function}
|
|
*/
|
|
const tasksExtra = useCallback(() => {
|
|
return (
|
|
<Space direction="horizontal">
|
|
{!onlyMine && (
|
|
<Switch
|
|
checkedChildren={t("tasks.buttons.myTasks")}
|
|
unCheckedChildren={t("tasks.buttons.allTasks")}
|
|
title={t("tasks.titles.mine")}
|
|
checked={mine === "true"}
|
|
onChange={(value) => handleSwitchChange("mine", value)}
|
|
/>
|
|
)}
|
|
<Switch
|
|
checkedChildren={<CheckCircleFilled />}
|
|
unCheckedChildren={<CheckCircleOutlined />}
|
|
title={t("tasks.titles.completed")}
|
|
checked={completed === "true"}
|
|
onChange={(value) => handleSwitchChange("completed", value)}
|
|
/>
|
|
<Switch
|
|
checkedChildren={<DeleteOutlined />}
|
|
unCheckedChildren={<DeleteFilled />}
|
|
title={t("tasks.titles.deleted")}
|
|
checked={deleted === "true"}
|
|
onChange={(value) => handleSwitchChange("deleted", value)}
|
|
/>
|
|
<Button title={t("tasks.buttons.create")} onClick={handleCreateTask}>
|
|
<PlusCircleFilled />
|
|
{t("tasks.buttons.create")}
|
|
</Button>
|
|
<Button title={t("tasks.buttons.refresh")} onClick={() => refetch()}>
|
|
<SyncOutlined />
|
|
</Button>
|
|
</Space>
|
|
);
|
|
}, [refetch, deleted, completed, mine, onlyMine, t, handleSwitchChange, handleCreateTask]);
|
|
|
|
return (
|
|
<Card title={titleTranslation} extra={tasksExtra()}>
|
|
<Table
|
|
loading={loading}
|
|
pagination={{
|
|
pageSize: pageLimit,
|
|
current: parseInt(page || 1),
|
|
total: total,
|
|
responsive: true,
|
|
showQuickJumper: true
|
|
}}
|
|
columns={columns}
|
|
rowKey="id"
|
|
scroll={{ x: true }}
|
|
dataSource={tasks}
|
|
onChange={handleTableChange}
|
|
expandable={{
|
|
expandedRowRender: expandableRow,
|
|
rowExpandable: (record) => record.description
|
|
}}
|
|
/>
|
|
</Card>
|
|
);
|
|
}
|