- PR Change Requests (Progress)

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-04-10 16:48:06 -04:00
parent 693d02de87
commit 3d753a2d19
15 changed files with 186 additions and 94 deletions

View File

@@ -0,0 +1,48 @@
import { DatePicker } from "antd";
import dayjs from "../../utils/day.js";
import React, { useRef } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced);
const dateFormat = "MM/DD/YYYY h:mm a";
export function FormDateTimePickerEnhanced({
bodyshop,
value,
onBlur,
onlyFuture,
onlyToday,
isDateOnly = true,
...restProps
}) {
const ref = useRef();
return (
<div>
<DatePicker
ref={ref}
value={value ? dayjs(value) : null}
format={dateFormat}
onBlur={onBlur}
showToday={false}
disabledDate={(d) => {
if (onlyToday) {
return !dayjs().isSame(d, "day");
} else if (onlyFuture) {
return dayjs().subtract(1, "day").isAfter(d);
}
}}
{...restProps}
/>
</div>
);
}

View File

@@ -7,23 +7,21 @@ import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import TaskListContainer from "../task-list/task-list.container.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
export function JobLinesExpander({ jobline, jobid, bodyshop }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only",
@@ -133,8 +131,6 @@ export function JobLinesExpander({ jobline, jobid, bodyshop, currentUser }) {
</Col>
<Col md={24} lg={24}>
<TaskListContainer
currentUser={currentUser}
bodyshop={bodyshop}
parentJobId={jobid}
relationshipType={"joblineid"}
relationshipId={jobline.id}

View File

@@ -334,19 +334,24 @@ export function JobLinesComponent({
>
<EditFilled />
</Button>
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
joblineid: record.id
}
});
}}
>
<FaTasks />
</Button>
</>
)}
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
joblineid: record.id
}
});
}}
>
<FaTasks />
</Button>
{(record.manual_line || jobIsPrivate) && (
<>
<Button
disabled={jobRO}
onClick={async () => {

View File

@@ -15,7 +15,7 @@ import {
PlusCircleFilled,
SyncOutlined
} from "@ant-design/icons";
import { DateFormatter } from "../../utils/DateFormatter.jsx";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
import { connect } from "react-redux";
import { setModalContext } from "../../redux/modals/modals.actions";
@@ -33,12 +33,26 @@ const DueDateRecord = ({ dueDate }) => {
const isBeforeToday = dueDateDayjs.isBefore(dayjs());
return (
<div title={relativeDueDate} style={{ color: isBeforeToday ? "red" : "black" }}>
<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
@@ -68,7 +82,7 @@ const PriorityLabel = ({ priority }) => {
default:
return (
<div>
None <ExclamationCircleFilled style={{ marginLeft: "5px", color: "black" }} />
None <ExclamationCircleFilled style={{ marginLeft: "5px" }} />
</div>
);
}
@@ -79,9 +93,7 @@ const mapDispatchToProps = (dispatch) => ({
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
const mapStateToProps = (state) => ({
// Existing state props...
});
const mapStateToProps = (state) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
@@ -99,6 +111,7 @@ function TaskListComponent({
relationshipId,
onlyMine,
parentJobId,
query,
showRo = true
}) {
const { t } = useTranslation();
@@ -111,6 +124,17 @@ function TaskListComponent({
const history = useNavigate();
const columns = [];
columns.push({
title: t("tasks.fields.created_at"),
dataIndex: "created_at",
key: "created_at",
width: "8%",
defaultSortOrder: "ascend",
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"),
@@ -204,7 +228,7 @@ function TaskListComponent({
sorter: true,
sortOrder: sortcolumn === "remind_at" && sortorder,
width: "8%",
render: (text, record) => <DueDateRecord dueDate={record.remind_at} />
render: (text, record) => <RemindAtRecord remindAt={record.remind_at} />
},
{
title: t("tasks.fields.priority"),
@@ -226,7 +250,8 @@ function TaskListComponent({
onClick={() => {
setTaskUpsertContext({
context: {
existingTask: record
existingTask: record,
query
}
});
}}
@@ -252,10 +277,11 @@ function TaskListComponent({
actions: {},
context: {
jobid: parentJobId,
[relationshipType]: relationshipId
[relationshipType]: relationshipId,
query
}
});
}, [parentJobId, relationshipId, relationshipType, setTaskUpsertContext]);
}, [parentJobId, relationshipId, relationshipType, setTaskUpsertContext, query]);
const handleTableChange = (pagination, filters, sorter) => {
search.page = pagination.current;

View File

@@ -8,11 +8,22 @@ import React from "react";
import TaskListComponent from "./task-list.component.jsx";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { connect, useDispatch } from "react-redux";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
export default function TaskListContainer({
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TaskListContainer);
export function TaskListContainer({
bodyshop,
titleTranslation,
query,
@@ -140,7 +151,6 @@ export default function TaskListContainer({
return (
<TaskListComponent
bodyshop={bodyshop}
loading={loading}
tasks={data ? data.tasks : null}
total={data ? data.tasks_aggregate.aggregate.count : 0}
@@ -153,6 +163,8 @@ export default function TaskListContainer({
onlyMine={onlyMine}
showRo={showRo}
parentJobId={parentJobId}
bodyshop={bodyshop}
query={query}
/>
);
}

View File

@@ -1,4 +1,4 @@
import { Col, DatePicker, Form, Input, Row, Select, Switch } from "antd";
import { Col, Form, Input, Row, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
@@ -9,6 +9,7 @@ import dayjs from "../../utils/day";
import { connect } from "react-redux";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -194,12 +195,12 @@ export function TaskUpsertModalComponent({
</Col>
<Col span={8}>
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
<FormDatePicker format="MM/DD/YYYY" presets={datePickerPresets} />
<FormDatePicker onlyFuture format="MM/DD/YYYY" presets={datePickerPresets} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label={t("tasks.fields.remind_at")} name="remind_at">
<DatePicker showTime minuteStep={15} format="MM/DD/YYYY h:mm a" presets={datePickerPresets} />
<FormDateTimePickerEnhanced onlyFuture showTime minuteStep={15} presets={datePickerPresets} />
</Form.Item>
</Col>
</Row>

View File

@@ -34,7 +34,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
const { open, context, actions } = taskUpsert;
const { jobid, joblineid, billid, partsorderid, taskId, existingTask } = context;
const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query } = context;
const { refetch } = actions;
const [form] = Form.useForm();
const [selectedJobId, setSelectedJobId] = useState(null);
@@ -54,7 +54,6 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
variables: { id: taskId },
skip: !taskId
});
// Use Effect to hydrate existing task if only a taskid is provided
useEffect(() => {
if (!taskLoading && !taskError && taskData && taskData?.tasks_by_pk) {
@@ -113,12 +112,19 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
const handleExistingTask = async (values) => {
const isAssignedToDirty = values.assigned_to !== existingTask.assigned_to;
const taskData = await updateTask({
const taskObject = {
variables: {
taskId: existingTask.id,
task: replaceUndefinedWithNull(values)
}
});
},
refetchQueries: ["GET_JOB_BY_PK"]
};
if (query && Object.keys(query).length) {
taskObject.refetchQueries.push(Object.keys(query)[0]);
}
const taskData = await updateTask(taskObject);
if (!taskData.errors) {
const oldTask = taskData?.data?.update_tasks?.returning[0];
@@ -164,7 +170,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
};
const handleNewTask = async (values) => {
const newTaskData = await insertTask({
const newTaskObject = {
variables: {
taskInput: [
{
@@ -174,16 +180,27 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
}
]
},
update(cache, { data }) {
cache.modify({
fields: {
tasks(cached) {
return [...data?.insert_tasks?.returning, ...cached];
}
}
});
}
});
refetchQueries: ["GET_JOB_BY_PK"]
// update(cache, { data }) {
// cache.modify({
// fields: {
// tasks(cached) {
// const newTasks = data?.insert_tasks?.returning.map(task => cache.writeFragment({
// data: task,
// fragment: PARTIAL_TASK_FIELDS_WRAPPER
// }));
// return [...cached, ...newTasks];
// }
// }
// });
// }
};
if (query && Object.keys(query).length) {
newTaskObject.refetchQueries.push(Object.keys(query)[0]);
}
const newTaskData = await insertTask(newTaskObject);
const newTask = newTaskData?.data?.insert_tasks?.returning[0];
const newTaskID = newTask?.id;
@@ -237,6 +254,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
*/
const handleFinish = async (formValues) => {
const { ...values } = formValues;
if (existingTask) {
await handleExistingTask(values);
} else {

View File

@@ -1,6 +1,6 @@
import { gql } from "@apollo/client";
const PARTIAL_TASK_FIELDS = gql`
export const PARTIAL_TASK_FIELDS = gql`
fragment TaskFields on tasks {
id
created_at
@@ -63,6 +63,10 @@ const PARTIAL_TASK_FIELDS = gql`
}
`;
export const PARTIAL_TASK_FIELDS_WRAPPER = gql`
${PARTIAL_TASK_FIELDS}
`;
export const QUERY_GET_TASK_BY_ID = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_GET_TASK_BY_ID($id: uuid!) {

View File

@@ -45,7 +45,7 @@ import ScheduleJobModalContainer from "../../components/schedule-job-modal/sched
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import UndefinedToNull from "../../utils/undefinedtonull";
import _ from "lodash";
@@ -58,8 +58,7 @@ import { QUERY_JOB_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
@@ -85,8 +84,6 @@ export function JobsDetailPage({
jobRO,
job,
mutationUpdateJob,
handleSubmit,
currentUser,
insertAuditTrail,
refetch
}) {
@@ -394,8 +391,6 @@ export function JobsDetailPage({
),
children: (
<TaskListContainer
currentUser={currentUser}
bodyshop={bodyshop}
relationshipType={"jobid"}
relationshipId={job.id}
query={{ QUERY_JOB_TASKS_PAGINATED }}

View File

@@ -7,15 +7,11 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import TaskPageTypes from "./taskPageTypes.jsx";
import { setModalContext } from "../../redux/modals/modals.actions.js";
import { useLocation } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -23,13 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function AllTasksPageContainer({
bodyshop,
currentUser,
setBreadcrumbs,
setSelectedHeader,
setTaskUpsertContext
}) {
export function AllTasksPageContainer({ setBreadcrumbs, setSelectedHeader, setTaskUpsertContext }) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
useEffect(() => {
@@ -64,7 +54,7 @@ export function AllTasksPageContainer({
}
}, [setTaskUpsertContext, searchParams]);
return <TasksPageComponent type={TaskPageTypes.ALL_TASKS} currentUser={currentUser} bodyshop={bodyshop} />;
return <TasksPageComponent type={TaskPageTypes.ALL_TASKS} />;
}
export default connect(mapStateToProps, mapDispatchToProps)(AllTasksPageContainer);

View File

@@ -6,20 +6,16 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import TaskPageTypes from "./taskPageTypes.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function MyTasksPageContainer({ bodyshop, currentUser, setBreadcrumbs, setSelectedHeader }) {
export function MyTasksPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.my_tasks", {
@@ -38,7 +34,7 @@ export function MyTasksPageContainer({ bodyshop, currentUser, setBreadcrumbs, se
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return <TasksPageComponent type={TaskPageTypes.MY_TASKS} currentUser={currentUser} bodyshop={bodyshop} />;
return <TasksPageComponent type={TaskPageTypes.MY_TASKS} />;
}
export default connect(mapStateToProps, mapDispatchToProps)(MyTasksPageContainer);

View File

@@ -2,8 +2,15 @@ import React from "react";
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
import { QUERY_ALL_TASKS_PAGINATED, QUERY_MY_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import taskPageTypes from "./taskPageTypes.jsx";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors.js";
export default function TasksPageComponent({ bodyshop, currentUser, type }) {
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({});
export default function TasksPageComponent({ currentUser, type }) {
switch (type) {
case taskPageTypes.MY_TASKS:
return (
@@ -12,20 +19,11 @@ export default function TasksPageComponent({ bodyshop, currentUser, type }) {
relationshipId={currentUser.email}
relationshipType={"user"}
query={{ QUERY_MY_TASKS_PAGINATED }}
bodyshop={bodyshop}
titleTranslation={"tasks.titles.my_tasks"}
currentUser={currentUser}
/>
);
case taskPageTypes.ALL_TASKS:
return (
<TaskListContainer
query={{ QUERY_ALL_TASKS_PAGINATED }}
bodyshop={bodyshop}
titleTranslation={"tasks.titles.all_tasks"}
currentUser={currentUser}
/>
);
return <TaskListContainer query={{ QUERY_ALL_TASKS_PAGINATED }} titleTranslation={"tasks.titles.all_tasks"} />;
default:
return <></>;
}

View File

@@ -2205,6 +2205,7 @@
"medium": "Medium",
"high": "High"
},
"created_at": "Created At",
"jobline": "Job Line",
"parts_order": "Parts Order",
"bill": "Bill",

View File

@@ -2199,6 +2199,7 @@
"assigned_to": ""
},
"fields": {
"created_at": "" ,
"jobline": "",
"parts_order": "",
"bill": "",

View File

@@ -2199,6 +2199,7 @@
"assigned_to": ""
},
"fields": {
"created_at": "",
"jobline": "",
"parts_order": "",
"bill": "",