feature/IO-3096-GlobalNotifications - Watchers - First revision.
This commit is contained in:
@@ -51,6 +51,7 @@ const colSpan = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||||
|
console.dir({ job });
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [notesClamped, setNotesClamped] = useState(true);
|
const [notesClamped, setNotesClamped] = useState(true);
|
||||||
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
|
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
|
||||||
@@ -119,7 +120,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
<DataLabel label={t("jobs.labels.contracts")}>
|
<DataLabel label={t("jobs.labels.contracts")}>
|
||||||
{job.cccontracts.map((c, index) => (
|
{job.cccontracts.map((c, index) => (
|
||||||
<Space key={c.id} wrap>
|
<Space key={c.id} wrap>
|
||||||
<Link to={`/manage/courtesycars/contracts/${c.id}`}>
|
<Link to={`/manage/courtesycars/contracts/${c.id}`}>
|
||||||
{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}
|
{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}
|
||||||
{index !== job.cccontracts.length - 1 ? "," : null}
|
{index !== job.cccontracts.length - 1 ? "," : null}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -524,6 +524,9 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
invoice_final_note
|
invoice_final_note
|
||||||
iouparent
|
iouparent
|
||||||
job_totals
|
job_totals
|
||||||
|
job_watchers {
|
||||||
|
user_email
|
||||||
|
}
|
||||||
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||||
act_price
|
act_price
|
||||||
act_price_before_ppc
|
act_price_before_ppc
|
||||||
@@ -2566,3 +2569,30 @@ export const GET_JOB_BY_PK_QUICK_INTAKE = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const GET_JOB_WATCHERS = gql`
|
||||||
|
query GET_JOB_WATCHERS($jobid: uuid!) {
|
||||||
|
job_watchers(where: { jobid: { _eq: $jobid } }) {
|
||||||
|
id
|
||||||
|
user_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ADD_JOB_WATCHER = gql`
|
||||||
|
mutation ADD_JOB_WATCHER($jobid: uuid!, $userEmail: String!) {
|
||||||
|
insert_job_watchers_one(object: { jobid: $jobid, user_email: $userEmail }) {
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
user_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const REMOVE_JOB_WATCHER = gql`
|
||||||
|
mutation REMOVE_JOB_WATCHER($jobid: uuid!, $userEmail: String!) {
|
||||||
|
delete_job_watchers(where: { jobid: { _eq: $jobid }, user_email: { _eq: $userEmail } }) {
|
||||||
|
affected_rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
|
import { EyeFilled, EyeOutlined } from "@ant-design/icons";
|
||||||
|
import { GET_JOB_WATCHERS, ADD_JOB_WATCHER, REMOVE_JOB_WATCHER } from "../../graphql/jobs.queries.js";
|
||||||
|
import { Button, Tooltip } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const JobWatcherToggle = ({ job, currentUser }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const userEmail = currentUser.email;
|
||||||
|
const jobid = job.id;
|
||||||
|
|
||||||
|
// Fetch current watchers
|
||||||
|
const { data, loading } = useQuery(GET_JOB_WATCHERS, {
|
||||||
|
variables: { jobid }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract current watchers list
|
||||||
|
const jobWatchers = useMemo(() => data?.job_watchers || [], [data]);
|
||||||
|
const isWatching = useMemo(() => !!jobWatchers.find((w) => w.user_email === userEmail), [jobWatchers, userEmail]);
|
||||||
|
|
||||||
|
// Add watcher mutation
|
||||||
|
const [addWatcher] = useMutation(ADD_JOB_WATCHER, {
|
||||||
|
variables: { jobid, userEmail },
|
||||||
|
refetchQueries: [{ query: GET_JOB_WATCHERS, variables: { jobid } }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove watcher mutation
|
||||||
|
const [removeWatcher] = useMutation(REMOVE_JOB_WATCHER, {
|
||||||
|
variables: { jobid, userEmail },
|
||||||
|
refetchQueries: [{ query: GET_JOB_WATCHERS, variables: { jobid } }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle watcher status
|
||||||
|
const handleToggle = useCallback(() => {
|
||||||
|
if (!isWatching) {
|
||||||
|
// Fix: Add if not watching, remove if watching
|
||||||
|
addWatcher().catch((err) => console.error(`Something went wrong adding a job watcher: ${err.message}`));
|
||||||
|
} else {
|
||||||
|
removeWatcher().catch((err) => console.error(`Something went wrong removing a job watcher: ${err.message}`));
|
||||||
|
}
|
||||||
|
}, [isWatching, addWatcher, removeWatcher]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={t("notifications.tooltips.unwatch")}>
|
||||||
|
<Button
|
||||||
|
shape="circle"
|
||||||
|
type="primary"
|
||||||
|
icon={<EyeFilled />}
|
||||||
|
disabled
|
||||||
|
aria-label={t("notifications.aria.toggle")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={isWatching ? t("notifications.tooltips.unwatch") : t("notifications.tooltips.watch")}>
|
||||||
|
<Button
|
||||||
|
shape="circle"
|
||||||
|
type={isWatching ? "primary" : "default"}
|
||||||
|
icon={isWatching ? <EyeFilled /> : <EyeOutlined />}
|
||||||
|
onClick={handleToggle}
|
||||||
|
aria-label={t("notifications.aria.toggle")}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JobWatcherToggle;
|
||||||
@@ -2,6 +2,7 @@ import Icon, {
|
|||||||
BarsOutlined,
|
BarsOutlined,
|
||||||
CalendarFilled,
|
CalendarFilled,
|
||||||
DollarCircleOutlined,
|
DollarCircleOutlined,
|
||||||
|
EyeOutlined,
|
||||||
FileImageFilled,
|
FileImageFilled,
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
PrinterFilled,
|
PrinterFilled,
|
||||||
@@ -56,6 +57,7 @@ import { DateTimeFormat } from "../../utils/DateFormatter";
|
|||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import JobWatcherToggle from "./job-watcher-toggle.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -266,6 +268,8 @@ export function JobsDetailPage({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleWatchClick = () => {};
|
||||||
|
|
||||||
const menuExtra = (
|
const menuExtra = (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button
|
<Button
|
||||||
@@ -319,7 +323,13 @@ export function JobsDetailPage({
|
|||||||
>
|
>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
// onBack={() => window.history.back()}
|
// onBack={() => window.history.back()}
|
||||||
title={job.ro_number || t("general.labels.na")}
|
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<JobWatcherToggle job={job} currentUser={bodyshop} />
|
||||||
|
{job.ro_number || t("general.labels.na")}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
extra={menuExtra}
|
extra={menuExtra}
|
||||||
/>
|
/>
|
||||||
<JobsDetailHeader job={job} />
|
<JobsDetailHeader job={job} />
|
||||||
|
|||||||
@@ -3764,6 +3764,13 @@
|
|||||||
"notificationscenarios": "Notification Scenarios",
|
"notificationscenarios": "Notification Scenarios",
|
||||||
"save": "Save Scenarios"
|
"save": "Save Scenarios"
|
||||||
},
|
},
|
||||||
|
"aria": {
|
||||||
|
"toggle": "Toggle Watching Job"
|
||||||
|
},
|
||||||
|
"tooltips": {
|
||||||
|
"watch": "Watch Job",
|
||||||
|
"unwatch": "Unwatch Job"
|
||||||
|
},
|
||||||
"scenarios": {
|
"scenarios": {
|
||||||
"job-assigned-to-me": "Job Assigned to Me",
|
"job-assigned-to-me": "Job Assigned to Me",
|
||||||
"bill-posted": "Bill Posted",
|
"bill-posted": "Bill Posted",
|
||||||
|
|||||||
@@ -31,14 +31,6 @@
|
|||||||
headers:
|
headers:
|
||||||
- name: x-imex-auth
|
- name: x-imex-auth
|
||||||
value_from_env: DATAPUMP_AUTH
|
value_from_env: DATAPUMP_AUTH
|
||||||
- name: Task Reminders
|
|
||||||
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
|
||||||
schedule: '*/15 * * * *'
|
|
||||||
include_in_metadata: true
|
|
||||||
payload: {}
|
|
||||||
headers:
|
|
||||||
- name: event-secret
|
|
||||||
value_from_env: EVENT_SECRET
|
|
||||||
- name: Rome Usage Report
|
- name: Rome Usage Report
|
||||||
webhook: '{{HASURA_API_URL}}/data/usagereport'
|
webhook: '{{HASURA_API_URL}}/data/usagereport'
|
||||||
schedule: 0 12 * * 5
|
schedule: 0 12 * * 5
|
||||||
@@ -47,3 +39,11 @@
|
|||||||
headers:
|
headers:
|
||||||
- name: x-imex-auth
|
- name: x-imex-auth
|
||||||
value_from_env: DATAPUMP_AUTH
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
- name: Task Reminders
|
||||||
|
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
||||||
|
schedule: '*/15 * * * *'
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: event-secret
|
||||||
|
value_from_env: EVENT_SECRET
|
||||||
|
|||||||
@@ -2846,13 +2846,12 @@
|
|||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
check:
|
check:
|
||||||
user:
|
job:
|
||||||
_and:
|
bodyshop:
|
||||||
- associations:
|
associations:
|
||||||
active:
|
user:
|
||||||
_eq: true
|
authid:
|
||||||
- authid:
|
_eq: X-Hasura-User-Id
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
columns:
|
columns:
|
||||||
- user_email
|
- user_email
|
||||||
- created_at
|
- created_at
|
||||||
@@ -2868,13 +2867,12 @@
|
|||||||
- id
|
- id
|
||||||
- jobid
|
- jobid
|
||||||
filter:
|
filter:
|
||||||
user:
|
job:
|
||||||
_and:
|
bodyshop:
|
||||||
- associations:
|
associations:
|
||||||
active:
|
user:
|
||||||
_eq: true
|
authid:
|
||||||
- authid:
|
_eq: X-Hasura-User-Id
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
comment: ""
|
comment: ""
|
||||||
update_permissions:
|
update_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
@@ -2885,26 +2883,24 @@
|
|||||||
- id
|
- id
|
||||||
- jobid
|
- jobid
|
||||||
filter:
|
filter:
|
||||||
user:
|
job:
|
||||||
_and:
|
bodyshop:
|
||||||
- associations:
|
associations:
|
||||||
active:
|
user:
|
||||||
_eq: true
|
authid:
|
||||||
- authid:
|
_eq: X-Hasura-User-Id
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
check: null
|
check: null
|
||||||
comment: ""
|
comment: ""
|
||||||
delete_permissions:
|
delete_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
permission:
|
permission:
|
||||||
filter:
|
filter:
|
||||||
user:
|
job:
|
||||||
_and:
|
bodyshop:
|
||||||
- associations:
|
associations:
|
||||||
active:
|
user:
|
||||||
_eq: true
|
authid:
|
||||||
- authid:
|
_eq: X-Hasura-User-Id
|
||||||
_eq: X-Hasura-User-Id
|
|
||||||
comment: ""
|
comment: ""
|
||||||
- table:
|
- table:
|
||||||
name: joblines
|
name: joblines
|
||||||
@@ -3369,6 +3365,13 @@
|
|||||||
table:
|
table:
|
||||||
name: job_conversations
|
name: job_conversations
|
||||||
schema: public
|
schema: public
|
||||||
|
- name: job_watchers
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
column: jobid
|
||||||
|
table:
|
||||||
|
name: job_watchers
|
||||||
|
schema: public
|
||||||
- name: joblines
|
- name: joblines
|
||||||
using:
|
using:
|
||||||
foreign_key_constraint_on:
|
foreign_key_constraint_on:
|
||||||
|
|||||||
Reference in New Issue
Block a user