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

View File

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

View File

@@ -8,6 +8,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";
const mapStateToProps = createStructuredSelector({
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.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 (
<>
@@ -80,20 +100,95 @@ export function TaskUpsertModalComponent({
label={t("tasks.fields.completed")}
name="completed"
valuePropName="checked"
initialValue={false}
rules={[
{
required: true,
},
]}
>
<Switch/>
</Form.Item>
</Col>
</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]}>
<Col span={8}>
<Form.Item
label={t("tasks.fields.assigned_to")}
name="assigned_to"
initialValue={currentUser.email}
rules={[
{
required: true,
},
]}
>
<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}>
{employee.first_name} {employee.last_name}
</Select.Option>
@@ -131,71 +226,6 @@ export function TaskUpsertModalComponent({
</Form.Item>
</Col>
</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 {createStructuredSelector} from "reselect";
import {MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK} from "../../graphql/tasks.queries";
import {
QUERY_GET_TASKS_JOB_DETAILS,
QUERY_GET_TASKS_JOB_DETAILS_BY_ID
} from "../../graphql/jobs.queries.js";
import {QUERY_GET_TASKS_JOB_DETAILS_BY_ID} from "../../graphql/jobs.queries.js";
import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectTaskUpsert} from "../../redux/modals/modals.selectors";
import {selectBodyshop, selectCurrentUser} from "../../redux/user/user.selectors";
import TaskUpsertModalComponent from "./task-upsert-modal.component";
import {replaceUndefinedWithNull} from "../../utils/undefinedtonull.js";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -41,16 +39,14 @@ export function TaskUpsertModalContainer({
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
const [jobIdState, setJobIdState] = useState(null);
const {
loading: jobDetailsLoading,
error: jobDetailsError,
data: jobDetailsData
loading: loading,
error: error,
data: data
} = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
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
@@ -72,16 +68,6 @@ export function TaskUpsertModalContainer({
}
}, [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
@@ -97,10 +83,11 @@ export function TaskUpsertModalContainer({
* Set the selected job details when the job details query is successful
*/
useEffect(() => {
if (!jobDetailsLoading && !jobDetailsError && jobDetailsData) {
setSelectedJobDetails(jobDetailsData.jobs_by_pk);
if (!loading && !error && data) {
setSelectedJobDetails(data.jobs_by_pk);
}
}, [jobDetailsLoading, jobDetailsError, jobDetailsData]);
}, [loading, error, data]);
/**
* Handle the form submit
@@ -109,13 +96,11 @@ export function TaskUpsertModalContainer({
*/
const handleFinish = async (formValues) => {
const {...values} = formValues;
if (existingTask) {
await updateTask({
variables: {
taskId: existingTask.id,
task: values,
jobid: selectedJobId,
task: replaceUndefinedWithNull(values)
},
});
@@ -128,19 +113,19 @@ export function TaskUpsertModalContainer({
await insertTask({
variables: {
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) {
cache.modify({
fields: {
tasks(existingTasks) {
return [{
...values,
jobid: selectedJobId,
created_by: currentUser.email,
bodyshopid: bodyshop.id
}, ...existingTasks]
@@ -154,7 +139,7 @@ export function TaskUpsertModalContainer({
form.resetFields();
toggleModalVisible();
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">
<TaskUpsertModalComponent form={form} loading={loading} error={error} data={data}
selectedJobId={setSelectedJobId}
selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId}
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`
query GET_JOB_FOR_CC_CONTRACT($id: uuid!) {
jobs_by_pk(id: $id) {

View File

@@ -900,6 +900,7 @@
},
"titles": {
"joblifecycle": "Job Life Cycles",
"tasks": "Tasks",
"labhours": "Total Body Hours",
"larhours": "Total Refinish Hours",
"monthlyemployeeefficiency": "Monthly Employee Efficiency",
@@ -2096,6 +2097,18 @@
}
},
"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": {
"next_week": "Next Week",
"two_weeks": "Two Weeks",
@@ -3117,6 +3130,7 @@
"accounting-receivables": "Receivables | {{app}}",
"app": "",
"bc": {
"tasks": "Tasks",
"accounting-payables": "Payables",
"accounting-payments": "Payments",
"accounting-receivables": "Receivables",

View File

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

View File

@@ -900,7 +900,8 @@
},
"titles": {
"joblifecycle": "",
"labhours": "",
"tasks": "",
"labhours": "",
"larhours": "",
"monthlyemployeeefficiency": "",
"monthlyjobcosting": "",
@@ -2095,6 +2096,18 @@
}
},
"tasks": {
"failures": {
"created": "",
"deleted": "",
"updated": "",
"completed": ""
},
"successes": {
"created": "",
"deleted": "",
"updated": "",
"completed": ""
},
"date_presets": {
"next_week": "",
"two_weeks": "",
@@ -3116,7 +3129,8 @@
"accounting-receivables": "",
"app": "",
"bc": {
"accounting-payables": "",
"tasks": "",
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",
"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) {
Object.keys(obj).forEach((key) => {
if (keys && keys.indexOf(key) >= 0) {
@@ -8,3 +18,21 @@ export default function UndefinedToNull(obj, keys) {
});
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];
}
})
);
}