Add basic acknolwedgmenet to notifications.

This commit is contained in:
Patrick Fic
2022-11-22 11:27:37 -08:00
parent 66626472a7
commit 7bf838ddb5
30 changed files with 344 additions and 38 deletions

View File

@@ -0,0 +1,70 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, Space } from "antd";
import React from "react";
import { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { ACCEPT_NOTIFICATION } from "../../../graphql/notification.queries";
import { checkForNotification } from "../../../redux/user/user.actions";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
checkForNotification: () => dispatch(checkForNotification()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(NotificationDisplayMolecule);
export function NotificationDisplayMolecule({
notification,
checkForNotification,
}) {
const [acceptNotification] = useMutation(ACCEPT_NOTIFICATION);
const [loading, setLoading] = useState(false);
const handleConfirm = async (values) => {
console.log("form submit", notification, values);
setLoading(true);
await acceptNotification({
variables: {
id: notification.id,
notification: { acceptedby: values.acceptedby, acceptedat: new Date() },
},
});
checkForNotification();
setLoading(false);
};
const handleDismiss = async () => {};
const [form] = Form.useForm();
return (
<Card>
<div dangerouslySetInnerHTML={{ __html: notification.html }} />
{notification.requiresconfirmation ? (
<Form form={form} onFinish={handleConfirm}>
<Space align="baseline">
<Form.Item
label="Acknowledged by"
name="acceptedby"
rules={[
{
required: true,
message:
"Please enter your name to acknowledge and agree to the notice above.",
},
]}
>
<Input />
</Form.Item>
<Button loading={loading} onClick={() => form.submit()}>
Acknowledge
</Button>
</Space>
</Form>
) : (
<Button onClick={handleDismiss}>Dismiss</Button>
)}
</Card>
);
}

View File

@@ -0,0 +1,30 @@
import { Modal } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectNotifications } from "../../../redux/user/user.selectors";
import NotificationDisplayMolecule from "../../molecules/notification-display/notification-display.molecule";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
notifications: selectNotifications,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(NotificationModal);
export function NotificationModal({ notifications }) {
if (!notifications || notifications.length === 0) return null;
return (
<Modal
open={notifications && notifications.length > 0}
closable={false}
footer={null}
>
{notifications &&
notifications.map((n) => (
<NotificationDisplayMolecule key={n.id} notification={n} />
))}
</Modal>
);
}

View File

@@ -7,6 +7,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../../redux/user/user.selectors";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import ReleaseNotes from "../../molecules/release-notes/release-notes.molecule";
import NotificationModalOrganism from "../../organisms/notification-modal/notification-modal.organism";
import SiderMenuOrganism from "../../organisms/sider-menu/sider-menu.organism";
import UpdateManagerOrganism from "../../organisms/update-manager/update-manager.organism";
import JobsPage from "../jobs/jobs.page";
@@ -37,6 +38,7 @@ export function RoutesPage({ bodyshop }) {
</Layout.Sider>
<Layout style={{ background: "#fff" }}>
<Layout.Content style={{ marginLeft: "1rem", height: "100%" }}>
<NotificationModalOrganism />
<Routes>
<Route exact path="/settings" element={<SettingsPage />} />
<Route exact path="/reporting" element={<ReportingPage />} />

View File

@@ -16,7 +16,7 @@ export const UPDATE_SHOP = gql`
mutation UPDATE_SHOP($id: uuid, $shop: bodyshops_set_input!) {
update_bodyshops(where: { id: { _eq: $id } }, _set: $shop) {
returning {
id
id
shopname
targets
accepted_ins_co
@@ -26,3 +26,18 @@ export const UPDATE_SHOP = gql`
}
}
`;
export const QUERY_NOTIFICATIONS = gql`
query QUERY_NOTIFICATIONS($now: timestamptz) {
notifications(
where: { acceptedat: { _is_null: true }, effectivedate: { _lte: $now } }
) {
effectivedate
html
id
requiresconfirmation
requiresconfirmationby
updated_at
}
}
`;

View File

@@ -0,0 +1,18 @@
import gql from "graphql-tag";
export const ACCEPT_NOTIFICATION = gql`
mutation ACCEPT_NOTIFICATION(
$id: Int!
$notification: notifications_set_input
) {
update_notifications_by_pk(pk_columns: { id: $id }, _set: $notification) {
html
id
requiresconfirmation
requiresconfirmationby
updated_at
acceptedat
effectivedate
}
}
`;

View File

@@ -150,6 +150,7 @@
"UPLANDER",
"YUKON",
"YUKON DENALI",
"YUKON XL",
"EQUINOX LS",
"EQUINOX LT",
"EQUINOX PREMIER",
@@ -164,6 +165,7 @@
"RAV4 XLE HYBRID",
"HIGHLANDER",
"4RUNNER",
"SEQUOIA",
"PATHFINDER SE",
"PATHFINDER SL",

View File

@@ -85,3 +85,10 @@ export const validatePasswordResetFailure = (error) => ({
payload: error,
});
export const setNotification = (notificationObject) => ({
type: UserActionTypes.SET_NOTIFICATIONS,
payload: notificationObject,
});
export const checkForNotification = () => ({
type: UserActionTypes.CHECK_FOR_NOTIFICATION,
});

View File

@@ -15,10 +15,13 @@ const INITIAL_STATE = {
success: false,
},
loginLoading: false,
notifications: null,
};
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case UserActionTypes.SET_NOTIFICATIONS:
return { ...state, notifications: action.payload };
case UserActionTypes.SET_SHOP_DETAILS:
return { ...state, bodyshop: action.payload };
case UserActionTypes.SET_LOCAL_FINGERPRINT:

View File

@@ -1,12 +1,16 @@
import { message } from "antd";
import { message, notification } from "antd";
import moment from "moment";
//import LogRocket from "logrocket";
import { all, call, put, takeLatest } from "redux-saga/effects";
import { all, call, put, takeLatest, delay } from "redux-saga/effects";
import {
auth,
getCurrentUser,
updateCurrentUser,
} from "../../firebase/firebase.utils";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import {
QUERY_BODYSHOP,
QUERY_NOTIFICATIONS,
} from "../../graphql/bodyshop.queries";
import client from "../../graphql/GraphQLClient";
import { UPSERT_USER } from "../../graphql/user.queries";
import ipcTypes from "../../ipc.types";
@@ -20,6 +24,8 @@ import {
signOutSuccess,
unauthorizedUser,
updateUserDetailsSuccess,
checkForNotification,
setNotification,
} from "./user.actions";
import UserActionTypes from "./user.types";
@@ -144,6 +150,8 @@ export function* signInSuccessSaga({ payload }) {
ipcRenderer.send(ipcTypes.default.fileWatcher.toMain.start, {
startup: true,
});
yield put(checkForNotification());
//Check for notifications, and continue to check.
} else {
console.log("No bodyshop has been associated.");
yield put(setBodyshop(false));
@@ -153,6 +161,27 @@ export function* signInSuccessSaga({ payload }) {
// yield logImEXEvent("redux_sign_in_success");
}
export function* onCheckForNotification() {
yield takeLatest(
UserActionTypes.CHECK_FOR_NOTIFICATION,
checkForNotificationSaga
);
}
export function* checkForNotificationSaga() {
const {
data: { notifications },
} = yield client.query({
query: QUERY_NOTIFICATIONS,
variables: { now: moment() },
});
if (notifications) {
yield put(setNotification(notifications));
}
yield delay(4 * 60 * 60 * 1000);
yield put(checkForNotification());
}
export function* onSendPasswordResetStart() {
yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
@@ -181,6 +210,7 @@ export function* userSagas() {
call(onUpdateUserDetails),
call(onSignInSuccess),
call(onSendPasswordResetStart),
call(onCheckForNotification),
]);
}

View File

@@ -26,3 +26,7 @@ export const selectLoginLoading = createSelector(
[selectUser],
(user) => user.loginLoading
);
export const selectNotifications = createSelector(
[selectUser],
(user) => user.notifications
);

View File

@@ -27,5 +27,7 @@ const UserActionTypes = {
VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS",
VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE",
SET_AUTH_LEVEL: "SET_AUTH_LEVEL",
CHECK_FOR_NOTIFICATION: "CHECK_FOR_NOTIFICATION",
SET_NOTIFICATIONS: "SET_NOTIFICATIONS",
};
export default UserActionTypes;
export default UserActionTypes;