feature/IO-3096-GlobalNotifications - Watchers - First revision.

This commit is contained in:
Dave Richer
2025-02-06 15:03:07 -05:00
parent 996f5b3c71
commit f11d9dd804
7 changed files with 160 additions and 38 deletions

View File

@@ -51,6 +51,7 @@ const colSpan = {
};
export function JobsDetailHeader({ job, bodyshop, disabled }) {
console.dir({ job });
const { t } = useTranslation();
const [notesClamped, setNotesClamped] = useState(true);
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")}>
{job.cccontracts.map((c, index) => (
<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}`}
{index !== job.cccontracts.length - 1 ? "," : null}
</Link>

View File

@@ -524,6 +524,9 @@ export const GET_JOB_BY_PK = gql`
invoice_final_note
iouparent
job_totals
job_watchers {
user_email
}
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
act_price
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
}
}
`;

View File

@@ -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;

View File

@@ -2,6 +2,7 @@ import Icon, {
BarsOutlined,
CalendarFilled,
DollarCircleOutlined,
EyeOutlined,
FileImageFilled,
HistoryOutlined,
PrinterFilled,
@@ -56,6 +57,7 @@ import { DateTimeFormat } from "../../utils/DateFormatter";
import dayjs from "../../utils/day";
import UndefinedToNull from "../../utils/undefinedtonull";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import JobWatcherToggle from "./job-watcher-toggle.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -266,6 +268,8 @@ export function JobsDetailPage({
}
};
const handleWatchClick = () => {};
const menuExtra = (
<Space wrap>
<Button
@@ -319,7 +323,13 @@ export function JobsDetailPage({
>
<PageHeader
// 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}
/>
<JobsDetailHeader job={job} />

View File

@@ -3764,6 +3764,13 @@
"notificationscenarios": "Notification Scenarios",
"save": "Save Scenarios"
},
"aria": {
"toggle": "Toggle Watching Job"
},
"tooltips": {
"watch": "Watch Job",
"unwatch": "Unwatch Job"
},
"scenarios": {
"job-assigned-to-me": "Job Assigned to Me",
"bill-posted": "Bill Posted",

View File

@@ -31,14 +31,6 @@
headers:
- name: x-imex-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
webhook: '{{HASURA_API_URL}}/data/usagereport'
schedule: 0 12 * * 5
@@ -47,3 +39,11 @@
headers:
- name: x-imex-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

View File

@@ -2846,13 +2846,12 @@
- role: user
permission:
check:
user:
_and:
- associations:
active:
_eq: true
- authid:
_eq: X-Hasura-User-Id
job:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
columns:
- user_email
- created_at
@@ -2868,13 +2867,12 @@
- id
- jobid
filter:
user:
_and:
- associations:
active:
_eq: true
- authid:
_eq: X-Hasura-User-Id
job:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
comment: ""
update_permissions:
- role: user
@@ -2885,26 +2883,24 @@
- id
- jobid
filter:
user:
_and:
- associations:
active:
_eq: true
- authid:
_eq: X-Hasura-User-Id
job:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
check: null
comment: ""
delete_permissions:
- role: user
permission:
filter:
user:
_and:
- associations:
active:
_eq: true
- authid:
_eq: X-Hasura-User-Id
job:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
comment: ""
- table:
name: joblines
@@ -3369,6 +3365,13 @@
table:
name: job_conversations
schema: public
- name: job_watchers
using:
foreign_key_constraint_on:
column: jobid
table:
name: job_watchers
schema: public
- name: joblines
using:
foreign_key_constraint_on: