Merge branch 'feature/payroll' into feature/america
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { Select, Space, Tag } from "antd";
|
||||
import React, { forwardRef } from "react";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const { Option } = Select;
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeSearchSelect = ({ options, ...props }, ref) => {
|
||||
const EmployeeSearchSelect = ({ options, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -39,4 +39,4 @@ const EmployeeSearchSelect = ({ options, ...props }, ref) => {
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
export default forwardRef(EmployeeSearchSelect);
|
||||
export default EmployeeSearchSelect;
|
||||
|
||||
@@ -262,11 +262,13 @@ function Header({
|
||||
{t("menus.header.timetickets")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="ttapprovals" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/ttapprovals">
|
||||
{t("menus.header.ttapprovals")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
{bodyshop?.md_tasks_presets?.use_approvals && (
|
||||
<Menu.Item key="ttapprovals" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/ttapprovals">
|
||||
{t("menus.header.ttapprovals")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item
|
||||
key="entertimetickets"
|
||||
icon={<Icon component={GiPlayerTime} />}
|
||||
|
||||
@@ -250,21 +250,24 @@ export function JobsDetailHeaderActions({
|
||||
>
|
||||
{t("timetickets.actions.enter")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="claimtimetickettasks"
|
||||
disabled={
|
||||
!job.converted ||
|
||||
(!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced)
|
||||
}
|
||||
onClick={() => {
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: { jobid: job.id },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("timetickets.actions.claimtasks")}
|
||||
</Menu.Item>
|
||||
{bodyshop.md_tasks_presets.enable_tasks && (
|
||||
<Menu.Item
|
||||
key="claimtimetickettasks"
|
||||
disabled={
|
||||
!job.converted ||
|
||||
(!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced)
|
||||
}
|
||||
onClick={() => {
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: { jobid: job.id },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("timetickets.actions.claimtasks")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Item
|
||||
key="enterpayments"
|
||||
disabled={!job.converted}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
InputNumber,
|
||||
Row,
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -18,127 +19,146 @@ export default function ShopInfoTaskPresets({ form }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<LayoutFormRow header={t("bodyshop.labels.md_tasks_presets")}>
|
||||
<Form.List name={["md_tasks_presets", "presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.hourstype")}
|
||||
key={`${index}hourstype`}
|
||||
name={[field.name, "hourstype"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Checkbox.Group>
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAB"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAB")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAR"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAR")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAM"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAM")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAF"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAF")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAG"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAG")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.percent")}
|
||||
key={`${index}percent`}
|
||||
name={[field.name, "percent"]}
|
||||
>
|
||||
<InputNumber min={0} max={100} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.memo")}
|
||||
key={`${index}memo`}
|
||||
name={[field.name, "memo"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Space wrap>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<FormListMoveArrows
|
||||
move={move}
|
||||
index={index}
|
||||
total={fields.length}
|
||||
/>
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
<>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.enable_tasks")}
|
||||
valuePropName="checked"
|
||||
name={["md_tasks_presets", "enable_tasks"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.use_approvals")}
|
||||
valuePropName="checked"
|
||||
name={["md_tasks_presets", "use_approvals"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("bodyshop.labels.md_tasks_presets")}>
|
||||
<Form.List name={["md_tasks_presets", "presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.hourstype")}
|
||||
key={`${index}hourstype`}
|
||||
name={[field.name, "hourstype"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Checkbox.Group>
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAB"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAB")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAR"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAR")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAM"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAM")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAF"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAF")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox
|
||||
value="LAG"
|
||||
style={{ lineHeight: "32px" }}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAG")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.percent")}
|
||||
key={`${index}percent`}
|
||||
name={[field.name, "percent"]}
|
||||
>
|
||||
<InputNumber min={0} max={100} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.memo")}
|
||||
key={`${index}memo`}
|
||||
name={[field.name, "memo"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Space wrap>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<FormListMoveArrows
|
||||
move={move}
|
||||
index={index}
|
||||
total={fields.length}
|
||||
/>
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("bodyshop.actions.add_task_preset")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("bodyshop.actions.add_task_preset")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,16 +205,15 @@ export function TimeTicketList({
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Pay",
|
||||
dataIndex: "pay",
|
||||
key: "pay",
|
||||
render: (text, record) =>
|
||||
Dinero({ amount: Math.round(record.rate * 100) })
|
||||
.multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
.toFormat("$0.00"),
|
||||
},
|
||||
|
||||
// {
|
||||
// title: "Pay",
|
||||
// dataIndex: "pay",
|
||||
// key: "pay",
|
||||
// render: (text, record) =>
|
||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
// .toFormat("$0.00"),
|
||||
// },
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -282,16 +281,18 @@ export function TimeTicketList({
|
||||
// context={{ jobId: jobId }}
|
||||
// />
|
||||
}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: { jobid: jobId },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("timetickets.actions.claimtasks")}
|
||||
</Button>
|
||||
{bodyshop.md_tasks_presets.enable_tasks && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: { jobid: jobId },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("timetickets.actions.claimtasks")}
|
||||
</Button>
|
||||
)}
|
||||
{jobId &&
|
||||
(techConsole ? null : (
|
||||
<TimeTicketEnterButton
|
||||
|
||||
@@ -37,6 +37,7 @@ export function TimeTicketModalComponent({
|
||||
authLevel,
|
||||
employeeAutoCompleteOptions,
|
||||
isEdit,
|
||||
disabled,
|
||||
employeeSelectDisabled,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -50,7 +51,7 @@ export function TimeTicketModalComponent({
|
||||
<Select
|
||||
value={value === "timetickets.labels.shift" ? t(value) : value}
|
||||
{...props}
|
||||
disabled={value === "timetickets.labels.shift"}
|
||||
disabled={value === "timetickets.labels.shift" || disabled}
|
||||
>
|
||||
{emps &&
|
||||
emps.rates.map((item) => (
|
||||
@@ -73,7 +74,7 @@ export function TimeTicketModalComponent({
|
||||
<Input
|
||||
value={value?.startsWith("timetickets.") ? t(value) : value}
|
||||
{...props}
|
||||
disabled={value?.startsWith("timetickets.")}
|
||||
disabled={value?.startsWith("timetickets.") || disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -127,7 +128,7 @@ export function TimeTicketModalComponent({
|
||||
]}
|
||||
>
|
||||
<EmployeeSearchSelect
|
||||
disabled={employeeSelectDisabled}
|
||||
disabled={employeeSelectDisabled || disabled}
|
||||
options={employeeAutoCompleteOptions}
|
||||
onSelect={(value) => {
|
||||
const emps =
|
||||
@@ -279,6 +280,7 @@ export function TimeTicketModalComponent({
|
||||
<FormDateTimePicker
|
||||
minuteStep={5}
|
||||
disabled={
|
||||
disabled ||
|
||||
!HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel,
|
||||
@@ -317,6 +319,7 @@ export function TimeTicketModalComponent({
|
||||
<FormDateTimePicker
|
||||
minuteStep={5}
|
||||
disabled={
|
||||
disabled ||
|
||||
!HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectTimeTicket } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
timeTicketModal: selectTimeTicket,
|
||||
@@ -174,7 +175,11 @@ export function TimeTicketModalContainer({
|
||||
footer={
|
||||
<span>
|
||||
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
||||
<Button loading={loading} onClick={() => form.submit()}>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={timeTicketModal.context?.timeticket?.committed_at}
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
{timeTicketModal.context && timeTicketModal.context.id ? null : (
|
||||
@@ -198,6 +203,7 @@ export function TimeTicketModalContainer({
|
||||
autoComplete={"off"}
|
||||
form={form}
|
||||
onFinishFailed={() => setEnterAgain(false)}
|
||||
disabled={timeTicketModal.context?.timeticket?.committed_at}
|
||||
initialValues={
|
||||
timeTicketModal.context.timeticket
|
||||
? {
|
||||
@@ -218,6 +224,9 @@ export function TimeTicketModalContainer({
|
||||
<PageHeader
|
||||
extra={
|
||||
<Space>
|
||||
<TimeTicketsCommitToggleComponent
|
||||
timeticket={timeTicketModal.context?.timeticket}
|
||||
/>
|
||||
<Button onClick={handleCancel}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
@@ -241,14 +250,16 @@ export function TimeTicketModalContainer({
|
||||
<TimeTicketModalComponent
|
||||
isEdit={timeTicketModal.context.id}
|
||||
form={form}
|
||||
disabled={timeTicketModal.context?.timeticket?.committed_at}
|
||||
employeeAutoCompleteOptions={
|
||||
EmployeeAutoCompleteData && EmployeeAutoCompleteData.employees
|
||||
}
|
||||
employeeSelectDisabled={
|
||||
timeTicketModal.context?.timeticket?.employeeid &&
|
||||
timeTicketModal.context?.timeticket?.committed_at ||
|
||||
(timeTicketModal.context?.timeticket?.employeeid &&
|
||||
!timeTicketModal.context.id
|
||||
? true
|
||||
: false
|
||||
: false)
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
|
||||
@@ -23,6 +23,7 @@ import JobSearchSelectComponent from "../job-search-select/job-search-select.com
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
||||
import TimeTicketsTasksPresets from "../time-ticket-tasks-presets/time-ticket-tasks-presets.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -54,7 +55,8 @@ export function TimeTicketTaskModalComponent({
|
||||
calculateTimeTickets={calculateTimeTickets}
|
||||
/>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col lg={12} md={24}>
|
||||
|
||||
<Col xl={12} lg={24}>
|
||||
<Form.Item
|
||||
name="jobid"
|
||||
label={t("timetickets.fields.ro_number")}
|
||||
@@ -77,37 +79,37 @@ export function TimeTicketTaskModalComponent({
|
||||
>
|
||||
<EmployeeTeamSearchSelectComponent />
|
||||
</Form.Item>
|
||||
<Space wrap>
|
||||
<Form.Item
|
||||
name="hourstype"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Checkbox.Group>
|
||||
<Space wrap>
|
||||
<Checkbox value="LAB" style={{ display: "flex" }}>
|
||||
Body
|
||||
</Checkbox>
|
||||
<Checkbox value="LAR" style={{ display: "flex" }}>
|
||||
Refinish
|
||||
</Checkbox>
|
||||
<Checkbox value="LAM" style={{ display: "flex" }}>
|
||||
Mechanical
|
||||
</Checkbox>
|
||||
<Checkbox value="LAF" style={{ display: "flex" }}>
|
||||
Frame
|
||||
</Checkbox>
|
||||
<Checkbox value="LAG" style={{ display: "flex" }}>
|
||||
Glass
|
||||
</Checkbox>
|
||||
</Space>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="hourstype"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Checkbox.Group>
|
||||
<Space wrap>
|
||||
<Checkbox value="LAB" style={{ display: "flex" }}>
|
||||
{t("jobs.fields.lab")}
|
||||
</Checkbox>
|
||||
<Checkbox value="LAR" style={{ display: "flex" }}>
|
||||
{t("jobs.fields.lar")}
|
||||
</Checkbox>
|
||||
<Checkbox value="LAM" style={{ display: "flex" }}>
|
||||
{t("jobs.fields.lam")}
|
||||
</Checkbox>
|
||||
<Checkbox value="LAF" style={{ display: "flex" }}>
|
||||
{t("jobs.fields.laf")}
|
||||
</Checkbox>
|
||||
<Checkbox value="LAG" style={{ display: "flex" }}>
|
||||
{t("jobs.fields.lag")}
|
||||
</Checkbox>
|
||||
</Space>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
<Space wrap align="start">
|
||||
<Form.Item
|
||||
name="percent"
|
||||
rules={[
|
||||
@@ -119,10 +121,13 @@ export function TimeTicketTaskModalComponent({
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={1} addonAfter="%" />
|
||||
</Form.Item>
|
||||
|
||||
<Button onClick={calculateTimeTickets}>
|
||||
{t("tt_approvals.labels.calculate")}
|
||||
</Button>
|
||||
</Space>
|
||||
<Button onClick={calculateTimeTickets}>Calculate</Button>
|
||||
</Col>
|
||||
<Col lg={12} md={24}>
|
||||
<Col xl={12} lg={24}>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
const data = form.getFieldValue("timetickets");
|
||||
@@ -167,11 +172,11 @@ export function TimeTicketTaskModalComponent({
|
||||
dataIndex: "rate",
|
||||
key: "rate",
|
||||
},
|
||||
{
|
||||
title: "Pay",
|
||||
dataIndex: "pay",
|
||||
key: "pay",
|
||||
},
|
||||
// {
|
||||
// title: "Pay",
|
||||
// dataIndex: "pay",
|
||||
// key: "pay",
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
@@ -231,11 +236,9 @@ export function TimeTicketTaskModalComponent({
|
||||
<Alert key={idx} message={e} />
|
||||
))}
|
||||
<div
|
||||
style={
|
||||
{
|
||||
//display: "none"
|
||||
}
|
||||
}
|
||||
style={{
|
||||
display: "none",
|
||||
}}
|
||||
>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item
|
||||
@@ -364,6 +367,11 @@ export function TimeTicketTaskModalComponent({
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
{bodyshop?.md_tasks_presets?.use_approvals && (
|
||||
<Col span={24}>
|
||||
<Alert message={t("tt_approvals.labels.approval_queue_in_use")} type="warning" />
|
||||
</Col>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,12 +52,8 @@ export function TimeTickeTaskModalContainer({
|
||||
});
|
||||
|
||||
async function handleFinish(values) {
|
||||
console.log(
|
||||
"🚀 ~ file: time-ticket-task-modal.container.jsx:55 ~ handleFinish ~ values:",
|
||||
values
|
||||
);
|
||||
try {
|
||||
if (true) {
|
||||
if (bodyshop.md_tasks_presets.use_approvals) {
|
||||
const result = await insertTimeTicketApproval({
|
||||
variables: {
|
||||
timeTicketInput: values.timetickets.map((ticket) => ({
|
||||
@@ -88,6 +84,7 @@ export function TimeTickeTaskModalContainer({
|
||||
_.omit(ticket, "pay")
|
||||
),
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
@@ -105,6 +102,13 @@ export function TimeTickeTaskModalContainer({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("🚀 ~ file: time-ticket-task-modal.container.jsx:104 ~ handleFinish ~ error:", error)
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(error),
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
UPDATE_TIME_TICKET,
|
||||
UPDATE_TIME_TICKETS,
|
||||
} from "../../graphql/timetickets.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setTimeTicketContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||
});
|
||||
export function TimeTicketsCommit({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
timeticket,
|
||||
disabled,
|
||||
refetch,
|
||||
setTimeTicketContext,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateTimeTicket] = useMutation(UPDATE_TIME_TICKET);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleCommit = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const ticketUpdate = timeticket.committed_at
|
||||
? { commited_by: null, committed_at: null }
|
||||
: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: moment(),
|
||||
};
|
||||
|
||||
const result = await updateTimeTicket({
|
||||
variables: {
|
||||
timeticketId: timeticket.id,
|
||||
timeticket: ticketUpdate,
|
||||
},
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
timeTickets(existingtickets, { readField }) {
|
||||
return existingtickets.map((ticket) => {
|
||||
if (timeticket.id === readField("id", ticket)) {
|
||||
return {
|
||||
...ticket,
|
||||
...ticketUpdate,
|
||||
};
|
||||
}
|
||||
return ticket;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
setTimeTicketContext({
|
||||
context: {
|
||||
id: timeticket.id,
|
||||
timeticket: result.data.update_timetickets.returning[0],
|
||||
},
|
||||
});
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.committed"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!timeticket?.id) return null;
|
||||
|
||||
return (
|
||||
<Button onClick={handleCommit} loading={loading} disabled={!timeticket?.id}>
|
||||
{timeticket?.committed_at
|
||||
? t("timetickets.actions.uncommit")
|
||||
: t("timetickets.actions.commitone")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketsCommit);
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_TIME_TICKETS } from "../../graphql/timetickets.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
export function TimeTicketsCommit({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
timetickets,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateTimeTickets] = useMutation(UPDATE_TIME_TICKETS);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleCommit = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await updateTimeTickets({
|
||||
variables: {
|
||||
timeticketIds: timetickets.map((ticket) => ticket.id),
|
||||
timeticket: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: moment(),
|
||||
},
|
||||
},
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
timeTickets(existingtickets, { readField }) {
|
||||
const modifiedIds = timetickets.map((ticket) => ticket.id);
|
||||
return existingtickets.map((ticket) => {
|
||||
if (modifiedIds.includes(readField("id", ticket))) {
|
||||
return {
|
||||
...ticket,
|
||||
commited_by: currentUser.email,
|
||||
committed_at: moment(),
|
||||
};
|
||||
}
|
||||
return ticket;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.creating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.committed"),
|
||||
});
|
||||
if (!!completedCallback) completedCallback([]);
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleCommit}
|
||||
loading={loading}
|
||||
disabled={disabled || timetickets?.length === 0}
|
||||
>
|
||||
{t("timetickets.actions.commit", { count: timetickets?.length })}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(TimeTicketsCommit);
|
||||
@@ -9,13 +9,16 @@ import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_TIME_TICKET_AND_APPROVE } from "../../graphql/timetickets.queries";
|
||||
import { QUERY_TT_APPROVALS_BY_IDS } from "../../graphql/tt-approvals.queries";
|
||||
import {
|
||||
selectAuthLevel,
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
authLevel: selectAuthLevel,
|
||||
});
|
||||
|
||||
export function TtApproveButton({
|
||||
@@ -23,6 +26,7 @@ export function TtApproveButton({
|
||||
currentUser,
|
||||
selectedTickets,
|
||||
disabled,
|
||||
authLevel,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch,
|
||||
@@ -64,7 +68,7 @@ export function TtApproveButton({
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.createdg"),
|
||||
message: t("timetickets.successes.created"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -83,7 +87,14 @@ export function TtApproveButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button
|
||||
onClick={handleQbxml}
|
||||
loading={loading}
|
||||
disabled={
|
||||
disabled ||
|
||||
!HasRbacAccess({ bodyshop, authLevel, action: "ttapprovals:approve" })
|
||||
}
|
||||
>
|
||||
{t("tt_approvals.actions.approveselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user