Files
bodyshop/client/src/components/profile-my/notification-settings.component.jsx
2025-02-11 12:33:13 -05:00

187 lines
6.2 KiB
JavaScript

import { useMutation, useQuery } from "@apollo/client";
import { useEffect, useState } from "react";
import { Button, Card, Form, Checkbox, Table } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import { QUERY_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATION_SETTINGS } from "../../graphql/user.queries.js";
import { notificationScenarios } from "../../utils/jobNotificationScenarios.js";
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
/**
* ColumnHeaderCheckbox
*
* A header checkbox for a given channel that toggles the state for all scenarios.
*
* Props:
* - channel: The notification channel (e.g. "app", "email", "fcm").
* - form: The Ant Design form instance.
* - disabled: (Optional) If true, the checkbox will be disabled.
*/
const ColumnHeaderCheckbox = ({ channel, form, disabled = false }) => {
const { t } = useTranslation();
// Watch the entire form values so that this component re-renders on changes.
const formValues = Form.useWatch([], form) || {};
// Use the known scenarios list to decide if every row has this channel enabled.
const allChecked =
notificationScenarios.length > 0 && notificationScenarios.every((scenario) => formValues[scenario]?.[channel]);
const onChange = (e) => {
const checked = e.target.checked;
// Get current form values.
const currentValues = form.getFieldsValue();
// Update each scenario for this channel.
const newValues = { ...currentValues };
notificationScenarios.forEach((scenario) => {
newValues[scenario] = { ...newValues[scenario], [channel]: checked };
});
form.setFieldsValue(newValues);
};
return (
<Checkbox onChange={onChange} checked={allChecked} disabled={disabled}>
{t(`notifications.channels.${channel}`)}
</Checkbox>
);
};
function NotificationSettingsForm({ currentUser }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const [initialValues, setInitialValues] = useState({});
const [isDirty, setIsDirty] = useState(false);
// Fetch notification settings
const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: { email: currentUser.email },
skip: !currentUser
});
const [updateNotificationSettings, { loading: saving }] = useMutation(UPDATE_NOTIFICATION_SETTINGS);
// Populate form with fetched data
useEffect(() => {
if (data?.associations?.length > 0) {
const settings = data.associations[0].notification_settings || {};
// For each scenario, expect an object with keys { app, email, fcm }.
// If not present in the fetched data, default to all false.
const formattedValues = notificationScenarios.reduce((acc, scenario) => {
acc[scenario] = settings[scenario] ?? { app: false, email: false, fcm: false };
return acc;
}, {});
setInitialValues(formattedValues);
form.setFieldsValue(formattedValues);
setIsDirty(false); // Reset dirty state when new data loads
}
}, [data, form]);
const handleSave = async (values) => {
if (data?.associations?.length > 0) {
const userId = data.associations[0].id;
// `values` now contains, for each scenario, an object with keys { app, email, fcm }
await updateNotificationSettings({ variables: { id: userId, ns: values } });
setInitialValues(values);
setIsDirty(false);
}
};
const handleFormChange = () => {
setIsDirty(true);
};
const handleReset = () => {
form.setFieldsValue(initialValues);
setIsDirty(false);
};
if (error) return <AlertComponent type="error" message={error.message} />;
if (loading) return <LoadingSpinner />;
const columns = [
{
title: t("notifications.labels.scenario"),
dataIndex: "scenarioLabel",
key: "scenario",
render: (_, record) => t(`notifications.scenarios.${record.key}`),
width: "90%"
},
{
title: <ColumnHeaderCheckbox channel="app" form={form} />,
dataIndex: "app",
key: "app",
align: "center", // Center the cell content
render: (_, record) => (
<Form.Item name={[record.key, "app"]} valuePropName="checked" noStyle>
<Checkbox />
</Form.Item>
)
},
{
title: <ColumnHeaderCheckbox channel="email" form={form} />,
dataIndex: "email",
key: "email",
align: "center", // Center the cell content
render: (_, record) => (
<Form.Item name={[record.key, "email"]} valuePropName="checked" noStyle>
<Checkbox />
</Form.Item>
)
},
{
title: <ColumnHeaderCheckbox channel="fcm" form={form} disabled />,
dataIndex: "fcm",
key: "fcm",
align: "center", // Center the cell content
render: (_, record) => (
<Form.Item name={[record.key, "fcm"]} valuePropName="checked" noStyle>
<Checkbox disabled />
</Form.Item>
)
}
];
// Create dataSource from the list of scenarios.
const dataSource = notificationScenarios.map((scenario) => ({ key: scenario }));
return (
<Form
form={form}
onFinish={handleSave}
onValuesChange={handleFormChange}
initialValues={initialValues}
autoComplete="off"
layout="vertical"
>
<Card
title={t("notifications.labels.notificationscenarios")}
extra={
<>
<Button type="default" onClick={handleReset} disabled={!isDirty} style={{ marginRight: 8 }}>
{t("general.actions.clear")}
</Button>
<Button type="primary" htmlType="submit" disabled={!isDirty} loading={saving}>
{t("notifications.labels.save")}
</Button>
</>
}
>
<Table dataSource={dataSource} columns={columns} pagination={false} bordered rowKey="key" />
</Card>
</Form>
);
}
// Redux connection
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
export default connect(mapStateToProps)(NotificationSettingsForm);