- Progress commit

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-03-27 17:00:07 -04:00
parent 301c680bff
commit ae9e9f4b72
9 changed files with 237 additions and 140 deletions

View File

@@ -145,7 +145,7 @@ function TaskListComponent({
key: "priority", key: "priority",
sorter: true, sorter: true,
sortOrder: sortcolumn === "priority" && sortorder, sortOrder: sortcolumn === "priority" && sortorder,
width: '5%', width: '8%',
render: (text, record) => <PriorityLabel priority={record.priority} render: (text, record) => <PriorityLabel priority={record.priority}
/> />
}, },
@@ -157,7 +157,6 @@ function TaskListComponent({
<Space direction='horizontal'> <Space direction='horizontal'>
<Button title={t('tasks.buttons.edit')} onClick={() => { <Button title={t('tasks.buttons.edit')} onClick={() => {
setTaskUpsertContext({ setTaskUpsertContext({
actions: {},
context: { context: {
existingTask: record, existingTask: record,
}, },

View File

@@ -2,15 +2,19 @@ import queryString from "query-string";
import {useLocation} from "react-router-dom"; import {useLocation} from "react-router-dom";
import {useMutation, useQuery} from "@apollo/client"; import {useMutation, useQuery} from "@apollo/client";
import { import {
MUTATION_TOGGLE_TASK_COMPLETED, MUTATION_TOGGLE_TASK_DELETED, MUTATION_TOGGLE_TASK_COMPLETED,
MUTATION_TOGGLE_TASK_DELETED,
QUERY_MY_TASKS_PAGINATED QUERY_MY_TASKS_PAGINATED
} from "../../graphql/tasks.queries.js"; } from "../../graphql/tasks.queries.js";
import {pageLimit} from "../../utils/config.js"; import {pageLimit} from "../../utils/config.js";
import AlertComponent from "../alert/alert.component.jsx"; import AlertComponent from "../alert/alert.component.jsx";
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import TaskListComponent from "./task-list.component.jsx"; import TaskListComponent from "./task-list.component.jsx";
import {notification} from "antd";
import {useTranslation} from "react-i18next";
export default function TaskListContainer({bodyshop, currentUser}) { export default function TaskListContainer({bodyshop, currentUser}) {
const {t} = useTranslation();
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, deleted, completed} = searchParams; const {page, sortcolumn, sortorder, deleted, completed} = searchParams;
const {loading, error, data, refetch} = useQuery( const {loading, error, data, refetch} = useQuery(
@@ -45,7 +49,8 @@ export default function TaskListContainer({bodyshop, currentUser}) {
const handleTaskUpdated = () => { const handleTaskUpdated = () => {
refetch().catch((e) => { refetch().catch((e) => {
console.error(`Something went wrong fetching tasks: ${e.message || ''}`); console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
}); }; });
};
}, [refetch]); }, [refetch]);
/** /**
@@ -61,16 +66,26 @@ export default function TaskListContainer({bodyshop, currentUser}) {
*/ */
const toggleCompletedStatus = async (id, currentStatus) => { const toggleCompletedStatus = async (id, currentStatus) => {
const completed_at = !currentStatus ? new Date().toISOString() : null; const completed_at = !currentStatus ? new Date().toISOString() : null;
await toggleTaskCompleted({ try {
variables: { await toggleTaskCompleted({
id: id, variables: {
completed: !currentStatus, id: id,
completed_at: completed_at completed: !currentStatus,
} completed_at: completed_at
}); }
refetch().catch((e) => { });
console.error(`Something went wrong fetching tasks: ${e.message || ''}`); refetch().catch((e) => {
}); }; console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
});
notification["success"]({
message: t("tasks.successes.completed"),
});
} catch (err) {
notification["error"]({
message: t("tasks.failures.completed"),
});
}
};
/** /**
* Toggle task deleted mutation * Toggle task deleted mutation
@@ -85,20 +100,30 @@ export default function TaskListContainer({bodyshop, currentUser}) {
*/ */
const toggleDeletedStatus = async (id, currentStatus) => { const toggleDeletedStatus = async (id, currentStatus) => {
const deleted_at = !currentStatus ? new Date().toISOString() : null; const deleted_at = !currentStatus ? new Date().toISOString() : null;
await toggleTaskDeleted({ try {
variables: { await toggleTaskDeleted({
id: id, variables: {
deleted: !currentStatus, id: id,
deleted_at: deleted_at deleted: !currentStatus,
} deleted_at: deleted_at
}); }
refetch().catch((e) => { });
console.error(`Something went wrong fetching tasks: ${e.message || ''}`); refetch().catch((e) => {
}); console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
});
notification["success"]({
message: t("tasks.successes.deleted"),
});
} catch (err) {
notification["error"]({
message: t("tasks.failures.deleted"),
});
}
}; };
if (error) return <AlertComponent message={error.message} type="error"/>; if (error) return <AlertComponent message={error.message} type="error"/>;
return ( return (
<TaskListComponent <TaskListComponent
loading={loading} loading={loading}

View File

@@ -8,6 +8,7 @@ import dayjs from '../../utils/day';
import {connect} from "react-redux"; import {connect} from "react-redux";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -41,8 +42,27 @@ export function TaskUpsertModalComponent({
{label: t('tasks.date_presets.three_weeks'), value: dayjs().add(3, 'weeks')}, {label: t('tasks.date_presets.three_weeks'), value: dayjs().add(3, 'weeks')},
{label: t('tasks.date_presets.one_month'), value: dayjs().add(1, 'month')}, {label: t('tasks.date_presets.one_month'), value: dayjs().add(1, 'month')},
]; ];
if (loading || error) return <LoadingSkeleton active/>; const clearRelations = () => {
form.setFieldsValue({
billid: null,
partsorderid: null,
joblineid: null
});
}
/**
* Change the selected job id
* @param jobId
*/
const changeJobId = (jobId) => {
setSelectedJobId(jobId || null);
// Reset the form fields when selectedJobId changes
clearRelations();
};
if (!data || loading || error) return <LoadingSkeleton active/>;
return ( return (
<> <>
@@ -80,20 +100,95 @@ export function TaskUpsertModalComponent({
label={t("tasks.fields.completed")} label={t("tasks.fields.completed")}
name="completed" name="completed"
valuePropName="checked" valuePropName="checked"
initialValue={false}
rules={[
{
required: true,
},
]}
> >
<Switch/> <Switch/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Form.Item
name="jobid"
// initialValue={selectedJobId}
label={t("tasks.fields.jobid")}
rules={[
{
required: true,
},
]}
>
<JobSearchSelectComponent placeholder={t('tasks.placeholders.jobid')}
onSelect={changeJobId} onClear={changeJobId}/>
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={8}>
<Form.Item
label={t("tasks.fields.joblineid")}
name="joblineid"
>
<Select allowClear placeholder={t("tasks.placeholders.joblineid")}
disabled={!selectedJobDetails || !selectedJobId}>
{selectedJobDetails?.joblines?.map((jobline) => (
<Select.Option key={jobline.id} value={jobline.id}>
{jobline.line_desc}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.partsorderid")}
name="partsorderid"
>
<Select allowClear placeholder={t("tasks.placeholders.partsorderid")}
disabled={!selectedJobDetails || !selectedJobId}>
{selectedJobDetails?.parts_orders?.map((partsOrder) => (
<Select.Option key={partsOrder.id} value={partsOrder.id}>
{partsOrder.order_number} - {partsOrder.vendor.name}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.billid")}
name="billid"
>
<Select allowClear placeholder={t("tasks.placeholders.billid")}
disabled={!selectedJobDetails || !selectedJobId}>
{selectedJobDetails?.bills?.map((bill) => (
<Select.Option key={bill.id} value={bill.id}>
{bill.invoice_number} - {bill.vendor.name}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={8}> <Col span={8}>
<Form.Item <Form.Item
label={t("tasks.fields.assigned_to")} label={t("tasks.fields.assigned_to")}
name="assigned_to" name="assigned_to"
initialValue={currentUser.email} initialValue={currentUser.email}
rules={[
{
required: true,
},
]}
> >
<Select placeholder={t("tasks.labels.selectemployee")}> <Select placeholder={t("tasks.labels.selectemployee")}>
{bodyshop.employees.map((employee) => ( {bodyshop.employees.filter(x => x.active).map((employee) => (
<Select.Option key={employee.id} value={employee.user_email}> <Select.Option key={employee.id} value={employee.user_email}>
{employee.first_name} {employee.last_name} {employee.first_name} {employee.last_name}
</Select.Option> </Select.Option>
@@ -131,71 +226,6 @@ export function TaskUpsertModalComponent({
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Form.Item
name="jobid"
label={t("tasks.fields.jobid")}
rules={[
{
required: true,
},1
]}
>
<Select placeholder={t('tasks.placeholders.jobid')} defaultValue={selectedJobId} onSelect={setSelectedJobId}>
{data.jobs.map((job) => (
<Select.Option key={job.id} value={job.id}>
{job.ro_number}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={8}>
<Form.Item
label={t("tasks.fields.joblineid")}
name="joblineid"
>
<Select placeholder={t("tasks.placeholders.joblineid")} disabled={!selectedJobDetails}>
{selectedJobDetails?.joblines?.map((jobline) => (
<Select.Option key={jobline.id} value={jobline.id}>
{jobline.line_desc}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.partsorderid")}
name="partsorderid"
>
<Select placeholder={t("tasks.placeholders.partsorderid")} disabled={!selectedJobDetails}>
{selectedJobDetails?.parts_orders?.map((partsOrder) => (
<Select.Option key={partsOrder.id} value={partsOrder.id}>
{partsOrder.order_number} - {partsOrder.vendor.name}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.billid")}
name="billid"
>
<Select placeholder={t("tasks.placeholders.billid")} disabled={!selectedJobDetails}>
{selectedJobDetails?.bills?.map((bill) => (
<Select.Option key={bill.id} value={bill.id}>
{bill.invoice_number} - {bill.vendor.name}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
</> </>
); );
} }

View File

@@ -5,14 +5,12 @@ import {useTranslation} from "react-i18next";
import {connect} from "react-redux"; import {connect} from "react-redux";
import {createStructuredSelector} from "reselect"; import {createStructuredSelector} from "reselect";
import {MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK} from "../../graphql/tasks.queries"; import {MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK} from "../../graphql/tasks.queries";
import { import {QUERY_GET_TASKS_JOB_DETAILS_BY_ID} from "../../graphql/jobs.queries.js";
QUERY_GET_TASKS_JOB_DETAILS,
QUERY_GET_TASKS_JOB_DETAILS_BY_ID
} from "../../graphql/jobs.queries.js";
import {toggleModalVisible} from "../../redux/modals/modals.actions"; import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectTaskUpsert} from "../../redux/modals/modals.selectors"; import {selectTaskUpsert} from "../../redux/modals/modals.selectors";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors"; import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
import TaskUpsertModalComponent from "./task-upsert-modal.component"; import TaskUpsertModalComponent from "./task-upsert-modal.component";
import {replaceUndefinedWithNull} from "../../utils/undefinedtonull.js";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -41,16 +39,14 @@ export function TaskUpsertModalContainer({
const [selectedJobDetails, setSelectedJobDetails] = useState(null); const [selectedJobDetails, setSelectedJobDetails] = useState(null);
const [jobIdState, setJobIdState] = useState(null); const [jobIdState, setJobIdState] = useState(null);
const { const {
loading: jobDetailsLoading, loading: loading,
error: jobDetailsError, error: error,
data: jobDetailsData data: data
} = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, { } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
variables: {id: jobIdState}, variables: {id: jobIdState},
skip: !jobIdState, // Skip the query if jobIdState is null skip: !jobIdState,
}); });
const {loading, error, data} = useQuery(QUERY_GET_TASKS_JOB_DETAILS);
/** /**
* Set the selected job id when the modal is opened and jobId is passed as a prop or when an existing task is passed as a prop * Set the selected job id when the modal is opened and jobId is passed as a prop or when an existing task is passed as a prop
@@ -72,16 +68,6 @@ export function TaskUpsertModalContainer({
} }
}, [existingTask, form, open]); }, [existingTask, form, open]);
/**
* Reset the form values when the selected job id changes
*/
useEffect(() => {
form.setFieldsValue({
joblineid: undefined,
billid: undefined,
partsorderid: undefined,
});
}, [selectedJobId, form]);
/** /**
* Set the job id state when the selected job id changes * Set the job id state when the selected job id changes
@@ -97,10 +83,11 @@ export function TaskUpsertModalContainer({
* Set the selected job details when the job details query is successful * Set the selected job details when the job details query is successful
*/ */
useEffect(() => { useEffect(() => {
if (!jobDetailsLoading && !jobDetailsError && jobDetailsData) { if (!loading && !error && data) {
setSelectedJobDetails(jobDetailsData.jobs_by_pk); setSelectedJobDetails(data.jobs_by_pk);
} }
}, [jobDetailsLoading, jobDetailsError, jobDetailsData]); }, [loading, error, data]);
/** /**
* Handle the form submit * Handle the form submit
@@ -109,13 +96,11 @@ export function TaskUpsertModalContainer({
*/ */
const handleFinish = async (formValues) => { const handleFinish = async (formValues) => {
const {...values} = formValues; const {...values} = formValues;
if (existingTask) { if (existingTask) {
await updateTask({ await updateTask({
variables: { variables: {
taskId: existingTask.id, taskId: existingTask.id,
task: values, task: replaceUndefinedWithNull(values)
jobid: selectedJobId,
}, },
}); });
@@ -128,19 +113,19 @@ export function TaskUpsertModalContainer({
await insertTask({ await insertTask({
variables: { variables: {
taskInput: [ taskInput: [
{...values, jobid: selectedJobId, created_by: currentUser.email, bodyshopid: bodyshop.id}, {
...replaceUndefinedWithNull(values),
created_by: currentUser.email,
bodyshopid: bodyshop.id
},
], ],
}, },
updateQueries: {
query: QUERY_GET_TASKS_JOB_DETAILS,
},
update(cache) { update(cache) {
cache.modify({ cache.modify({
fields: { fields: {
tasks(existingTasks) { tasks(existingTasks) {
return [{ return [{
...values, ...values,
jobid: selectedJobId,
created_by: currentUser.email, created_by: currentUser.email,
bodyshopid: bodyshop.id bodyshopid: bodyshop.id
}, ...existingTasks] }, ...existingTasks]
@@ -154,7 +139,7 @@ export function TaskUpsertModalContainer({
form.resetFields(); form.resetFields();
toggleModalVisible(); toggleModalVisible();
notification["success"]({ notification["success"]({
message: t("tasks.successes.create"), message: t("tasks.successes.created"),
}); });
} }
}; };
@@ -176,7 +161,7 @@ export function TaskUpsertModalContainer({
> >
<Form form={form} onFinish={handleFinish} layout="vertical"> <Form form={form} onFinish={handleFinish} layout="vertical">
<TaskUpsertModalComponent form={form} loading={loading} error={error} data={data} <TaskUpsertModalComponent form={form} loading={loading} error={error} data={data}
selectedJobId={setSelectedJobId} selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId} setSelectedJobId={setSelectedJobId}
selectedJobDetails={selectedJobDetails}/> selectedJobDetails={selectedJobDetails}/>

View File

@@ -2156,18 +2156,6 @@ export const QUERY_GET_TASKS_JOB_DETAILS_BY_ID = gql`
} }
`; `;
export const QUERY_GET_TASKS_JOB_DETAILS = gql`
query GetTasksJobDetails {
jobs {
id
ro_number
ownr_fn
ownr_ln
ownr_co_nm
}
}
`;
export const GET_JOB_FOR_CC_CONTRACT = gql` export const GET_JOB_FOR_CC_CONTRACT = gql`
query GET_JOB_FOR_CC_CONTRACT($id: uuid!) { query GET_JOB_FOR_CC_CONTRACT($id: uuid!) {
jobs_by_pk(id: $id) { jobs_by_pk(id: $id) {

View File

@@ -900,6 +900,7 @@
}, },
"titles": { "titles": {
"joblifecycle": "Job Life Cycles", "joblifecycle": "Job Life Cycles",
"tasks": "Tasks",
"labhours": "Total Body Hours", "labhours": "Total Body Hours",
"larhours": "Total Refinish Hours", "larhours": "Total Refinish Hours",
"monthlyemployeeefficiency": "Monthly Employee Efficiency", "monthlyemployeeefficiency": "Monthly Employee Efficiency",
@@ -2096,6 +2097,18 @@
} }
}, },
"tasks": { "tasks": {
"failures": {
"created": "Failed to create Task.",
"deleted": "Failed to toggle Task deletion.",
"updated": "Failed to update Task.",
"completed": "Failed to toggle Task completion."
},
"successes": {
"created": "Task created successfully.",
"deleted": "Toggled Task deletion successfully.",
"updated": "Task updated successfully.",
"completed": "Toggled Task completion successfully."
},
"date_presets": { "date_presets": {
"next_week": "Next Week", "next_week": "Next Week",
"two_weeks": "Two Weeks", "two_weeks": "Two Weeks",
@@ -3117,6 +3130,7 @@
"accounting-receivables": "Receivables | {{app}}", "accounting-receivables": "Receivables | {{app}}",
"app": "", "app": "",
"bc": { "bc": {
"tasks": "Tasks",
"accounting-payables": "Payables", "accounting-payables": "Payables",
"accounting-payments": "Payments", "accounting-payments": "Payments",
"accounting-receivables": "Receivables", "accounting-receivables": "Receivables",

View File

@@ -900,6 +900,7 @@
}, },
"titles": { "titles": {
"joblifecycle": "", "joblifecycle": "",
"tasks": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -2095,6 +2096,18 @@
} }
}, },
"tasks": { "tasks": {
"failures": {
"created": "",
"deleted": "",
"updated": "",
"completed": ""
},
"successes": {
"created": "",
"deleted": "",
"updated": "",
"completed": ""
},
"date_presets": { "date_presets": {
"next_week": "", "next_week": "",
"two_weeks": "", "two_weeks": "",
@@ -3116,6 +3129,7 @@
"accounting-receivables": "", "accounting-receivables": "",
"app": "", "app": "",
"bc": { "bc": {
"tasks": "",
"accounting-payables": "", "accounting-payables": "",
"accounting-payments": "", "accounting-payments": "",
"accounting-receivables": "", "accounting-receivables": "",

View File

@@ -900,7 +900,8 @@
}, },
"titles": { "titles": {
"joblifecycle": "", "joblifecycle": "",
"labhours": "", "tasks": "",
"labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
"monthlyjobcosting": "", "monthlyjobcosting": "",
@@ -2095,6 +2096,18 @@
} }
}, },
"tasks": { "tasks": {
"failures": {
"created": "",
"deleted": "",
"updated": "",
"completed": ""
},
"successes": {
"created": "",
"deleted": "",
"updated": "",
"completed": ""
},
"date_presets": { "date_presets": {
"next_week": "", "next_week": "",
"two_weeks": "", "two_weeks": "",
@@ -3116,7 +3129,8 @@
"accounting-receivables": "", "accounting-receivables": "",
"app": "", "app": "",
"bc": { "bc": {
"accounting-payables": "", "tasks": "",
"accounting-payables": "",
"accounting-payments": "", "accounting-payments": "",
"accounting-receivables": "", "accounting-receivables": "",
"availablejobs": "", "availablejobs": "",

View File

@@ -1,3 +1,13 @@
/**
* Replaces undefined values with null in an object.
* Optionally, you can specify keys to replace.
* If keys are specified, only those keys will be replaced.
* If no keys are specified, all undefined values will be replaced.
* @param obj
* @param keys
* @returns {*}
* @constructor
*/
export default function UndefinedToNull(obj, keys) { export default function UndefinedToNull(obj, keys) {
Object.keys(obj).forEach((key) => { Object.keys(obj).forEach((key) => {
if (keys && keys.indexOf(key) >= 0) { if (keys && keys.indexOf(key) >= 0) {
@@ -8,3 +18,21 @@ export default function UndefinedToNull(obj, keys) {
}); });
return obj; return obj;
} }
/**
* Replaces undefined values with null in an object. Optionally, you can specify keys to replace. If keys are specified, only those keys will be replaced. If no keys are specified, all undefined values will be replaced.
* @param obj
* @param keys
* @returns {{[p: string]: unknown}}
*/
export function replaceUndefinedWithNull(obj, keys) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => {
if (keys) {
return [key, (keys.includes(key) && value === undefined) ? null : value];
} else {
return [key, value === undefined ? null : value];
}
})
);
}