@@ -17,6 +17,7 @@ import Icon, {
|
|||||||
LineChartOutlined,
|
LineChartOutlined,
|
||||||
PaperClipOutlined,
|
PaperClipOutlined,
|
||||||
PhoneOutlined,
|
PhoneOutlined,
|
||||||
|
PlusCircleOutlined,
|
||||||
QuestionCircleFilled,
|
QuestionCircleFilled,
|
||||||
ScheduleOutlined,
|
ScheduleOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
@@ -25,11 +26,11 @@ import Icon, {
|
|||||||
UnorderedListOutlined,
|
UnorderedListOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useSplitTreatments } from '@splitsoftware/splitio-react';
|
import {useSplitTreatments} from '@splitsoftware/splitio-react';
|
||||||
import { Layout, Menu, Switch, Tooltip } from 'antd';
|
import {Layout, Menu, Switch, Tooltip} from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import {useTranslation} from 'react-i18next';
|
||||||
import { BsKanban } from 'react-icons/bs';
|
import {BsKanban} from 'react-icons/bs';
|
||||||
import {
|
import {
|
||||||
FaCalendarAlt,
|
FaCalendarAlt,
|
||||||
FaCarCrash,
|
FaCarCrash,
|
||||||
@@ -37,23 +38,23 @@ import {
|
|||||||
FaFileInvoiceDollar,
|
FaFileInvoiceDollar,
|
||||||
FaTasks
|
FaTasks
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from 'react-icons/gi';
|
import {GiPayMoney, GiPlayerTime, GiSettingsKnobs} from 'react-icons/gi';
|
||||||
import { IoBusinessOutline } from 'react-icons/io5';
|
import {IoBusinessOutline} from 'react-icons/io5';
|
||||||
import { RiSurveyLine } from 'react-icons/ri';
|
import {RiSurveyLine} from 'react-icons/ri';
|
||||||
import { connect } from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import { createStructuredSelector } from 'reselect';
|
import {createStructuredSelector} from 'reselect';
|
||||||
import {
|
import {
|
||||||
selectRecentItems,
|
selectRecentItems,
|
||||||
selectSelectedHeader,
|
selectSelectedHeader,
|
||||||
} from '../../redux/application/application.selectors';
|
} from '../../redux/application/application.selectors';
|
||||||
import { setModalContext } from '../../redux/modals/modals.actions';
|
import {setModalContext} from '../../redux/modals/modals.actions';
|
||||||
import { signOutStart } from '../../redux/user/user.actions';
|
import {signOutStart} from '../../redux/user/user.actions';
|
||||||
import { selectBodyshop, selectCurrentUser } from '../../redux/user/user.selectors';
|
import {selectBodyshop, selectCurrentUser} from '../../redux/user/user.selectors';
|
||||||
import { FiLogOut } from 'react-icons/fi';
|
import {FiLogOut} from 'react-icons/fi';
|
||||||
import { checkBeta, handleBeta, setBeta } from '../../utils/betaHandler';
|
import {checkBeta, handleBeta, setBeta} from '../../utils/betaHandler';
|
||||||
import InstanceRenderManager from '../../utils/instanceRenderMgr';
|
import InstanceRenderManager from '../../utils/instanceRenderMgr';
|
||||||
import { HasFeatureAccess } from '../feature-wrapper/feature-wrapper.component';
|
import {HasFeatureAccess} from '../feature-wrapper/feature-wrapper.component';
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -73,6 +74,10 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
signOutStart: () => dispatch(signOutStart()),
|
signOutStart: () => dispatch(signOutStart()),
|
||||||
setCardPaymentContext: (context) =>
|
setCardPaymentContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: 'cardPayment' })),
|
dispatch(setModalContext({ context: context, modal: 'cardPayment' })),
|
||||||
|
setTaskUpsertContext: (context) => dispatch(setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: 'taskUpsert'
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
function Header({
|
function Header({
|
||||||
@@ -87,6 +92,7 @@ function Header({
|
|||||||
setReportCenterContext,
|
setReportCenterContext,
|
||||||
recentItems,
|
recentItems,
|
||||||
setCardPaymentContext,
|
setCardPaymentContext,
|
||||||
|
setTaskUpsertContext,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
treatments: { ImEXPay, DmsAp, Simple_Inventory },
|
treatments: { ImEXPay, DmsAp, Simple_Inventory },
|
||||||
@@ -470,7 +476,25 @@ function Header({
|
|||||||
key: 'tasks',
|
key: 'tasks',
|
||||||
id: 'tasks',
|
id: 'tasks',
|
||||||
icon: <FaTasks />,
|
icon: <FaTasks />,
|
||||||
label: <Link to="/manage/tasks">{t('menus.header.tasks')}</Link>,
|
label: t('menus.header.tasks'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'createTask',
|
||||||
|
icon: <PlusCircleOutlined/>,
|
||||||
|
label: t('menus.header.create_task'),
|
||||||
|
onClick: () => {
|
||||||
|
setTaskUpsertContext({
|
||||||
|
actions: {},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'mytasks',
|
||||||
|
icon: <FaTasks/>,
|
||||||
|
label: <Link to="/manage/tasks">{t('menus.header.tasks')}</Link>,
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'shopsubmenu',
|
key: 'shopsubmenu',
|
||||||
|
|||||||
@@ -2,58 +2,91 @@ import {Button, Card, Space, Switch, Table} from "antd";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, {useCallback, useState} from "react";
|
import React, {useCallback, useState} from "react";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useLocation, useNavigate} from "react-router-dom";
|
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||||
import {pageLimit} from "../../utils/config";
|
import {pageLimit} from "../../utils/config";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import {
|
import {
|
||||||
CheckCircleFilled,
|
CheckCircleFilled,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
DeleteFilled,
|
DeleteFilled,
|
||||||
DeleteOutlined, ExclamationCircleFilled,
|
DeleteOutlined,
|
||||||
|
EditFilled,
|
||||||
|
ExclamationCircleFilled,
|
||||||
|
PlusCircleFilled,
|
||||||
SyncOutlined
|
SyncOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
|
import {DateFormatter} 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}) => {
|
const DueDateRecord = ({dueDate}) => {
|
||||||
if (dueDate) {
|
if (dueDate) {
|
||||||
const dueDateFormatted = dayjs(dueDate).format("YYYY-MM-DD");
|
const dueDateDayjs = dayjs(dueDate);
|
||||||
const relativeDueDate = dayjs(dueDate).fromNow();
|
const relativeDueDate = dueDateDayjs.fromNow();
|
||||||
const today = dayjs().format("YYYY-MM-DD");
|
const today = dayjs();
|
||||||
if (dueDateFormatted < today) {
|
|
||||||
return <div title={relativeDueDate} style={{color: 'red'}}>{dueDateFormatted}</div>;
|
if (dueDateDayjs.isAfter(today)) {
|
||||||
|
return <div title={relativeDueDate} style={{color: 'red'}}>
|
||||||
|
<DateFormatter>{dueDate}</DateFormatter></div>;
|
||||||
} else {
|
} else {
|
||||||
return <div title={relativeDueDate}>{dueDateFormatted}</div>;
|
return <div title={relativeDueDate}><DateFormatter>{dueDate}</DateFormatter></div>;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return <div>N/A</div>;
|
return <div>N/A</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority Label Component
|
||||||
|
* @param priority
|
||||||
|
* @returns {Element}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
const PriorityLabel = ({priority}) => {
|
const PriorityLabel = ({priority}) => {
|
||||||
switch(priority) {
|
switch(priority) {
|
||||||
case 1:
|
case 1:
|
||||||
return <div>
|
return <div>
|
||||||
<ExclamationCircleFilled style={{color: 'red'}} /> High
|
High <ExclamationCircleFilled style={{marginLeft: '5px', color: 'red'}}/>
|
||||||
</div>;
|
</div>;
|
||||||
case 2:
|
case 2:
|
||||||
return <div>
|
return <div>
|
||||||
<ExclamationCircleFilled style={{ color: 'yellow' }} /> Medium
|
Medium <ExclamationCircleFilled style={{marginLeft: '5px', color: 'yellow'}}/>
|
||||||
</div>;
|
</div>;
|
||||||
case 3:
|
case 3:
|
||||||
return <div>
|
return <div>
|
||||||
<ExclamationCircleFilled style={{ color: 'green' }} /> Low
|
Low <ExclamationCircleFilled style={{marginLeft: '5px', color: 'green'}}/>
|
||||||
</div>;
|
</div>;
|
||||||
default:
|
default:
|
||||||
return <div>
|
return <div>
|
||||||
<ExclamationCircleFilled style={{ color: 'black' }} /> None
|
None <ExclamationCircleFilled style={{marginLeft: '5px', color: 'black'}}/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TaskListComponent({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
// Existing dispatch props...
|
||||||
|
setTaskUpsertContext: (context) => dispatch(setModalContext({context, modal: 'taskUpsert'})),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
// Existing state props...
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
|
||||||
|
|
||||||
|
function TaskListComponent({
|
||||||
loading,
|
loading,
|
||||||
tasks,
|
tasks,
|
||||||
total,
|
total,
|
||||||
refetch,
|
refetch,
|
||||||
toggleCompletedStatus,
|
toggleCompletedStatus,
|
||||||
|
setTaskUpsertContext,
|
||||||
}) {
|
}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
@@ -71,6 +104,17 @@ export default function TaskListComponent({
|
|||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("tasks.fields.job.ro_number"),
|
||||||
|
dataIndex: ["job", "ro_number"],
|
||||||
|
key: "job.ro_number",
|
||||||
|
width: '5%',
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link to={`/manage/jobs/${record.job.id}`}>
|
||||||
|
{record.job.ro_number}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("tasks.fields.title"),
|
title: t("tasks.fields.title"),
|
||||||
dataIndex: "title",
|
dataIndex: "title",
|
||||||
@@ -110,6 +154,16 @@ export default function TaskListComponent({
|
|||||||
width: '5%',
|
width: '5%',
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space direction='horizontal'>
|
<Space direction='horizontal'>
|
||||||
|
<Button title={t('tasks.actions.edit')} onClick={() => {
|
||||||
|
setTaskUpsertContext({
|
||||||
|
actions: {},
|
||||||
|
context: {
|
||||||
|
existingTask: record,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
<EditFilled/>
|
||||||
|
</Button>
|
||||||
<Button title={t('tasks.actions.toggle_completed')}
|
<Button title={t('tasks.actions.toggle_completed')}
|
||||||
onClick={() => toggleCompletedStatus(record.id, record.completed)}>
|
onClick={() => toggleCompletedStatus(record.id, record.completed)}>
|
||||||
{record.completed ? <CheckCircleOutlined/> :
|
{record.completed ? <CheckCircleOutlined/> :
|
||||||
@@ -129,7 +183,12 @@ export default function TaskListComponent({
|
|||||||
filteredInfo: {text: ""},
|
filteredInfo: {text: ""},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Columns definition remains the same
|
const handleCreateTask = () => {
|
||||||
|
setTaskUpsertContext({
|
||||||
|
actions: {},
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||||
@@ -169,6 +228,9 @@ export default function TaskListComponent({
|
|||||||
checked={deleted === "true"}
|
checked={deleted === "true"}
|
||||||
onChange={(value) => handleSwitchChange('deleted', value)}
|
onChange={(value) => handleSwitchChange('deleted', value)}
|
||||||
/>
|
/>
|
||||||
|
<Button title={t('tasks.titles.create')} onClick={handleCreateTask}>
|
||||||
|
<PlusCircleFilled/>{t('tasks.buttons.create')}
|
||||||
|
</Button>
|
||||||
<Button title={t('tasks.titles.refresh')}
|
<Button title={t('tasks.titles.refresh')}
|
||||||
onClick={() => refetch()}>
|
onClick={() => refetch()}>
|
||||||
<SyncOutlined/>
|
<SyncOutlined/>
|
||||||
@@ -179,7 +241,7 @@ export default function TaskListComponent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("menus.header.tasks")}
|
title={t("menus.header.my_tasks")}
|
||||||
extra={tasksExtra()}
|
extra={tasksExtra()}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} 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 from "react";
|
import React, {useEffect} from "react";
|
||||||
import TaskListComponent from "./task-list.component.jsx";
|
import TaskListComponent from "./task-list.component.jsx";
|
||||||
|
|
||||||
export default function TaskListContainer({bodyshop, currentUser}) {
|
export default function TaskListContainer({bodyshop, currentUser}) {
|
||||||
@@ -37,9 +37,36 @@ export default function TaskListContainer({bodyshop, currentUser}) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refetch tasks when a task is updated
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const handleTaskUpdated = () => {
|
||||||
|
refetch().catch((e) => {
|
||||||
|
console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
|
||||||
|
}); };
|
||||||
|
|
||||||
|
window.addEventListener('taskUpdated', handleTaskUpdated);
|
||||||
|
window.addEventListener('taskCreated', handleTaskUpdated);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('taskUpdated', handleTaskUpdated);
|
||||||
|
window.removeEventListener('taskCreated', handleTaskUpdated);
|
||||||
|
};
|
||||||
|
}, [refetch]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle task completed mutation
|
||||||
|
*/
|
||||||
const [toggleTaskCompleted] = useMutation(MUTATION_TOGGLE_TASK_COMPLETED);
|
const [toggleTaskCompleted] = useMutation(MUTATION_TOGGLE_TASK_COMPLETED);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle task completed status
|
||||||
|
* @param id
|
||||||
|
* @param currentStatus
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
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({
|
await toggleTaskCompleted({
|
||||||
@@ -49,11 +76,21 @@ export default function TaskListContainer({bodyshop, currentUser}) {
|
|||||||
completed_at: completed_at
|
completed_at: completed_at
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
refetch();
|
refetch().catch((e) => {
|
||||||
};
|
console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
|
||||||
|
}); };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle task deleted mutation
|
||||||
|
*/
|
||||||
const [toggleTaskDeleted] = useMutation(MUTATION_TOGGLE_TASK_DELETED);
|
const [toggleTaskDeleted] = useMutation(MUTATION_TOGGLE_TASK_DELETED);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle task deleted status
|
||||||
|
* @param id
|
||||||
|
* @param currentStatus
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
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({
|
await toggleTaskDeleted({
|
||||||
@@ -63,7 +100,9 @@ export default function TaskListContainer({bodyshop, currentUser}) {
|
|||||||
deleted_at: deleted_at
|
deleted_at: deleted_at
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
refetch();
|
refetch().catch((e) => {
|
||||||
|
console.error(`Something went wrong fetching tasks: ${e.message || ''}`);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
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";
|
||||||
|
import {createStructuredSelector} from "reselect";
|
||||||
|
import {selectBodyshop} from "../../redux/user/user.selectors.js";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TaskUpsertModalComponent);
|
||||||
|
|
||||||
|
|
||||||
|
export function TaskUpsertModalComponent({form, bodyshop}) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("tasks.fields.completed")}
|
||||||
|
name="completed"
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("tasks.fields.assigned_to")}
|
||||||
|
name="assigned_to"
|
||||||
|
>
|
||||||
|
<Select placeholder={t("tasks.labels.selectemployee")}>
|
||||||
|
{bodyshop.employees.map((employee) => (
|
||||||
|
<Select.Option key={employee.id} value={employee.user_email}>
|
||||||
|
{employee.first_name} {employee.last_name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("tasks.fields.priority")}
|
||||||
|
name="priority"
|
||||||
|
initialValue={1}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{value: 3, label: t("tasks.fields.priority.low")},
|
||||||
|
{value: 2, label: t("tasks.fields.priority.medium")},
|
||||||
|
{value: 1, label: t("tasks.fields.priority.high")},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("tasks.fields.title")}
|
||||||
|
name="title"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("tasks.labels.titleplaceholder")}/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("tasks.fields.description")}
|
||||||
|
name="description"
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
rows={4}
|
||||||
|
placeholder={t("tasks.labels.descriptionplaceholder")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("tasks.fields.due_date")}
|
||||||
|
name="due_date"
|
||||||
|
>
|
||||||
|
<FormDatePicker/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("tasks.fields.remind_at")}
|
||||||
|
name="remind_at"
|
||||||
|
>
|
||||||
|
<FormDatePicker/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import {useMutation} from "@apollo/client";
|
||||||
|
import {Form, Modal, notification} from "antd";
|
||||||
|
import React, {useEffect} from "react";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
import {createStructuredSelector} from "reselect";
|
||||||
|
import {INSERT_NEW_TASK, UPDATE_TASK} from "../../graphql/tasks.queries";
|
||||||
|
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";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
taskUpsert: selectTaskUpsert,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function TaskUpsertModalContainer({
|
||||||
|
bodyshop,
|
||||||
|
currentUser,
|
||||||
|
taskUpsert,
|
||||||
|
toggleModalVisible,
|
||||||
|
}) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const [insertTask] = useMutation(INSERT_NEW_TASK);
|
||||||
|
const [updateTask] = useMutation(UPDATE_TASK);
|
||||||
|
|
||||||
|
const {open, context, actions} = taskUpsert;
|
||||||
|
const {jobId, existingTask} = context;
|
||||||
|
const {refetch} = actions;
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (existingTask && open) {
|
||||||
|
form.setFieldsValue(existingTask);
|
||||||
|
} else if (!existingTask && open) {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [existingTask, form, open]);
|
||||||
|
|
||||||
|
const handleFinish = async (formValues) => {
|
||||||
|
const {...values} = formValues;
|
||||||
|
|
||||||
|
if (existingTask) {
|
||||||
|
updateTask({
|
||||||
|
variables: {
|
||||||
|
taskId: existingTask.id,
|
||||||
|
task: values,
|
||||||
|
},
|
||||||
|
}).then((r) => {
|
||||||
|
window.dispatchEvent(new CustomEvent('taskUpdated'));
|
||||||
|
notification["success"]({
|
||||||
|
message: t("tasks.successes.updated"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (refetch) refetch();
|
||||||
|
toggleModalVisible();
|
||||||
|
} else {
|
||||||
|
await insertTask({
|
||||||
|
variables: {
|
||||||
|
taskInput: [
|
||||||
|
{...values, jobid: jobId, created_by: currentUser.email, bodyshopid: bodyshop.id},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (refetch) refetch();
|
||||||
|
form.resetFields();
|
||||||
|
toggleModalVisible();
|
||||||
|
window.dispatchEvent(new CustomEvent('taskUpdated'));
|
||||||
|
notification["success"]({
|
||||||
|
message: t("tasks.successes.create"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
|
||||||
|
open={open}
|
||||||
|
okText={t("general.actions.save")}
|
||||||
|
onOk={() => {
|
||||||
|
form.submit();
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
toggleModalVisible();
|
||||||
|
}}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||||
|
<TaskUpsertModalComponent form={form}/>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TaskUpsertModalContainer);
|
||||||
@@ -160,25 +160,6 @@ export const QUERY_BODYSHOP = gql`
|
|||||||
external_id
|
external_id
|
||||||
flat_rate
|
flat_rate
|
||||||
}
|
}
|
||||||
tasks {
|
|
||||||
id
|
|
||||||
created_at
|
|
||||||
updated_at
|
|
||||||
title
|
|
||||||
description
|
|
||||||
deleted
|
|
||||||
deleted_at
|
|
||||||
due_date
|
|
||||||
created_by
|
|
||||||
assigned_to
|
|
||||||
completed
|
|
||||||
completed_at
|
|
||||||
remind_at
|
|
||||||
priority
|
|
||||||
jobid
|
|
||||||
joblineid
|
|
||||||
partsorderid
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ export const QUERY_MY_TASKS_PAGINATED = gql`
|
|||||||
completed_at
|
completed_at
|
||||||
remind_at
|
remind_at
|
||||||
priority
|
priority
|
||||||
jobid
|
job {
|
||||||
joblineid
|
ro_number
|
||||||
partsorderid
|
id
|
||||||
billid
|
}
|
||||||
bodyshopid
|
bodyshopid
|
||||||
}
|
}
|
||||||
tasks_aggregate(
|
tasks_aggregate(
|
||||||
@@ -129,3 +129,65 @@ export const MUTATION_TOGGLE_TASK_DELETED = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert new task mutation
|
||||||
|
* @type {DocumentNode}
|
||||||
|
*/
|
||||||
|
export const INSERT_NEW_TASK = gql`
|
||||||
|
mutation INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) {
|
||||||
|
insert_tasks(objects: $taskInput) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
title
|
||||||
|
description
|
||||||
|
deleted
|
||||||
|
deleted_at
|
||||||
|
due_date
|
||||||
|
created_by
|
||||||
|
assigned_to
|
||||||
|
completed
|
||||||
|
completed_at
|
||||||
|
remind_at
|
||||||
|
priority
|
||||||
|
jobid
|
||||||
|
joblineid
|
||||||
|
partsorderid
|
||||||
|
billid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update task mutation
|
||||||
|
* @type {DocumentNode}
|
||||||
|
*/
|
||||||
|
export const UPDATE_TASK = gql`
|
||||||
|
mutation UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) {
|
||||||
|
update_tasks(where: { id: { _eq: $taskId } }, _set: $task) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
title
|
||||||
|
description
|
||||||
|
deleted
|
||||||
|
deleted_at
|
||||||
|
due_date
|
||||||
|
created_by
|
||||||
|
assigned_to
|
||||||
|
completed
|
||||||
|
completed_at
|
||||||
|
remind_at
|
||||||
|
priority
|
||||||
|
jobid
|
||||||
|
joblineid
|
||||||
|
partsorderid
|
||||||
|
billid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ import "./manage.page.styles.scss";
|
|||||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||||
|
|
||||||
const CardPaymentModalContainer = lazy(() =>
|
const CardPaymentModalContainer = lazy(() =>
|
||||||
import("../../components/card-payment-modal/card-payment-modal.container.")
|
import("../../components/card-payment-modal/card-payment-modal.container..jsx")
|
||||||
|
);
|
||||||
|
|
||||||
|
const TaskUpsertModalContainer = lazy(() =>
|
||||||
|
import("../../components/task-upsert-modal/task-upsert-modal.container.jsx")
|
||||||
);
|
);
|
||||||
|
|
||||||
const JobsDetailPage = lazy(() =>
|
const JobsDetailPage = lazy(() =>
|
||||||
@@ -229,7 +233,7 @@ export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRide
|
|||||||
<PaymentModalContainer/>
|
<PaymentModalContainer/>
|
||||||
|
|
||||||
<CardPaymentModalContainer/>
|
<CardPaymentModalContainer/>
|
||||||
|
<TaskUpsertModalContainer/>
|
||||||
<BreadCrumbs/>
|
<BreadCrumbs/>
|
||||||
<BillEnterModalContainer/>
|
<BillEnterModalContainer/>
|
||||||
<JobCostingModal/>
|
<JobCostingModal/>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const INITIAL_STATE = {
|
|||||||
billEnter: {...baseModal},
|
billEnter: {...baseModal},
|
||||||
courtesyCarReturn: {...baseModal},
|
courtesyCarReturn: {...baseModal},
|
||||||
noteUpsert: {...baseModal},
|
noteUpsert: {...baseModal},
|
||||||
|
taskUpsert: {...baseModal},
|
||||||
schedule: {...baseModal},
|
schedule: {...baseModal},
|
||||||
partsOrder: {...baseModal},
|
partsOrder: {...baseModal},
|
||||||
timeTicket: {...baseModal},
|
timeTicket: {...baseModal},
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ export const selectNoteUpsert = createSelector(
|
|||||||
(modals) => modals.noteUpsert
|
(modals) => modals.noteUpsert
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectTaskUpsert = createSelector(
|
||||||
|
[selectModals],
|
||||||
|
(modals) => modals.taskUpsert
|
||||||
|
);
|
||||||
|
|
||||||
export const selectSchedule = createSelector(
|
export const selectSchedule = createSelector(
|
||||||
[selectModals],
|
[selectModals],
|
||||||
(modals) => modals.schedule
|
(modals) => modals.schedule
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const ModalActionTypes = {
|
const ModalActionTypes = {
|
||||||
TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE",
|
TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE",
|
||||||
SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT"
|
SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT",
|
||||||
};
|
};
|
||||||
export default ModalActionTypes;
|
export default ModalActionTypes;
|
||||||
|
|||||||
Reference in New Issue
Block a user