302 lines
8.5 KiB
TypeScript
302 lines
8.5 KiB
TypeScript
"use client";
|
||
|
||
import React from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import { useDispatch, useSelector } from "react-redux";
|
||
import { AppDispatch, RootState } from "@/app/redux/store";
|
||
import { updateUserDetails } from "@/app/redux/user/userSlice";
|
||
import { IEditUserForm, EditUserField } from "../User.interfaces";
|
||
import { IUser } from "../../Pages/Admin/Users/interfaces";
|
||
import toast from "react-hot-toast";
|
||
import { Switch, FormControlLabel } from "@mui/material";
|
||
import "./EditUser.scss";
|
||
import { selectAppMetadata } from "@/app/redux/metadata/selectors";
|
||
|
||
const EditUser = ({ user }: { user: IUser }) => {
|
||
const router = useRouter();
|
||
const dispatch = useDispatch<AppDispatch>();
|
||
const { status } = useSelector((state: RootState) => state.user);
|
||
// Get original Metadata from Redux store
|
||
const data = useSelector(selectAppMetadata);
|
||
const {
|
||
merchants: metadataMerchants = [],
|
||
groups: metadataGroups = [],
|
||
job_titles: metadataJobTitles = [],
|
||
} = data || {};
|
||
|
||
// Get original user data from the API response
|
||
const {
|
||
username,
|
||
first_name,
|
||
last_name,
|
||
email,
|
||
groups,
|
||
phone,
|
||
enabled,
|
||
job_title,
|
||
merchants,
|
||
} = user;
|
||
|
||
const [form, setForm] = React.useState<IEditUserForm>({
|
||
first_name: first_name || "",
|
||
last_name: last_name || "",
|
||
email: email || "",
|
||
phone: phone || "",
|
||
groups: groups || [],
|
||
merchants: merchants || [],
|
||
job_title: job_title || "",
|
||
username: username || "",
|
||
});
|
||
|
||
const handleChange = (
|
||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||
) => {
|
||
const name = e.target.name as EditUserField;
|
||
const value = e.target.value;
|
||
|
||
if (name === "phone") {
|
||
const filtered = value.replace(/[^0-9+\-\s()]/g, "");
|
||
setForm(prev => ({ ...prev, phone: filtered }));
|
||
return;
|
||
}
|
||
|
||
// Handle array fields (merchants and groups)
|
||
if (name === "merchants" || name === "groups") {
|
||
if (value === "") {
|
||
// If empty selection, set empty array
|
||
setForm(prev => ({ ...prev, [name]: [] }));
|
||
} else {
|
||
// Add the selected value to the array if not already present
|
||
setForm(prev => {
|
||
const currentArray = prev[name as keyof IEditUserForm] as string[];
|
||
if (!currentArray.includes(value)) {
|
||
return { ...prev, [name]: [...currentArray, value] };
|
||
}
|
||
return prev;
|
||
});
|
||
}
|
||
} else {
|
||
// Handle single value fields
|
||
setForm(prev => ({ ...prev, [name]: value }));
|
||
}
|
||
};
|
||
|
||
const handleUpdate = async (
|
||
e?: React.FormEvent<HTMLFormElement> | Partial<{ enabled: boolean }>
|
||
) => {
|
||
if (e && "preventDefault" in e) e.preventDefault();
|
||
|
||
// Check if this was a toggle-only update
|
||
const isToggle = !e || ("enabled" in e && Object.keys(e).length === 1);
|
||
|
||
let updates: Record<string, unknown> = {};
|
||
|
||
// If toggle, send entire user object with updated enabled field (excluding id)
|
||
if (isToggle) {
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||
const { id, ...rest } = user;
|
||
updates = {
|
||
...rest,
|
||
enabled: !(enabled ?? true),
|
||
};
|
||
} else {
|
||
// Compare form fields vs original user object
|
||
Object.entries(form).forEach(([key, value]) => {
|
||
const originalValue = (user as unknown as Record<string, unknown>)[key];
|
||
// Only include changed values and skip empty ones
|
||
if (
|
||
value !== "" &&
|
||
JSON.stringify(value) !== JSON.stringify(originalValue)
|
||
) {
|
||
updates[key] = value;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Nothing changed — no need to call API
|
||
if (Object.keys(updates).length === 0) {
|
||
toast("No changes detected");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const resultAction = await dispatch(
|
||
updateUserDetails({ id: user.id, updates })
|
||
);
|
||
|
||
if (updateUserDetails.fulfilled.match(resultAction)) {
|
||
toast.success(resultAction.payload.message);
|
||
router.refresh();
|
||
} else {
|
||
toast.error(
|
||
(resultAction.payload as string) || "Failed to update user"
|
||
);
|
||
}
|
||
} catch (err) {
|
||
console.error("Failed to update user:", err);
|
||
toast.error("An unexpected error occurred");
|
||
}
|
||
};
|
||
|
||
const loading = status === "loading";
|
||
|
||
return (
|
||
<form className="edit-user" onSubmit={handleUpdate}>
|
||
<div className="edit-user__status-toggle">
|
||
<FormControlLabel
|
||
control={
|
||
<Switch
|
||
checked={enabled ?? true}
|
||
onChange={() => handleUpdate()}
|
||
disabled={loading}
|
||
color={(enabled ?? true) ? "success" : "default"}
|
||
/>
|
||
}
|
||
label={(enabled ?? true) ? "User Enabled" : "User Disabled"}
|
||
/>
|
||
</div>
|
||
|
||
<input
|
||
type="text"
|
||
placeholder="First Name"
|
||
name="first_name"
|
||
value={form.first_name}
|
||
onChange={handleChange}
|
||
/>
|
||
<input
|
||
type="text"
|
||
placeholder="Last Name"
|
||
name="last_name"
|
||
value={form.last_name}
|
||
onChange={handleChange}
|
||
/>
|
||
<input
|
||
type="email"
|
||
placeholder="Email"
|
||
name="email"
|
||
value={form.email}
|
||
onChange={handleChange}
|
||
/>
|
||
<input
|
||
type="text"
|
||
placeholder="Username"
|
||
name="username"
|
||
value={form.username}
|
||
onChange={handleChange}
|
||
/>
|
||
<div className="array-field-container">
|
||
<label>Merchants:</label>
|
||
<select
|
||
name="merchants"
|
||
value=""
|
||
onChange={handleChange}
|
||
className="edit-user__select"
|
||
>
|
||
<option value="">Select Merchant</option>
|
||
{metadataMerchants.map((merchant: string) => (
|
||
<option key={merchant} value={merchant}>
|
||
{merchant}
|
||
</option>
|
||
))}
|
||
</select>
|
||
{form.merchants.length > 0 && (
|
||
<div className="selected-items">
|
||
{form.merchants.map((merchant, index) => (
|
||
<span key={index} className="selected-item">
|
||
{merchant}
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
setForm(prev => ({
|
||
...prev,
|
||
merchants: prev.merchants.filter((_, i) => i !== index),
|
||
}));
|
||
}}
|
||
className="remove-item"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="array-field-container">
|
||
<label>Groups:</label>
|
||
<select
|
||
name="groups"
|
||
value=""
|
||
onChange={handleChange}
|
||
className="edit-user__select"
|
||
>
|
||
<option value="">Select Group</option>
|
||
{metadataGroups.map((group: string) => (
|
||
<option key={group} value={group}>
|
||
{group}
|
||
</option>
|
||
))}
|
||
</select>
|
||
{form.groups.length > 0 && (
|
||
<div className="selected-items">
|
||
{form.groups.map((group, index) => (
|
||
<span key={index} className="selected-item">
|
||
{group}
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
setForm(prev => ({
|
||
...prev,
|
||
groups: prev.groups.filter((_, i) => i !== index),
|
||
}));
|
||
}}
|
||
className="remove-item"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="array-field-container">
|
||
<label>Job Title:</label>
|
||
<select
|
||
name="job_title"
|
||
value={form.job_title}
|
||
onChange={handleChange}
|
||
className="edit-user__select"
|
||
>
|
||
<option value="">Select Job Title</option>
|
||
{metadataJobTitles?.map((job_title: string) => (
|
||
<option key={job_title} value={job_title}>
|
||
{job_title}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
<input
|
||
type="tel"
|
||
placeholder="Phone"
|
||
name="phone"
|
||
value={form.phone}
|
||
maxLength={15}
|
||
pattern="[0-9+\-\s()]*"
|
||
onChange={handleChange}
|
||
inputMode="tel"
|
||
autoComplete="tel"
|
||
/>
|
||
|
||
<div className="edit-user__button-container">
|
||
<button type="submit" disabled={loading}>
|
||
{loading ? "Saving..." : "Save"}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
);
|
||
};
|
||
|
||
export default EditUser;
|